ole2_extractor.c (35110B)
1 /* 2 This file is part of libextractor. 3 Copyright (C) 2004--2026 Vidyut Samanta and Christian Grothoff 4 5 libextractor is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published 7 by the Free Software Foundation; either version 3, or (at your 8 option) any later version. 9 10 libextractor is distributed in the hope that it will be useful, but 11 WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with libextractor; see the file COPYING. If not, write to the 17 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 18 Boston, MA 02110-1301, USA. 19 20 This code makes extensive use of libgsf 21 -- the Gnome Structured File Library 22 Copyright Copyright (C) 2002-2004 Jody Goldberg (jody@gnome.org) 23 24 Part of this code was adapted from wordleaker. 25 */ 26 /** 27 * @file plugins/ole2_extractor.c 28 * @brief plugin to support OLE2 (DOC, XLS, etc.) files 29 * @author Christian Grothoff 30 */ 31 #include "platform.h" 32 #include "extractor.h" 33 #include "convert.h" 34 #include "msoffice_biff.h" 35 #include <glib-object.h> 36 #include <string.h> 37 #include <stdio.h> 38 #include <ctype.h> 39 #include <gsf/gsf-utils.h> 40 #include <gsf/gsf-input-impl.h> 41 #include <gsf/gsf-input-memory.h> 42 #include <gsf/gsf-impl-utils.h> 43 #include <gsf/gsf-infile.h> 44 #include <gsf/gsf-infile-msole.h> 45 #include <gsf/gsf-msole-utils.h> 46 47 48 /** 49 * Set to 1 to use our own GsfInput subclass which supports seeking 50 * and thus can handle very large files. Set to 0 to use the simple 51 * gsf in-memory buffer (which can only access the first ~16k) for 52 * debugging. 53 */ 54 #define USE_LE_INPUT 1 55 56 57 /** 58 * Give the given UTF8 string to LE by calling 'proc'. 59 * 60 * @param proc callback to invoke 61 * @param proc_cls closure for proc 62 * @param phrase metadata string to pass; may include spaces 63 * just double-quotes or just a space in a double quote; 64 * in those cases, nothing should be done 65 * @param type meta data type to use 66 * @return if 'proc' returned 1, otherwise 0 67 */ 68 static int 69 add_metadata (EXTRACTOR_MetaDataProcessor proc, 70 void *proc_cls, 71 const char *phrase, 72 enum EXTRACTOR_MetaType type) 73 { 74 char *tmp; 75 int ret; 76 77 if (0 == strlen (phrase)) 78 return 0; 79 if (0 == strcmp (phrase, "\"\"")) 80 return 0; 81 if (0 == strcmp (phrase, "\" \"")) 82 return 0; 83 if (0 == strcmp (phrase, " ")) 84 return 0; 85 if (NULL == (tmp = strdup (phrase))) 86 return 0; 87 88 while ( (strlen (tmp) > 0) && 89 (isblank ((unsigned char) tmp [strlen (tmp) - 1])) ) 90 tmp [strlen (tmp) - 1] = '\0'; 91 ret = proc (proc_cls, 92 "ole2", 93 type, 94 EXTRACTOR_METAFORMAT_UTF8, 95 "text/plain", 96 tmp, 97 strlen (tmp) + 1); 98 free (tmp); 99 return ret; 100 } 101 102 103 /** 104 * Entry in the map from OLE meta type strings 105 * to LE types. 106 */ 107 struct Matches 108 { 109 /** 110 * OLE description. 111 */ 112 const char *text; 113 114 /** 115 * Corresponding LE type. 116 */ 117 enum EXTRACTOR_MetaType type; 118 }; 119 120 121 static struct Matches tmap[] = { 122 { "Title", EXTRACTOR_METATYPE_TITLE }, 123 { "PresentationFormat", EXTRACTOR_METATYPE_FORMAT }, 124 { "Category", EXTRACTOR_METATYPE_SECTION }, 125 { "Manager", EXTRACTOR_METATYPE_MANAGER }, 126 { "Company", EXTRACTOR_METATYPE_COMPANY }, 127 { "Subject", EXTRACTOR_METATYPE_SUBJECT }, 128 { "Author", EXTRACTOR_METATYPE_AUTHOR_NAME }, 129 { "Keywords", EXTRACTOR_METATYPE_KEYWORDS }, 130 { "Comments", EXTRACTOR_METATYPE_COMMENT }, 131 { "Template", EXTRACTOR_METATYPE_TEMPLATE }, 132 { "NumPages", EXTRACTOR_METATYPE_PAGE_COUNT }, 133 { "AppName", EXTRACTOR_METATYPE_PRODUCED_BY_SOFTWARE }, 134 { "RevisionNumber", EXTRACTOR_METATYPE_REVISION_NUMBER }, 135 { "NumBytes", EXTRACTOR_METATYPE_EMBEDDED_FILE_SIZE }, 136 { "CreatedTime", EXTRACTOR_METATYPE_CREATION_DATE }, 137 { "LastSavedTime", EXTRACTOR_METATYPE_MODIFICATION_DATE }, 138 { "gsf:company", EXTRACTOR_METATYPE_COMPANY }, 139 { "gsf:character-count", EXTRACTOR_METATYPE_CHARACTER_COUNT }, 140 { "gsf:page-count", EXTRACTOR_METATYPE_PAGE_COUNT }, 141 { "gsf:line-count", EXTRACTOR_METATYPE_LINE_COUNT }, 142 { "gsf:word-count", EXTRACTOR_METATYPE_WORD_COUNT }, 143 { "gsf:paragraph-count", EXTRACTOR_METATYPE_PARAGRAPH_COUNT }, 144 { "gsf:last-saved-by", EXTRACTOR_METATYPE_LAST_SAVED_BY }, 145 { "gsf:manager", EXTRACTOR_METATYPE_MANAGER }, 146 { "dc:title", EXTRACTOR_METATYPE_TITLE }, 147 { "dc:creator", EXTRACTOR_METATYPE_CREATOR }, 148 { "dc:date", EXTRACTOR_METATYPE_UNKNOWN_DATE }, 149 { "dc:subject", EXTRACTOR_METATYPE_SUBJECT }, 150 { "dc:keywords", EXTRACTOR_METATYPE_KEYWORDS }, 151 { "dc:last-printed", EXTRACTOR_METATYPE_LAST_PRINTED }, 152 { "dc:description", EXTRACTOR_METATYPE_DESCRIPTION }, 153 { "meta:creation-date", EXTRACTOR_METATYPE_CREATION_DATE }, 154 { "meta:generator", EXTRACTOR_METATYPE_CREATED_BY_SOFTWARE }, 155 { "meta:template", EXTRACTOR_METATYPE_TEMPLATE }, 156 { "meta:editing-cycles", EXTRACTOR_METATYPE_EDITING_CYCLES }, 157 /* { "Dictionary", EXTRACTOR_METATYPE_LANGUAGE }, */ 158 /* { "gsf:security", EXTRACTOR_SECURITY }, */ 159 /* { "gsf:scale", EXTRACTOR_SCALE }, // always "false"? */ 160 /* { "meta:editing-duration", EXTRACTOR_METATYPE_TOTAL_EDITING_TIME }, // encoding? */ 161 /* { "msole:codepage", EXTRACTOR_CHARACTER_SET }, */ 162 { NULL, 0 } 163 }; 164 165 166 /** 167 * Closure for 'process_metadata'. 168 */ 169 struct ProcContext 170 { 171 /** 172 * Function to call for meta data that was found. 173 */ 174 EXTRACTOR_MetaDataProcessor proc; 175 176 /** 177 * Closure for @e proc. 178 */ 179 void *proc_cls; 180 181 /** 182 * Return value; 0 to continue to extract, 1 if we are done 183 */ 184 int ret; 185 }; 186 187 188 /** 189 * Function invoked by 'gst_msole_metadata_read' with 190 * metadata found in the document. 191 * 192 * @param key 'const char *' describing the meta data 193 * @param value the UTF8 representation of the meta data 194 * @param user_data our 'struct ProcContext' (closure) 195 */ 196 static void 197 process_metadata (gpointer key, 198 gpointer value, 199 gpointer user_data) 200 { 201 const char *type = key; 202 const GsfDocProp *prop = value; 203 struct ProcContext *pc = user_data; 204 const GValue *gval; 205 char *contents; 206 int pos; 207 208 if ( (NULL == key) || 209 (NULL == value) ) 210 return; 211 if (0 != pc->ret) 212 return; 213 gval = gsf_doc_prop_get_val (prop); 214 215 if (G_VALUE_TYPE (gval) == G_TYPE_STRING) 216 { 217 const char *gvals; 218 219 gvals = g_value_get_string (gval); 220 if (NULL == gvals) 221 return; 222 contents = strdup (gvals); 223 } 224 else 225 { 226 /* convert other formats? */ 227 contents = g_strdup_value_contents (gval); 228 } 229 if (NULL == contents) 230 return; 231 if (0 == strcmp (type, 232 "meta:generator")) 233 { 234 const char *mimetype = "application/vnd.ms-files"; 235 struct 236 { 237 const char *v; 238 const char *m; 239 } mm[] = { 240 { "Microsoft Word", "application/msword" }, 241 { "Microsoft Office Word", "application/msword" }, 242 { "Microsoft Excel", "application/vnd.ms-excel" }, 243 { "Microsoft Office Excel", "application/vnd.ms-excel" }, 244 { "Microsoft PowerPoint", "application/vnd.ms-powerpoint" }, 245 { "Microsoft Office PowerPoint", "application/vnd.ms-powerpoint"}, 246 { "Microsoft Project", "application/vnd.ms-project" }, 247 { "Microsoft Visio", "application/vnd.visio" }, 248 { "Microsoft Office", "application/vnd.ms-office" }, 249 { NULL, NULL } 250 }; 251 int i; 252 253 for (i = 0; NULL != mm[i].v; i++) 254 if (0 == strncmp (value, 255 mm[i].v, 256 strlen (mm[i].v) + 1)) 257 { 258 mimetype = mm[i].m; 259 break; 260 } 261 if (0 != add_metadata (pc->proc, 262 pc->proc_cls, 263 mimetype, 264 EXTRACTOR_METATYPE_MIMETYPE)) 265 { 266 free (contents); 267 pc->ret = 1; 268 return; 269 } 270 } 271 for (pos = 0; NULL != tmap[pos].text; pos++) 272 if (0 == strcmp (tmap[pos].text, 273 type)) 274 break; 275 if ( (NULL != tmap[pos].text) && 276 (0 != add_metadata (pc->proc, pc->proc_cls, 277 contents, 278 tmap[pos].type)) ) 279 { 280 free (contents); 281 pc->ret = 1; 282 return; 283 } 284 free (contents); 285 } 286 287 288 /** 289 * Function called on (Document)SummaryInformation OLE 290 * streams. 291 * 292 * @param in the input OLE stream 293 * @param proc function to call on meta data found 294 * @param proc_cls closure for proc 295 * @return 0 to continue to extract, 1 if we are done 296 */ 297 static int 298 process (GsfInput *in, 299 EXTRACTOR_MetaDataProcessor proc, 300 void *proc_cls) 301 { 302 struct ProcContext pc; 303 GsfDocMetaData *sections; 304 GError *error; 305 306 pc.proc = proc; 307 pc.proc_cls = proc_cls; 308 pc.ret = 0; 309 sections = gsf_doc_meta_data_new (); 310 #ifdef HAVE_GSF_DOC_META_DATA_READ_FROM_MSOLE 311 error = gsf_doc_meta_data_read_from_msole (sections, in); 312 #else 313 error = gsf_msole_metadata_read (in, sections); 314 #endif 315 if (NULL == error) 316 { 317 gsf_doc_meta_data_foreach (sections, 318 &process_metadata, 319 &pc); 320 } 321 else 322 { 323 g_error_free (error); 324 } 325 g_object_unref (G_OBJECT (sections)); 326 return pc.ret; 327 } 328 329 330 /** 331 * Function called on SfxDocumentInfo OLE 332 * streams. 333 * 334 * @param in the input OLE stream 335 * @param proc function to call on meta data found 336 * @param proc_cls closure for proc 337 * @return 0 to continue to extract, 1 if we are done 338 */ 339 static int 340 process_star_office (GsfInput *src, 341 EXTRACTOR_MetaDataProcessor proc, 342 void *proc_cls) 343 { 344 off_t size = gsf_input_size (src); 345 char buf[0x374]; 346 347 if (size < 0x374) 348 return 0; 349 gsf_input_read (src, 350 sizeof (buf), 351 (unsigned char*) buf); 352 if ( (buf[0] != 0x0F) || 353 (buf[1] != 0x0) || 354 (0 != strncmp (&buf[2], 355 "SfxDocumentInfo", 356 strlen ("SfxDocumentInfo"))) || 357 (buf[0x11] != 0x0B) || 358 (buf[0x13] != 0x00) || /* pw protected! */ 359 (buf[0x12] != 0x00) ) 360 return 0; 361 buf[0xd3] = '\0'; 362 if ( (buf[0x94] + buf[0x93] > 0) && 363 (0 != add_metadata (proc, proc_cls, 364 &buf[0x95], 365 EXTRACTOR_METATYPE_TITLE)) ) 366 return 1; 367 buf[0x114] = '\0'; 368 if ( (buf[0xd5] + buf[0xd4] > 0) && 369 (0 != add_metadata (proc, proc_cls, 370 &buf[0xd6], 371 EXTRACTOR_METATYPE_SUBJECT)) ) 372 return 1; 373 buf[0x215] = '\0'; 374 if ( (buf[0x115] + buf[0x116] > 0) && 375 (0 != add_metadata (proc, proc_cls, 376 &buf[0x117], 377 EXTRACTOR_METATYPE_COMMENT)) ) 378 return 1; 379 buf[0x296] = '\0'; 380 if ( (buf[0x216] + buf[0x217] > 0) && 381 (0 != add_metadata (proc, proc_cls, 382 &buf[0x218], 383 EXTRACTOR_METATYPE_KEYWORDS)) ) 384 return 1; 385 /* fixme: do timestamps, 386 mime-type, user-defined info's */ 387 return 0; 388 } 389 390 391 /** 392 * Report the operating system recorded in the header of an OLE2 393 * property set stream. 394 * 395 * Every property set stream starts with a PropertySetStream header 396 * whose PropertySetSystemIdentifier field ([MS-OLEPS] section 2.21) 397 * names the operating system that wrote it. This is not exposed as a 398 * property and is therefore invisible to libgsf, but it is a reliable 399 * indicator of the system a document was authored on. 400 * 401 * @param src the property set stream, repositioned to the start on return 402 * @param reported set to 1 once we reported an operating system; a 403 * document usually has two property set streams that both 404 * carry the very same identifier 405 * @param proc function to call on meta data found 406 * @param proc_cls closure for @a proc 407 * @return 0 to continue to extract, 1 if we are done 408 */ 409 static int 410 process_system_identifier (GsfInput *src, 411 int *reported, 412 EXTRACTOR_MetaDataProcessor proc, 413 void *proc_cls) 414 { 415 guint8 hdr[8]; 416 uint32_t osver; 417 unsigned int os_type; 418 unsigned int major; 419 unsigned int minor; 420 char buf[128]; 421 int sret; 422 423 if ((gsf_off_t) sizeof (hdr) > gsf_input_size (src)) 424 return 0; 425 if (gsf_input_seek (src, 0, G_SEEK_SET)) 426 return 0; 427 /* Copy the header out before seeking back: the buffer handed to us 428 by gsf_input_read() only stays valid until the input is touched 429 again. */ 430 if (NULL == gsf_input_read (src, sizeof (hdr), hdr)) 431 return 0; 432 if (gsf_input_seek (src, 0, G_SEEK_SET)) 433 return 0; 434 if ( (0xFE != hdr[0]) || 435 (0xFF != hdr[1]) ) 436 return 0; /* not a property set stream after all */ 437 osver = ((uint32_t) hdr[4]) 438 | (((uint32_t) hdr[5]) << 8) 439 | (((uint32_t) hdr[6]) << 16) 440 | (((uint32_t) hdr[7]) << 24); 441 os_type = (unsigned int) (osver >> 16); 442 major = (unsigned int) (osver & 0xFF); 443 minor = (unsigned int) ((osver >> 8) & 0xFF); 444 switch (os_type) 445 { 446 case 0: 447 sret = snprintf (buf, sizeof (buf), 448 _ ("Windows (16 bit) %u.%u"), major, minor); 449 break; 450 case 1: 451 sret = snprintf (buf, sizeof (buf), 452 _ ("Macintosh %u.%u"), major, minor); 453 break; 454 case 2: 455 sret = snprintf (buf, sizeof (buf), 456 _ ("Windows %u.%u"), major, minor); 457 break; 458 default: 459 return 0; 460 } 461 if ( (0 >= sret) || 462 (sizeof (buf) <= (size_t) sret) ) 463 return 0; 464 *reported = 1; 465 return add_metadata (proc, proc_cls, 466 buf, 467 EXTRACTOR_METATYPE_AUTHORING_OS); 468 } 469 470 471 /** 472 * Function called on the "Workbook" (BIFF8) or "Book" (BIFF5) stream 473 * of an Excel document. Reports the user names from the File 474 * Protection Block, which the OLE2 property sets do not cover. 475 * 476 * @param src the workbook stream 477 * @param proc function to call on meta data found 478 * @param proc_cls closure for @a proc 479 * @return 0 to continue to extract, 1 if we are done 480 */ 481 static int 482 process_excel (GsfInput *src, 483 EXTRACTOR_MetaDataProcessor proc, 484 void *proc_cls) 485 { 486 const guint8 *data; 487 gsf_off_t size; 488 size_t want; 489 490 size = gsf_input_size (src); 491 if (8 > size) 492 return 0; 493 /* The File Protection Block is at the very beginning of the globals 494 substream, so we never need to look at the whole workbook. */ 495 want = (size > 64 * 1024) ? 64 * 1024 : (size_t) size; 496 if (gsf_input_seek (src, 0, G_SEEK_SET)) 497 return 0; 498 if (NULL == (data = gsf_input_read (src, want, NULL))) 499 return 0; 500 return (0 < EXTRACTOR_msoffice_biff_extract (data, 501 want, 502 "ole2", 503 proc, 504 proc_cls)) ? 1 : 0; 505 } 506 507 508 /** 509 * Function called on the "Current User" stream of a PowerPoint 510 * document. Its CurrentUserAtom ([MS-PPT] section 2.3.2) records the 511 * name of the user who last opened the presentation for editing -- 512 * which is not the same as, and frequently differs from, the author 513 * recorded in the summary information. 514 * 515 * @param src the "Current User" stream 516 * @param proc function to call on meta data found 517 * @param proc_cls closure for @a proc 518 * @return 0 to continue to extract, 1 if we are done 519 */ 520 static int 521 process_current_user (GsfInput *src, 522 EXTRACTOR_MetaDataProcessor proc, 523 void *proc_cls) 524 { 525 /** 526 * Size of the record header preceding the atom. 527 */ 528 static const size_t hdr_size = 8; 529 /** 530 * Size of the fixed part of the CurrentUserAtom. 531 */ 532 static const size_t atom_size = 20; 533 const guint8 *d; 534 gsf_off_t size; 535 size_t want; 536 size_t len_user; 537 char *name; 538 int ret; 539 540 size = gsf_input_size (src); 541 if ((gsf_off_t) (hdr_size + atom_size) > size) 542 return 0; 543 /* The stream holds nothing but this atom. */ 544 want = (size > 4096) ? 4096 : (size_t) size; 545 if (gsf_input_seek (src, 0, G_SEEK_SET)) 546 return 0; 547 if (NULL == (d = gsf_input_read (src, want, NULL))) 548 return 0; 549 if (0x0FF6 != (d[2] | (d[3] << 8))) 550 return 0; /* not an RT_CurrentUserAtom */ 551 len_user = d[hdr_size + 12] | (d[hdr_size + 13] << 8); 552 if ( (0 == len_user) || 553 (255 < len_user) || 554 (hdr_size + atom_size + len_user > want) ) 555 return 0; 556 /* The name is stored twice: once in the ANSI code page and, if the 557 stream is long enough, once more as UTF-16. Prefer the latter. */ 558 if (hdr_size + atom_size + 3 * len_user + 4 <= want) 559 name = EXTRACTOR_common_convert_to_utf8 ( 560 (const char *) &d[hdr_size + atom_size + len_user + 4], 561 2 * len_user, 562 "UTF-16LE"); 563 else 564 name = EXTRACTOR_common_convert_to_utf8 ( 565 (const char *) &d[hdr_size + atom_size], 566 len_user, 567 "CP1252"); 568 if (NULL == name) 569 return 0; 570 ret = add_metadata (proc, proc_cls, 571 name, 572 EXTRACTOR_METATYPE_LAST_SAVED_BY); 573 free (name); 574 return ret; 575 } 576 577 578 /** 579 * We use "__" to translate using iso-639. 580 * 581 * @param a string to translate 582 * @return translated string 583 */ 584 #define __(a) dgettext ("iso-639", a) 585 586 587 /** 588 * Get the language string for the given language ID (lid) 589 * value. 590 * 591 * @param lid language id value 592 * @return language string corresponding to the lid 593 */ 594 static const char * 595 lid_to_language (unsigned int lid) 596 { 597 switch (lid) 598 { 599 case 0x0400: 600 return _ ("No Proofing"); 601 case 0x0401: 602 return __ ("Arabic"); 603 case 0x0402: 604 return __ ("Bulgarian"); 605 case 0x0403: 606 return __ ("Catalan"); 607 case 0x0404: 608 return _ ("Traditional Chinese"); 609 case 0x0804: 610 return _ ("Simplified Chinese"); 611 case 0x0405: 612 return __ ("Chechen"); 613 case 0x0406: 614 return __ ("Danish"); 615 case 0x0407: 616 return __ ("German"); 617 case 0x0807: 618 return _ ("Swiss German"); 619 case 0x0408: 620 return __ ("Greek"); 621 case 0x0409: 622 return _ ("U.S. English"); 623 case 0x0809: 624 return _ ("U.K. English"); 625 case 0x0c09: 626 return _ ("Australian English"); 627 case 0x040a: 628 return _ ("Castilian Spanish"); 629 case 0x080a: 630 return _ ("Mexican Spanish"); 631 case 0x040b: 632 return __ ("Finnish"); 633 case 0x040c: 634 return __ ("French"); 635 case 0x080c: 636 return _ ("Belgian French"); 637 case 0x0c0c: 638 return _ ("Canadian French"); 639 case 0x100c: 640 return _ ("Swiss French"); 641 case 0x040d: 642 return __ ("Hebrew"); 643 case 0x040e: 644 return __ ("Hungarian"); 645 case 0x040f: 646 return __ ("Icelandic"); 647 case 0x0410: 648 return __ ("Italian"); 649 case 0x0810: 650 return _ ("Swiss Italian"); 651 case 0x0411: 652 return __ ("Japanese"); 653 case 0x0412: 654 return __ ("Korean"); 655 case 0x0413: 656 return __ ("Dutch"); 657 case 0x0813: 658 return _ ("Belgian Dutch"); 659 case 0x0414: 660 return _ ("Norwegian Bokmal"); 661 case 0x0814: 662 return __ ("Norwegian Nynorsk"); 663 case 0x0415: 664 return __ ("Polish"); 665 case 0x0416: 666 return __ ("Brazilian Portuguese"); 667 case 0x0816: 668 return __ ("Portuguese"); 669 case 0x0417: 670 return _ ("Rhaeto-Romanic"); 671 case 0x0418: 672 return __ ("Romanian"); 673 case 0x0419: 674 return __ ("Russian"); 675 case 0x041a: 676 return _ ("Croato-Serbian (Latin)"); 677 case 0x081a: 678 return _ ("Serbo-Croatian (Cyrillic)"); 679 case 0x041b: 680 return __ ("Slovak"); 681 case 0x041c: 682 return __ ("Albanian"); 683 case 0x041d: 684 return __ ("Swedish"); 685 case 0x041e: 686 return __ ("Thai"); 687 case 0x041f: 688 return __ ("Turkish"); 689 case 0x0420: 690 return __ ("Urdu"); 691 case 0x0421: 692 return __ ("Bahasa"); 693 case 0x0422: 694 return __ ("Ukrainian"); 695 case 0x0423: 696 return __ ("Byelorussian"); 697 case 0x0424: 698 return __ ("Slovenian"); 699 case 0x0425: 700 return __ ("Estonian"); 701 case 0x0426: 702 return __ ("Latvian"); 703 case 0x0427: 704 return __ ("Lithuanian"); 705 case 0x0429: 706 return _ ("Farsi"); 707 case 0x042D: 708 return __ ("Basque"); 709 case 0x042F: 710 return __ ("Macedonian"); 711 case 0x0436: 712 return __ ("Afrikaans"); 713 case 0x043E: 714 return __ ("Malayalam"); 715 default: 716 return NULL; 717 } 718 } 719 720 721 /** 722 * Extract editing history from XTable stream. 723 * 724 * @param stream OLE stream to process 725 * @param lcSttbSavedBy length of the revision history in bytes 726 * @param fcSttbSavedBy offset of the revision history in the stream 727 * @param proc function to call on meta data found 728 * @param proc_cls closure for proc 729 * @return 0 to continue to extract, 1 if we are done 730 */ 731 static int 732 history_extract (GsfInput *stream, 733 unsigned int lcbSttbSavedBy, 734 unsigned int fcSttbSavedBy, 735 EXTRACTOR_MetaDataProcessor proc, 736 void *proc_cls) 737 { 738 unsigned int where; 739 unsigned char *lbuffer; 740 unsigned int i; 741 unsigned int length; 742 char *author; 743 char *filename; 744 char *rbuf; 745 unsigned int nRev; 746 int ret; 747 748 /* goto offset of revision information */ 749 gsf_input_seek (stream, fcSttbSavedBy, G_SEEK_SET); 750 if (gsf_input_remaining (stream) < lcbSttbSavedBy) 751 return 0; 752 if (NULL == (lbuffer = malloc (lcbSttbSavedBy))) 753 return 0; 754 /* read all the revision history */ 755 gsf_input_read (stream, lcbSttbSavedBy, lbuffer); 756 /* there are n strings, so n/2 revisions (author & file) */ 757 nRev = (lbuffer[2] + (lbuffer[3] << 8)) / 2; 758 where = 6; 759 ret = 0; 760 for (i = 0; i < nRev; i++) 761 { 762 if (where >= lcbSttbSavedBy) 763 break; 764 length = lbuffer[where++]; 765 if ( (where + 2 * length + 2 >= lcbSttbSavedBy) || 766 (where + 2 * length + 2 <= where) ) 767 break; 768 author = EXTRACTOR_common_convert_to_utf8 ((const char*) &lbuffer[where], 769 length * 2, 770 "UTF-16BE"); 771 where += length * 2 + 1; 772 length = lbuffer[where++]; 773 if ( (where + 2 * length >= lcbSttbSavedBy) || 774 (where + 2 * length + 1 <= where) ) 775 { 776 if (NULL != author) 777 free (author); 778 break; 779 } 780 filename = EXTRACTOR_common_convert_to_utf8 ((const char*) &lbuffer[where], 781 length * 2, 782 "UTF-16BE"); 783 where += length * 2 + 1; 784 if ( (NULL != author) && 785 (NULL != filename) ) 786 { 787 size_t bsize; 788 789 bsize = strlen (author) + strlen (filename) + 512; 790 if (NULL != (rbuf = malloc (bsize))) 791 { 792 int snret; 793 794 snret = snprintf (rbuf, 795 bsize, 796 _ ("Revision #%u: Author `%s' worked on `%s'"), 797 i, 798 author, 799 filename); 800 if ( (-1 != snret) && 801 (bsize > (size_t) snret) ) 802 { 803 ret = add_metadata (proc, 804 proc_cls, 805 rbuf, 806 EXTRACTOR_METATYPE_REVISION_HISTORY); 807 } 808 free (rbuf); 809 } 810 } 811 if (NULL != author) 812 free (author); 813 if (NULL != filename) 814 free (filename); 815 if (0 != ret) 816 break; 817 } 818 free (lbuffer); 819 return ret; 820 } 821 822 823 /* *************************** custom GSF input method ***************** */ 824 825 #define LE_TYPE_INPUT (le_input_get_type ()) 826 #define LE_INPUT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ 827 LE_TYPE_INPUT, \ 828 LeInput)) 829 #define LE_INPUT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), \ 830 LE_TYPE_INPUT, \ 831 LeInputClass)) 832 #define IS_LE_INPUT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ 833 LE_TYPE_INPUT)) 834 #define IS_LE_INPUT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), \ 835 LE_TYPE_INPUT)) 836 #define LE_INPUT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), \ 837 LE_TYPE_INPUT, \ 838 LeInputClass) \ 839 ) 840 841 /** 842 * Internal state of an "LeInput" object. 843 */ 844 typedef struct _LeInputPrivate 845 { 846 /** 847 * Our extraction context. 848 */ 849 struct EXTRACTOR_ExtractContext *ec; 850 } LeInputPrivate; 851 852 853 /** 854 * Overall state of an "LeInput" object. 855 */ 856 typedef struct _LeInput 857 { 858 /** 859 * Inherited state from parent (GsfInput). 860 */ 861 GsfInput input; 862 863 /*< private > */ 864 /** 865 * Private state of the LeInput. 866 */ 867 LeInputPrivate *priv; 868 } LeInput; 869 870 871 /** 872 * LeInput's class state. 873 */ 874 typedef struct _LeInputClass 875 { 876 /** 877 * GsfInput is our parent class. 878 */ 879 GsfInputClass parent_class; 880 881 /* Padding for future expansion */ 882 void (*_gtk_reserved1)(void); 883 void (*_gtk_reserved2)(void); 884 void (*_gtk_reserved3)(void); 885 void (*_gtk_reserved4)(void); 886 } LeInputClass; 887 888 889 /** 890 * Constructor for LeInput objects. 891 * 892 * @param ec extraction context to use 893 * @return the LeInput, NULL on error 894 */ 895 GsfInput * 896 le_input_new (struct EXTRACTOR_ExtractContext *ec); 897 898 899 /** 900 * Class initializer for the "LeInput" class. 901 * 902 * @param class class object to initialize 903 */ 904 static void 905 le_input_class_init (LeInputClass *class); 906 907 908 /** 909 * Initialize internal state of fresh input object. 910 * 911 * @param input object to initialize 912 */ 913 static void 914 le_input_init (LeInput *input); 915 916 917 /** 918 * Macro to create LeInput type definition and register the class. 919 */ 920 GSF_CLASS (LeInput, le_input, le_input_class_init, le_input_init, 921 GSF_INPUT_TYPE) 922 923 924 /** 925 * Duplicate input, leaving the new one at the same offset. 926 * 927 * @param input the input to duplicate 928 * @param err location for error reporting, can be NULL 929 * @return NULL on error (always) 930 */ 931 static GsfInput * 932 le_input_dup (GsfInput * input, 933 GError * *err) 934 { 935 if (NULL != err) 936 *err = g_error_new (gsf_input_error_id (), 0, 937 "dup not supported on LeInput"); 938 return NULL; 939 } 940 941 942 /** 943 * Read at least num_bytes. Does not change the current position if 944 * there is an error. Will only read if the entire amount can be 945 * read. Invalidates the buffer associated with previous calls to 946 * gsf_input_read. 947 * 948 * @param input 949 * @param num_bytes 950 * @param optional_buffer 951 * @return buffer where num_bytes data are available, or NULL on error 952 */ 953 static const guint8 * 954 le_input_read (GsfInput *input, 955 size_t num_bytes, 956 guint8 *optional_buffer) 957 { 958 LeInput *li = LE_INPUT (input); 959 struct EXTRACTOR_ExtractContext *ec; 960 void *buf; 961 uint64_t old_off; 962 ssize_t ret; 963 964 ec = li->priv->ec; 965 old_off = ec->seek (ec->cls, 966 0, 967 SEEK_CUR); 968 if (num_bytes 969 != (ret = ec->read (ec->cls, 970 &buf, 971 num_bytes))) 972 { 973 /* we don't support partial reads; 974 most other GsfInput implementations in this case 975 allocate some huge temporary buffer just to avoid 976 the partial read; we might need to do that as well!? */ 977 ec->seek (ec->cls, 978 old_off, 979 SEEK_SET); 980 return NULL; 981 } 982 if (NULL != optional_buffer) 983 { 984 memcpy (optional_buffer, 985 buf, 986 num_bytes); 987 return optional_buffer; 988 } 989 return buf; 990 } 991 992 993 /** 994 * Move the current location in an input stream 995 * 996 * @param input stream to seek 997 * @param offset target offset 998 * @param whence determines to what the offset is relative to 999 * @return TRUE on error 1000 */ 1001 static gboolean 1002 le_input_seek (GsfInput *input, 1003 gsf_off_t offset, 1004 GSeekType whence) 1005 { 1006 LeInput *li = LE_INPUT (input); 1007 struct EXTRACTOR_ExtractContext *ec; 1008 int w; 1009 int64_t ret; 1010 1011 ec = li->priv->ec; 1012 switch (whence) 1013 { 1014 case G_SEEK_SET: 1015 w = SEEK_SET; 1016 break; 1017 case G_SEEK_CUR: 1018 w = SEEK_CUR; 1019 break; 1020 case G_SEEK_END: 1021 w = SEEK_END; 1022 break; 1023 default: 1024 return TRUE; 1025 } 1026 if (-1 == 1027 (ret = ec->seek (ec->cls, 1028 offset, 1029 w))) 1030 return TRUE; 1031 return FALSE; 1032 } 1033 1034 1035 /** 1036 * Class initializer for the "LeInput" class. 1037 * 1038 * @param class class object to initialize 1039 */ 1040 static void 1041 le_input_class_init (LeInputClass *class) 1042 { 1043 GsfInputClass *input_class; 1044 1045 input_class = (GsfInputClass *) class; 1046 input_class->Dup = le_input_dup; 1047 input_class->Read = le_input_read; 1048 input_class->Seek = le_input_seek; 1049 g_type_class_add_private (class, 1050 sizeof (LeInputPrivate)); 1051 } 1052 1053 1054 /** 1055 * Initialize internal state of fresh input object. 1056 * 1057 * @param input object to initialize 1058 */ 1059 static void 1060 le_input_init (LeInput *input) 1061 { 1062 LeInputPrivate *priv; 1063 1064 input->priv = 1065 G_TYPE_INSTANCE_GET_PRIVATE (input, LE_TYPE_INPUT, 1066 LeInputPrivate); 1067 priv = input->priv; 1068 priv->ec = NULL; 1069 } 1070 1071 1072 /** 1073 * Creates a new LeInput object. 1074 * 1075 * @param ec extractor context to wrap 1076 * @return NULL on error 1077 */ 1078 GsfInput * 1079 le_input_new (struct EXTRACTOR_ExtractContext *ec) 1080 { 1081 LeInput *input; 1082 1083 input = g_object_new (LE_TYPE_INPUT, NULL); 1084 gsf_input_set_size (GSF_INPUT (input), 1085 ec->get_size (ec->cls)); 1086 gsf_input_seek_emulate (GSF_INPUT (input), 1087 0); 1088 input->input.name = NULL; 1089 input->input.container = NULL; 1090 input->priv->ec = ec; 1091 1092 return GSF_INPUT (input); 1093 } 1094 1095 1096 /* *********************** end of custom GSF input method ************* */ 1097 1098 1099 /** 1100 * Main entry method for the OLE2 extraction plugin. 1101 * 1102 * @param ec extraction context provided to the plugin 1103 */ 1104 void 1105 EXTRACTOR_ole2_extract_method (struct EXTRACTOR_ExtractContext *ec); 1106 1107 void 1108 EXTRACTOR_ole2_extract_method (struct EXTRACTOR_ExtractContext *ec) 1109 { 1110 GsfInput *input; 1111 GsfInfile *infile; 1112 GsfInput *src; 1113 const char *name; 1114 unsigned int i; 1115 unsigned int lcb; 1116 unsigned int fcb; 1117 const unsigned char *data512; 1118 unsigned int lid; 1119 const char *lang; 1120 int os_reported = 0; 1121 int ret; 1122 void *data; 1123 uint64_t fsize; 1124 ssize_t data_size; 1125 1126 fsize = ec->get_size (ec->cls); 1127 if (fsize < 512 + 898) 1128 { 1129 /* File too small for OLE2 */ 1130 return; /* can hardly be OLE2 */ 1131 } 1132 if (512 + 898 > (data_size = ec->read (ec->cls, &data, fsize))) 1133 { 1134 /* Failed to read minimum file size to buffer */ 1135 return; 1136 } 1137 data512 = (const unsigned char*) data + 512; 1138 lid = data512[6] + (data512[7] << 8); 1139 if ( (NULL != (lang = lid_to_language (lid))) && 1140 (0 != (ret = add_metadata (ec->proc, ec->cls, 1141 lang, 1142 EXTRACTOR_METATYPE_LANGUAGE))) ) 1143 return; 1144 /* The top byte must be widened before it is shifted: `unsigned char' 1145 promotes to `int', and 0x80 << 24 overflows a 32 bit int. */ 1146 lcb = ((uint32_t) data512[726]) 1147 + (((uint32_t) data512[727]) << 8) 1148 + (((uint32_t) data512[728]) << 16) 1149 + (((uint32_t) data512[729]) << 24); 1150 fcb = ((uint32_t) data512[722]) 1151 + (((uint32_t) data512[723]) << 8) 1152 + (((uint32_t) data512[724]) << 16) 1153 + (((uint32_t) data512[725]) << 24); 1154 if (0 != ec->seek (ec->cls, 1155 0, 1156 SEEK_SET)) 1157 { 1158 /* seek failed!? */ 1159 return; 1160 } 1161 #if USE_LE_INPUT 1162 if (NULL == (input = le_input_new (ec))) 1163 { 1164 fprintf (stderr, "le_input_new failed\n"); 1165 return; 1166 } 1167 #else 1168 input = gsf_input_memory_new ((const guint8 *) data, 1169 data_size, 1170 FALSE); 1171 #endif 1172 if (NULL == (infile = gsf_infile_msole_new (input, NULL))) 1173 { 1174 g_object_unref (G_OBJECT (input)); 1175 return; 1176 } 1177 ret = 0; 1178 for (i = 0; i<gsf_infile_num_children (infile); i++) 1179 { 1180 if (0 != ret) 1181 break; 1182 if (NULL == (name = gsf_infile_name_by_index (infile, i))) 1183 continue; 1184 src = NULL; 1185 if ( ( (0 == strcmp (name, "\005SummaryInformation")) || 1186 (0 == strcmp (name, "\005DocumentSummaryInformation")) ) && 1187 (NULL != (src = gsf_infile_child_by_index (infile, i))) ) 1188 { 1189 if (0 == os_reported) 1190 ret = process_system_identifier (src, 1191 &os_reported, 1192 ec->proc, 1193 ec->cls); 1194 if (0 == ret) 1195 ret = process (src, 1196 ec->proc, 1197 ec->cls); 1198 } 1199 if ( (0 == strcmp (name, "SfxDocumentInfo")) && 1200 (NULL != (src = gsf_infile_child_by_index (infile, i))) ) 1201 ret = process_star_office (src, 1202 ec->proc, 1203 ec->cls); 1204 if ( ( (0 == strcmp (name, "Workbook")) || 1205 (0 == strcmp (name, "Book")) ) && 1206 (NULL != (src = gsf_infile_child_by_index (infile, i))) ) 1207 ret = process_excel (src, 1208 ec->proc, 1209 ec->cls); 1210 if ( (0 == strcmp (name, "Current User")) && 1211 (NULL != (src = gsf_infile_child_by_index (infile, i))) ) 1212 ret = process_current_user (src, 1213 ec->proc, 1214 ec->cls); 1215 if (NULL != src) 1216 g_object_unref (G_OBJECT (src)); 1217 } 1218 if (0 != ret) 1219 goto CLEANUP; 1220 1221 if (lcb < 6) 1222 goto CLEANUP; 1223 for (i = 0; i<gsf_infile_num_children (infile); i++) 1224 { 1225 if (ret != 0) 1226 break; 1227 if (NULL == (name = gsf_infile_name_by_index (infile, i))) 1228 continue; 1229 if ( ( (0 == strcmp (name, "1Table")) || 1230 (0 == strcmp (name, "0Table")) ) && 1231 (NULL != (src = gsf_infile_child_by_index (infile, i))) ) 1232 { 1233 ret = history_extract (src, 1234 lcb, 1235 fcb, 1236 ec->proc, ec->cls); 1237 g_object_unref (G_OBJECT (src)); 1238 } 1239 } 1240 CLEANUP: 1241 g_object_unref (G_OBJECT (infile)); 1242 g_object_unref (G_OBJECT (input)); 1243 } 1244 1245 1246 /** 1247 * Custom log function we give to GSF to disable logging. 1248 * 1249 * @param log_domain unused 1250 * @param log_level unused 1251 * @param message unused 1252 * @param user_data unused 1253 */ 1254 static void 1255 nolog (const gchar *log_domain, 1256 GLogLevelFlags log_level, 1257 const gchar *message, 1258 gpointer user_data) 1259 { 1260 /* do nothing */ 1261 } 1262 1263 1264 /** 1265 * OLE2 plugin constructor. Initializes glib and gsf, in particular 1266 * gsf logging is disabled. 1267 */ 1268 void __attribute__ ((constructor)) 1269 ole2_ltdl_init () 1270 { 1271 #if ! GLIB_CHECK_VERSION (2, 35, 0) 1272 g_type_init (); 1273 #endif 1274 #ifdef HAVE_GSF_INIT 1275 gsf_init (); 1276 #endif 1277 /* disable logging -- thanks, Jody! */ 1278 g_log_set_handler ("libgsf:msole", 1279 G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING, 1280 &nolog, NULL); 1281 } 1282 1283 1284 /** 1285 * OLE2 plugin destructor. Shutdown of gsf. 1286 */ 1287 void __attribute__ ((destructor)) 1288 ole2_ltdl_fini () 1289 { 1290 #ifdef HAVE_GSF_INIT 1291 gsf_shutdown (); 1292 #endif 1293 } 1294 1295 1296 /* end of ole2_extractor.c */