1/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2/* 3 * This file is part of the LibreOffice project. 4 * 5 * This Source Code Form is subject to the terms of the Mozilla Public 6 * License, v. 2.0. If a copy of the MPL was not distributed with this 7 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 * 9 * This file incorporates work covered by the following license notice: 10 * 11 * Licensed to the Apache Software Foundation (ASF) under one or more 12 * contributor license agreements. See the NOTICE file distributed 13 * with this work for additional information regarding copyright 14 * ownership. The ASF licenses this file to you under the Apache 15 * License, Version 2.0 (the "License"); you may not use this file 16 * except in compliance with the License. You may obtain a copy of 17 * the License at http://www.apache.org/licenses/LICENSE-2.0 . 18 */ 19 20#import <zlib.h> 21 22#import "OOoSpotlightImporter.h" 23#import "OOoMetaDataParser.h" 24#import "OOoContentDataParser.h" 25 26/* a dictionary to hold the UTIs */ 27static NSDictionary *uti2kind; 28 29typedef struct { 30 unsigned short min_version; 31 unsigned short general_flag; 32 unsigned short compression; 33 unsigned short lastmod_time; 34 unsigned short lastmod_date; 35 unsigned crc32; 36 unsigned compressed_size; 37 unsigned uncompressed_size; 38 unsigned short filename_size; 39 unsigned short extra_field_size; 40 NSString *filename; 41 NSString *extra_field; 42} LocalFileHeader; 43 44typedef struct { 45 unsigned short creator_version; 46 unsigned short min_version; 47 unsigned short general_flag; 48 unsigned short compression; 49 unsigned short lastmod_time; 50 unsigned short lastmod_date; 51 unsigned crc32; 52 unsigned compressed_size; 53 unsigned uncompressed_size; 54 unsigned short filename_size; 55 unsigned short extra_field_size; 56 unsigned short file_comment_size; 57 unsigned short disk_num; 58 unsigned short internal_attr; 59 unsigned external_attr; 60 unsigned offset; 61 NSString *filename; 62 NSString *extra_field; 63 NSString *file_comment; 64} CentralDirectoryEntry; 65 66typedef struct { 67 unsigned short disk_num; 68 unsigned short cdir_disk; 69 unsigned short disk_entries; 70 unsigned short cdir_entries; 71 unsigned cdir_size; 72 unsigned cdir_offset; 73 unsigned short comment_size; 74 NSString *comment; 75} CentralDirectoryEnd; 76 77#define CDIR_ENTRY_SIG (0x02014b50) 78#define LOC_FILE_HEADER_SIG (0x04034b50) 79#define CDIR_END_SIG (0x06054b50) 80 81static unsigned char readByte(NSFileHandle *file) 82{ 83 if (file == nil) 84 return 0; 85 NSData* tmpBuf = [file readDataOfLength: 1]; 86 if (tmpBuf == nil) 87 return 0; 88 unsigned char *d = (unsigned char*)[tmpBuf bytes]; 89 if (d == nil) 90 return 0; 91 return *d; 92} 93 94static unsigned short readShort(NSFileHandle *file) 95{ 96 unsigned short p0 = (unsigned short)readByte(file); 97 unsigned short p1 = (unsigned short)readByte(file); 98 return (unsigned short)(p0|(p1<<8)); 99} 100 101static unsigned readInt(NSFileHandle *file) 102{ 103 unsigned p0 = (unsigned)readByte(file); 104 unsigned p1 = (unsigned)readByte(file); 105 unsigned p2 = (unsigned)readByte(file); 106 unsigned p3 = (unsigned)readByte(file); 107 return (unsigned)(p0|(p1<<8)|(p2<<16)|(p3<<24)); 108} 109 110static bool readCentralDirectoryEnd(NSFileHandle *file, CentralDirectoryEnd *end) 111{ 112 unsigned signature = readInt(file); 113 if (signature != CDIR_END_SIG) 114 return false; 115 116 end->disk_num = readShort(file); 117 end->cdir_disk = readShort(file); 118 end->disk_entries = readShort(file); 119 end->cdir_entries = readShort(file); 120 end->cdir_size = readInt(file); 121 end->cdir_offset = readInt(file); 122 end->comment_size = readShort(file); 123 NSData *data = [file readDataOfLength: end->comment_size]; 124 end->comment = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 125 return true; 126} 127 128static bool readCentralDirectoryEntry(NSFileHandle *file, CentralDirectoryEntry *entry) 129{ 130 unsigned signature = readInt(file); 131 if (signature != CDIR_ENTRY_SIG) 132 return false; 133 134 entry->creator_version = readShort(file); 135 entry->min_version = readShort(file); 136 entry->general_flag = readShort(file); 137 entry->compression = readShort(file); 138 entry->lastmod_time = readShort(file); 139 entry->lastmod_date = readShort(file); 140 entry->crc32 = readInt(file); 141 entry->compressed_size = readInt(file); 142 entry->uncompressed_size = readInt(file); 143 entry->filename_size = readShort(file); 144 entry->extra_field_size = readShort(file); 145 entry->file_comment_size = readShort(file); 146 entry->disk_num = readShort(file); 147 entry->internal_attr = readShort(file); 148 entry->external_attr = readInt(file); 149 entry->offset = readInt(file); 150 NSData *data = [file readDataOfLength: entry->filename_size]; 151 entry->filename = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 152 data = [file readDataOfLength: entry->extra_field_size]; 153 entry->extra_field = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 154 data = [file readDataOfLength: entry->file_comment_size]; 155 entry->file_comment = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 156 return true; 157} 158 159static bool readLocalFileHeader(NSFileHandle *file, LocalFileHeader *header) 160{ 161 unsigned signature = readInt(file); 162 if (signature != LOC_FILE_HEADER_SIG) 163 return false; 164 165 header->min_version = readShort(file); 166 header->general_flag = readShort(file); 167 header->compression = readShort(file); 168 header->lastmod_time = readShort(file); 169 header->lastmod_date = readShort(file); 170 header->crc32 = readInt(file); 171 header->compressed_size = readInt(file); 172 header->uncompressed_size = readInt(file); 173 header->filename_size = readShort(file); 174 header->extra_field_size = readShort(file); 175 NSData *data = [file readDataOfLength: header->filename_size]; 176 header->filename = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 177 data = [file readDataOfLength: header->extra_field_size]; 178 header->extra_field = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; 179 return true; 180} 181 182static bool areHeadersConsistent(const LocalFileHeader *header, const CentralDirectoryEntry *entry) 183{ 184 if (header->min_version != entry->min_version) 185 return false; 186 if (header->general_flag != entry->general_flag) 187 return false; 188 if (header->compression != entry->compression) 189 return false; 190 if (!(header->general_flag & 0x08)) 191 { 192 if (header->crc32 != entry->crc32) 193 return false; 194 if (header->compressed_size != entry->compressed_size) 195 return false; 196 if (header->uncompressed_size != entry->uncompressed_size) 197 return false; 198 } 199 return true; 200} 201 202static bool findCentralDirectoryEnd(NSFileHandle *file) 203{ 204 // Assume the cdir end is in the last 1024 bytes 205 // Scan backward from end of file for the end signature 206 207 [file seekToEndOfFile]; 208 unsigned long long fileLength = [file offsetInFile]; 209 210 if (fileLength < 10) 211 return false; 212 213 [file seekToFileOffset: (fileLength - 4)]; 214 215 unsigned long long limit; 216 if (fileLength > 1024) 217 limit = fileLength - 1024; 218 else 219 limit = 0; 220 221 unsigned long long offset; 222 while ((offset = [file offsetInFile]) > limit) 223 { 224 unsigned signature = readInt(file); 225 if (signature == CDIR_END_SIG) 226 { 227 // Seek back over the CDIR_END_SIG 228 [file seekToFileOffset: offset]; 229 return true; 230 } 231 else 232 { 233 // Seek one byte back 234 [file seekToFileOffset: (offset - 1)]; 235 } 236 } 237 return false; 238} 239 240static bool isZipFile(NSFileHandle *file) 241{ 242 if (!findCentralDirectoryEnd(file)) 243 return false; 244 CentralDirectoryEnd end; 245 if (!readCentralDirectoryEnd(file, &end)) 246 return false; 247 [file seekToFileOffset: end.cdir_offset]; 248 CentralDirectoryEntry entry; 249 if (!readCentralDirectoryEntry(file, &entry)) 250 return false; 251 [file seekToFileOffset: entry.offset]; 252 LocalFileHeader header; 253 if (!readLocalFileHeader(file, &header)) 254 return false; 255 if (!areHeadersConsistent(&header, &entry)) 256 return false; 257 return true; 258} 259 260static bool findDataStream(NSFileHandle *file, CentralDirectoryEntry *entry, NSString *name) 261{ 262 [file seekToEndOfFile]; 263 unsigned long long fileLength = [file offsetInFile]; 264 if (!findCentralDirectoryEnd(file)) 265 return false; 266 CentralDirectoryEnd end; 267 if (!readCentralDirectoryEnd(file, &end)) 268 return false; 269 [file seekToFileOffset: end.cdir_offset]; 270 do 271 { 272 if (!readCentralDirectoryEntry(file, entry)) 273 return false; 274 if ([entry->filename compare: name] == NSOrderedSame) 275 break; 276 } 277 while ( [file offsetInFile] < fileLength && [file offsetInFile] < end.cdir_offset + end.cdir_size); 278 if ([entry->filename compare: name] != NSOrderedSame) 279 return false; 280 [file seekToFileOffset: entry->offset]; 281 LocalFileHeader header; 282 if (!readLocalFileHeader(file, &header)) 283 return false; 284 if (!areHeadersConsistent(&header, entry)) 285 return false; 286 return true; 287} 288 289static NSData *getUncompressedData(NSFileHandle *file, NSString *name) 290{ 291 CentralDirectoryEntry entry; 292 if (!findDataStream(file, &entry, name)) 293 return nil; 294 if (!entry.compression) 295 return [file readDataOfLength: entry.compressed_size]; 296 else 297 { 298 int ret; 299 z_stream strm; 300 301 /* allocate inflate state */ 302 strm.zalloc = Z_NULL; 303 strm.zfree = Z_NULL; 304 strm.opaque = Z_NULL; 305 strm.avail_in = 0; 306 strm.next_in = Z_NULL; 307 ret = inflateInit2(&strm,-MAX_WBITS); 308 if (ret != Z_OK) 309 return nil; 310 311 NSData *compressedData = [file readDataOfLength: entry.compressed_size]; 312 313 strm.avail_in = [compressedData length]; 314 strm.next_in = (Bytef *)[compressedData bytes]; 315 316 Bytef *uncompressedData = (Bytef *)malloc(entry.uncompressed_size); 317 if (!uncompressedData) 318 { 319 (void)inflateEnd(&strm); 320 return nil; 321 } 322 strm.avail_out = entry.uncompressed_size; 323 strm.next_out = uncompressedData; 324 ret = inflate(&strm, Z_FINISH); 325 switch (ret) 326 { 327 case Z_NEED_DICT: 328 case Z_DATA_ERROR: 329 case Z_MEM_ERROR: 330 (void)inflateEnd(&strm); 331 free(uncompressedData); 332 return nil; 333 } 334 (void)inflateEnd(&strm); 335 NSData *returnBuffer = [NSData dataWithBytes:(const void *)uncompressedData length:entry.uncompressed_size]; 336 free(uncompressedData); 337 return returnBuffer; 338 } 339} 340 341@implementation OOoSpotlightImporter 342 343/* initialize is only called once the first time this class is loaded */ 344+ (void)initialize 345{ 346 static BOOL isInitialized = NO; 347 if (isInitialized == NO) { 348 NSMutableDictionary *temp = [NSMutableDictionary new]; 349 [temp setObject:@"OpenOffice.org 1.0 Text" forKey:@"org.openoffice.text"]; 350 [temp setObject:@"OpenDocument Text" forKey:@"org.oasis.opendocument.text"]; 351 [temp setObject:@"OpenOffice.org 1.0 Spreadsheet" forKey:@"org.openoffice.spreadsheet"]; 352 [temp setObject:@"OpenDocument Spreadsheet" forKey:@"org.oasis.opendocument.spreadsheet"]; 353 [temp setObject:@"OpenOffice.org 1.0 Presentation" forKey:@"org.openoffice.presentation"]; 354 [temp setObject:@"OpenDocument Presentation" forKey:@"org.oasis.opendocument.presentation"]; 355 [temp setObject:@"OpenOffice.org 1.0 Drawing" forKey:@"org.openoffice.graphics"]; 356 [temp setObject:@"OpenDocument Drawing" forKey:@"org.oasis.opendocument.graphics"]; 357 [temp setObject:@"OpenOffice.org 1.0 Master" forKey:@"org.openoffice.text-master"]; 358 [temp setObject:@"OpenDocument Master" forKey:@"org.oasis.opendocument.text-master"]; 359 [temp setObject:@"OpenOffice.org 1.0 Formula" forKey:@"org.openoffice.formula"]; 360 [temp setObject:@"OpenDocument Formula" forKey:@"org.oasis.opendocument.formula"]; 361 [temp setObject:@"OpenOffice.org 1.0 Text Template" forKey:@"org.openoffice.text-template"]; 362 [temp setObject:@"OpenDocument Text Template" forKey:@"org.oasis.opendocument.text-template"]; 363 [temp setObject:@"OpenOffice.org 1.0 Spreadsheet Template" forKey:@"org.openoffice.spreadsheet-template"]; 364 [temp setObject:@"OpenDocument Spreadsheet Template" forKey:@"org.oasis.opendocument.spreadsheet-template"]; 365 [temp setObject:@"OpenOffice.org 1.0 Presentation Template" forKey:@"org.openoffice.presentation-template"]; 366 [temp setObject:@"OpenDocument Presentation Template" forKey:@"org.oasis.opendocument.presentation-template"]; 367 [temp setObject:@"OpenOffice.org 1.0 Drawing Template" forKey:@"org.openoffice.graphics-template"]; 368 [temp setObject:@"OpenDocument Drawing Template" forKey:@"org.oasis.opendocument.graphics-template"]; 369 [temp setObject:@"OpenOffice.org 1.0 Database" forKey:@"org.openoffice.database"]; 370 [temp setObject:@"OpenDocument Chart" forKey:@"org.oasis.opendocument.chart"]; 371 372 uti2kind = [[NSDictionary dictionaryWithDictionary:temp] retain]; 373 [temp release]; 374 375 isInitialized = YES; 376 } 377} 378 379/* importDocument is the real starting point for our plugin */ 380- (BOOL)importDocument:(NSString*)pathToFile contentType:(NSString*)contentTypeUTI attributes:(NSMutableDictionary*)attributes 381{ 382 //NSLog(contentTypeUTI); 383 //NSLog(pathToFile); 384 385 NSString *itemKind = [uti2kind objectForKey:contentTypeUTI]; 386 if (itemKind != nil) { 387 [attributes setObject:itemKind forKey:(NSString*)kMDItemKind]; 388 } 389 390 //first check to see if this is a valid zipped file that contains a file "meta.xml" 391 NSFileHandle *unzipFile = [self openZipFileAtPath:pathToFile]; 392 393 394 if (unzipFile == nil) { 395 //NSLog(@"zip file not open"); 396 return NO; 397 } 398 399 //first get the metadata 400 NSData *metaData = [self metaDataFileFromZip:unzipFile]; 401 if (metaData == nil) { 402 [unzipFile closeFile]; 403 return YES; 404 } 405 406 [metaData retain]; 407 408 OOoMetaDataParser *parser = [OOoMetaDataParser new]; 409 if (parser != nil) { 410 //parse and extract the data 411 [parser parseXML:metaData intoDictionary:attributes]; 412 } 413 414 [metaData release]; 415 [parser release]; 416 417 //and now get the content 418 NSData *contentData = [self contentDataFileFromZip:unzipFile]; 419 if (contentData == nil) { 420 [unzipFile closeFile]; 421 return YES; 422 } 423 424 [contentData retain]; 425 426 OOoContentDataParser *parser2 = [OOoContentDataParser new]; 427 if (parser2 != nil) { 428 //parse and extract the data 429 [parser2 parseXML:contentData intoDictionary:attributes]; 430 } 431 432 [contentData release]; 433 [parser2 release]; 434 435 [unzipFile closeFile]; 436 437 return YES; 438} 439 440/* openZipFileAtPath returns the file as a valid data structure or nil otherwise*/ 441- (NSFileHandle*)openZipFileAtPath:(NSString*)pathToFile 442{ 443 NSFileHandle* unzipFile = nil; 444 445 if ([pathToFile length] != 0) 446 { 447 unzipFile = [NSFileHandle fileHandleForReadingAtPath: pathToFile]; 448 } 449 450 if (unzipFile == nil) 451 { 452 //NSLog(@"Cannot open %s",zipfilename); 453 return nil; 454 } 455 456 if (!isZipFile(unzipFile)) 457 { 458 [unzipFile closeFile]; 459 return nil; 460 } 461 //NSLog(@"%s opened",zipfilename); 462 463 return unzipFile; 464} 465 466/* metaDataFileFromZip extracts the file meta.xml from the zip file and returns it as an NSData* structure 467 or nil if the metadata is not present */ 468- (NSData*) metaDataFileFromZip:(NSFileHandle*)unzipFile 469{ 470 if (unzipFile == nil) 471 return nil; 472 return getUncompressedData(unzipFile, @"meta.xml"); 473} 474 475/* contentDataFileFromZip extracts the file content.xml from the zip file and returns it as an NSData* structure 476 or nil if the metadata is not present */ 477- (NSData*) contentDataFileFromZip:(NSFileHandle*)unzipFile 478{ 479 if (unzipFile == nil) 480 return nil; 481 return getUncompressedData(unzipFile, @"content.xml"); 482} 483 484 485@end 486 487/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 488