rtf_extractor.c (48451B)
1 /* 2 This file is part of libextractor. 3 Copyright (C) 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 /** 21 * @file plugins/rtf_extractor.c 22 * @brief plugin to support Rich Text Format documents 23 * @author Christian Grothoff 24 * 25 * RTF is the interchange format Microsoft Word has written since 1987 26 * and, unlike the binary and OOXML formats, it is plain text -- which 27 * is precisely why so much of it is still around, and why the meta 28 * data it carries is so easy to overlook. 29 * 30 * Besides the document summary in the `\info' destination, an RTF file 31 * routinely records: 32 * 33 * - `\author' and `\operator', that is who created the document and 34 * who last saved it. These are frequently *different* people. 35 * - `\*\company', the organisation the authoring copy of Word was 36 * licensed to. 37 * - `\creatim', `\revtim' and `\printim', in *local* time and without 38 * a time zone, which leaks the author's time zone when compared to 39 * any absolute time stamp in the same document. 40 * - `\*\revtbl', a table naming everybody who ever made a tracked 41 * change, plus `\revauth' / `\revdttm' (and the `\crauth', `\trauth' 42 * and `\pnrauth' variants) which index into it and carry the date 43 * and time of the individual edit. 44 * - `\*\atnauthor' and `\*\atnid', the name and initials behind each 45 * comment. 46 * - `\*\userprops', where Outlook leaves `_AuthorEmail' and 47 * `_AuthorEmailDisplayName' behind when a document is circulated for 48 * review. 49 * - `\*\generator' and `\*\template', naming the exact application 50 * build and, not rarely, a local file system path. 51 * 52 * In a sample of the 1125 RTF files of the govdocs1 corpus, 60% carry 53 * an `\author', 64% an `\operator', 53% a `\*\company' and 11% a 54 * revision table. See https://bugs.gnunet.org/view.php?id=2096. 55 * 56 * The parser is a streaming RTF tokenizer: it never holds more than a 57 * fixed-size window of the file, skips the destinations that carry no 58 * meta data (font tables, pictures, embedded objects and every unknown 59 * ignorable destination), and bounds the number of groups, revision 60 * table entries and reported values it is willing to track. This 61 * keeps it safe on hostile input, which matters because it runs on 62 * whatever a privacy tool is asked to inspect. 63 */ 64 #include "platform.h" 65 #include <ctype.h> 66 #include "extractor.h" 67 #include "convert.h" 68 69 70 /** 71 * Name this plugin reports itself as. 72 */ 73 #define PLUGIN_NAME "rtf" 74 75 /** 76 * Longest meta data value we report. Also bounds the buffer we 77 * accumulate destination text in. 78 */ 79 #define MAX_VALUE 4096 80 81 /** 82 * Deepest group nesting we track individually. Anything below is 83 * still counted, so that the matching closing braces are recognised, 84 * but inherits the state of the deepest tracked group. 85 */ 86 #define MAX_DEPTH 128 87 88 /** 89 * Longest control word we accept. The specification limits control 90 * words to 32 letters. 91 */ 92 #define MAX_CW 32 93 94 /** 95 * Number of distinct strings we remember in order to suppress 96 * duplicates. Documents with tracked changes repeat the same handful 97 * of names thousands of times. 98 */ 99 #define MAX_SEEN 256 100 101 /** 102 * Number of entries we keep from the revision table. 103 */ 104 #define MAX_REVAUTHORS 64 105 106 /** 107 * Number of distinct (author, date) pairs we keep from the revision 108 * marks in the body. They are resolved against the revision table 109 * once the whole file has been read, as the table is allowed to appear 110 * after the marks that use it. 111 */ 112 #define MAX_REVISIONS 64 113 114 /** 115 * Maximum number of bytes we are willing to read from one file. 116 */ 117 #define MAX_SCAN_BYTES (64 * 1024 * 1024) 118 119 /** 120 * Number of bytes we request from the datasource at a time. 121 */ 122 #define READ_CHUNK (64 * 1024) 123 124 125 /** 126 * Destinations we distinguish. Everything not listed here is either 127 * transparent (text in it belongs to the enclosing destination) or 128 * skipped wholesale. 129 */ 130 enum Destination 131 { 132 /** 133 * Nothing in particular; text is discarded. 134 */ 135 DEST_NONE = 0, 136 137 /** 138 * Content of this group is of no interest at all. 139 */ 140 DEST_SKIP, 141 142 /** 143 * The `\info' group; holds the document summary. 144 */ 145 DEST_INFO, 146 147 /** 148 * The `\*\revtbl' group; each of its subgroups names one author. 149 */ 150 DEST_REVTBL, 151 152 /** 153 * The `\*\userprops' group; holds custom document properties. 154 */ 155 DEST_USERPROPS, 156 157 /** 158 * The `\*\annotation' group. We have no use for the text of a 159 * comment, but we do want to see the `\*\atndate' inside it, so it 160 * must not be skipped like an unknown ignorable destination. 161 */ 162 DEST_ANNOTATION, 163 164 /* The remaining destinations hold text that we report. They must 165 stay contiguous and DEST_FIRST_TEXT/DEST_LAST_TEXT must bracket 166 them; see is_text_dest(). */ 167 168 DEST_TITLE, 169 DEST_SUBJECT, 170 DEST_AUTHOR, 171 DEST_OPERATOR, 172 DEST_MANAGER, 173 DEST_COMPANY, 174 DEST_CATEGORY, 175 DEST_KEYWORDS, 176 DEST_DOCCOMM, 177 DEST_HLINKBASE, 178 DEST_GENERATOR, 179 DEST_TEMPLATE, 180 DEST_ATNAUTHOR, 181 DEST_ATNID, 182 DEST_PROPNAME, 183 DEST_STATICVAL, 184 DEST_ATNDATE, 185 186 /* Date destinations; they hold `\yr', `\mo', ... rather than text. */ 187 188 DEST_CREATIM, 189 DEST_REVTIM, 190 DEST_PRINTIM, 191 DEST_BUPTIM 192 }; 193 194 /** 195 * First destination that accumulates text. 196 */ 197 #define DEST_FIRST_TEXT DEST_TITLE 198 199 /** 200 * Last destination that accumulates text. 201 */ 202 #define DEST_LAST_TEXT DEST_ATNDATE 203 204 /** 205 * First destination that assembles a date. 206 */ 207 #define DEST_FIRST_DATE DEST_CREATIM 208 209 210 /** 211 * State of one RTF group. Pushed on `{', popped on `}'. 212 */ 213 struct Group 214 { 215 /** 216 * Destination in effect; inherited from the enclosing group unless 217 * this group introduced one of its own. 218 */ 219 enum Destination dest; 220 221 /** 222 * Number of bytes an unrepresentable Unicode character is replaced 223 * by, from `\ucN'. Scoped like a character property. 224 */ 225 int uc; 226 227 /** 228 * True if this group introduced @e dest, and hence has to flush it 229 * when it is closed. 230 */ 231 int owns; 232 233 /** 234 * True if the content of this group is to be ignored entirely. 235 */ 236 int skip; 237 }; 238 239 240 /** 241 * State kept while extracting from one document. 242 */ 243 struct RtfContext 244 { 245 /** 246 * Extraction context we were called with. 247 */ 248 struct EXTRACTOR_ExtractContext *ec; 249 250 /** 251 * Set to 1 once the caller asked us to stop. 252 */ 253 int stop; 254 255 /** 256 * Buffer most recently returned by the datasource. 257 */ 258 const unsigned char *rbuf; 259 260 /** 261 * Number of valid bytes in @e rbuf. 262 */ 263 size_t rhave; 264 265 /** 266 * Read position in @e rbuf. 267 */ 268 size_t rpos; 269 270 /** 271 * Single byte pushed back, or -1. 272 */ 273 int pushback; 274 275 /** 276 * Total number of bytes read so far. 277 */ 278 uint64_t consumed; 279 280 /** 281 * Set once the datasource is exhausted. 282 */ 283 int eof; 284 285 /** 286 * Group stack; entry 0 is the state outside of any group. 287 */ 288 struct Group stack[MAX_DEPTH]; 289 290 /** 291 * Index of the innermost tracked group in @e stack. 292 */ 293 int depth; 294 295 /** 296 * Number of groups nested below @e stack[MAX_DEPTH - 1] that we 297 * could not track individually. 298 */ 299 unsigned int extra_depth; 300 301 /** 302 * Number of RTF `characters' still to be skipped because of a 303 * preceding `\uN'. 304 */ 305 int skip_units; 306 307 /** 308 * Set by `\*'; the control word that follows introduces an ignorable 309 * destination. 310 */ 311 int pending_ignorable; 312 313 /** 314 * UTF-8 text accumulated for the current destination. 315 */ 316 char utf8[MAX_VALUE + 1]; 317 318 /** 319 * Number of bytes used in @e utf8. 320 */ 321 size_t utf8_len; 322 323 /** 324 * Bytes accumulated in @e charset, not yet converted. 325 */ 326 char raw[MAX_VALUE + 1]; 327 328 /** 329 * Number of bytes used in @e raw. 330 */ 331 size_t raw_len; 332 333 /** 334 * Character set the `\'hh' escapes and plain bytes are in. 335 */ 336 const char *charset; 337 338 /** 339 * True if @e charset was declared by the document rather than being 340 * our fallback. 341 */ 342 int charset_declared; 343 344 /** 345 * Platform the document was written on, if the character set 346 * keyword gives it away, otherwise NULL. `\ansi' does not: every 347 * producer on every system writes that one. 348 */ 349 const char *authoring_os; 350 351 /** 352 * High surrogate seen in a `\uN', waiting for its low half. 353 */ 354 unsigned int surrogate; 355 356 /** 357 * Fields of the date currently being assembled, and whether we saw 358 * a `\yr' at all. 359 */ 360 int have_date; 361 int d_yr; 362 int d_mo; 363 int d_dy; 364 int d_hr; 365 int d_mi; 366 int d_se; 367 368 /** 369 * Name of the custom document property we are inside of. 370 */ 371 char propname[256]; 372 373 /** 374 * Names from the revision table, in table order. 375 */ 376 char *revauth[MAX_REVAUTHORS]; 377 378 /** 379 * Number of used entries in @e revauth. 380 */ 381 unsigned int revauth_len; 382 383 /** 384 * Revision marks seen in the body, to be resolved against 385 * @e revauth at the end. 386 */ 387 struct 388 { 389 /** 390 * Index into the revision table. 391 */ 392 unsigned int idx; 393 394 /** 395 * Packed date and time of the edit. 396 */ 397 uint32_t dttm; 398 } rev[MAX_REVISIONS]; 399 400 /** 401 * Number of used entries in @e rev. 402 */ 403 unsigned int rev_len; 404 405 /** 406 * Author index from the most recent `\revauth' and friends, or -1. 407 */ 408 int pending_author; 409 410 /** 411 * Author of the annotation we are in the middle of, if any. 412 */ 413 char atn_author[256]; 414 415 /** 416 * Strings we have already reported, to suppress duplicates. 417 */ 418 char *seen[MAX_SEEN]; 419 420 /** 421 * Number of used entries in @e seen. 422 */ 423 unsigned int seen_len; 424 }; 425 426 427 /** 428 * Map from a control word introducing a destination to the 429 * destination it introduces. 430 */ 431 static const struct 432 { 433 const char *word; 434 enum Destination dest; 435 } dest_words[] = { 436 { "info", DEST_INFO }, 437 { "revtbl", DEST_REVTBL }, 438 { "userprops", DEST_USERPROPS }, 439 { "annotation", DEST_ANNOTATION }, 440 { "title", DEST_TITLE }, 441 { "subject", DEST_SUBJECT }, 442 { "author", DEST_AUTHOR }, 443 { "operator", DEST_OPERATOR }, 444 { "manager", DEST_MANAGER }, 445 { "company", DEST_COMPANY }, 446 { "category", DEST_CATEGORY }, 447 { "keywords", DEST_KEYWORDS }, 448 { "doccomm", DEST_DOCCOMM }, 449 { "hlinkbase", DEST_HLINKBASE }, 450 { "generator", DEST_GENERATOR }, 451 { "template", DEST_TEMPLATE }, 452 { "atnauthor", DEST_ATNAUTHOR }, 453 { "atnid", DEST_ATNID }, 454 { "atndate", DEST_ATNDATE }, 455 { "propname", DEST_PROPNAME }, 456 { "staticval", DEST_STATICVAL }, 457 { "creatim", DEST_CREATIM }, 458 { "revtim", DEST_REVTIM }, 459 { "printim", DEST_PRINTIM }, 460 { "buptim", DEST_BUPTIM }, 461 { NULL, DEST_NONE } 462 }; 463 464 465 /** 466 * Destinations that are not marked ignorable but that we still have no 467 * use for. Skipping them is not merely an optimisation: `\pict' and 468 * `\objdata' hold megabytes of hex-encoded binary. 469 */ 470 static const char *skip_words[] = { 471 "fonttbl", 472 "colortbl", 473 "stylesheet", 474 "listtable", 475 "listoverridetable", 476 "pict", 477 "objdata", 478 "objalias", 479 "objsect", 480 "objclass", 481 "objname", 482 NULL 483 }; 484 485 486 /** 487 * Map from a text destination to the meta data type to report it as. 488 */ 489 static const struct 490 { 491 enum Destination dest; 492 enum EXTRACTOR_MetaType type; 493 } text_map[] = { 494 { DEST_TITLE, EXTRACTOR_METATYPE_TITLE }, 495 { DEST_SUBJECT, EXTRACTOR_METATYPE_SUBJECT }, 496 { DEST_AUTHOR, EXTRACTOR_METATYPE_AUTHOR_NAME }, 497 { DEST_OPERATOR, EXTRACTOR_METATYPE_LAST_SAVED_BY }, 498 { DEST_MANAGER, EXTRACTOR_METATYPE_MANAGER }, 499 { DEST_COMPANY, EXTRACTOR_METATYPE_COMPANY }, 500 { DEST_CATEGORY, EXTRACTOR_METATYPE_SECTION }, 501 { DEST_KEYWORDS, EXTRACTOR_METATYPE_KEYWORDS }, 502 { DEST_DOCCOMM, EXTRACTOR_METATYPE_COMMENT }, 503 { DEST_HLINKBASE, EXTRACTOR_METATYPE_URL }, 504 { DEST_GENERATOR, EXTRACTOR_METATYPE_CREATED_BY_SOFTWARE }, 505 { DEST_TEMPLATE, EXTRACTOR_METATYPE_TEMPLATE }, 506 { DEST_ATNAUTHOR, EXTRACTOR_METATYPE_CONTRIBUTOR_NAME }, 507 { DEST_ATNID, EXTRACTOR_METATYPE_CONTRIBUTOR_NAME }, 508 { DEST_NONE, EXTRACTOR_METATYPE_UNKNOWN } 509 }; 510 511 512 /** 513 * Map from a date destination to the meta data type to report it as. 514 */ 515 static const struct 516 { 517 enum Destination dest; 518 enum EXTRACTOR_MetaType type; 519 } date_map[] = { 520 { DEST_CREATIM, EXTRACTOR_METATYPE_CREATION_DATE }, 521 { DEST_REVTIM, EXTRACTOR_METATYPE_MODIFICATION_DATE }, 522 { DEST_PRINTIM, EXTRACTOR_METATYPE_LAST_PRINTED }, 523 { DEST_BUPTIM, EXTRACTOR_METATYPE_UNKNOWN_DATE }, 524 { DEST_NONE, EXTRACTOR_METATYPE_UNKNOWN } 525 }; 526 527 528 /** 529 * Custom document properties that carry personal information. Word 530 * writes these when a document is circulated for review from Outlook, 531 * and they routinely outlive the review. 532 */ 533 static const struct 534 { 535 const char *name; 536 enum EXTRACTOR_MetaType type; 537 } custom_map[] = { 538 { "_AuthorEmail", EXTRACTOR_METATYPE_AUTHOR_EMAIL }, 539 { "_AuthorEmailDisplayName", EXTRACTOR_METATYPE_AUTHOR_NAME }, 540 { "_EmailSubject", EXTRACTOR_METATYPE_SUBJECT }, 541 { NULL, EXTRACTOR_METATYPE_UNKNOWN } 542 }; 543 544 545 /* ******************** generic helpers ******************** */ 546 547 548 /** 549 * Check whether @a d is a destination whose text we accumulate. 550 * 551 * @param d destination to check 552 * @return true if text in @a d is to be collected 553 */ 554 static int 555 is_text_dest (enum Destination d) 556 { 557 return (DEST_FIRST_TEXT <= d) && (DEST_LAST_TEXT >= d); 558 } 559 560 561 /** 562 * Trim leading and trailing white space in @a s, in place. 563 * 564 * @param s 0-terminated string to trim 565 * @return pointer into @a s to the first non-blank character 566 */ 567 static char * 568 trim (char *s) 569 { 570 size_t len = strlen (s); 571 572 while ( (0 < len) && 573 (isspace ((unsigned char) s[len - 1])) ) 574 s[--len] = '\0'; 575 while (isspace ((unsigned char) s[0])) 576 s++; 577 return s; 578 } 579 580 581 /** 582 * Check whether @a value was reported before, and remember it if not. 583 * 584 * @param rc our extraction state 585 * @param value string to check 586 * @return 1 if @a value is new (and was remembered), 0 if it is a 587 * duplicate or if we ran out of space to remember it 588 */ 589 static int 590 mark_seen (struct RtfContext *rc, 591 const char *value) 592 { 593 unsigned int i; 594 char *dup; 595 596 for (i = 0; i < rc->seen_len; i++) 597 if (0 == strcmp (rc->seen[i], value)) 598 return 0; 599 if (MAX_SEEN == rc->seen_len) 600 return 0; 601 if (NULL == (dup = strdup (value))) 602 return 0; 603 rc->seen[rc->seen_len++] = dup; 604 return 1; 605 } 606 607 608 /** 609 * Check that @a s is well-formed UTF-8. 610 * 611 * We announce our values as #EXTRACTOR_METAFORMAT_UTF8, so a string 612 * the character set conversion could not handle has to be dropped 613 * rather than passed on. This happens for documents that use a code 614 * page they never declare: `\ansicpg' is optional, and a file written 615 * with a double byte character set but no `\ansicpg' cannot be decoded 616 * from the `\info' group alone. 617 * 618 * @param s 0-terminated string to check 619 * @return true if @a s is valid UTF-8 620 */ 621 static int 622 is_utf8 (const char *s) 623 { 624 const unsigned char *p = (const unsigned char *) s; 625 626 while ('\0' != p[0]) 627 { 628 unsigned int extra; 629 unsigned int i; 630 uint32_t cp; 631 632 if (0x80 > p[0]) 633 { 634 p++; 635 continue; 636 } 637 if ( (0xC2 <= p[0]) && 638 (0xDF >= p[0]) ) 639 { 640 extra = 1; 641 cp = p[0] & 0x1FU; 642 } 643 else if ( (0xE0 <= p[0]) && 644 (0xEF >= p[0]) ) 645 { 646 extra = 2; 647 cp = p[0] & 0x0FU; 648 } 649 else if ( (0xF0 <= p[0]) && 650 (0xF4 >= p[0]) ) 651 { 652 extra = 3; 653 cp = p[0] & 0x07U; 654 } 655 else 656 { 657 return 0; 658 } 659 for (i = 1; i <= extra; i++) 660 { 661 /* A premature 0-terminator fails this test as well, so we never 662 read beyond the end of the string. */ 663 if (0x80 != (p[i] & 0xC0)) 664 return 0; 665 cp = (cp << 6) | (p[i] & 0x3FU); 666 } 667 if ( ( (1 == extra) && (0x80 > cp) ) || 668 ( (2 == extra) && (0x800 > cp) ) || 669 ( (3 == extra) && (0x10000 > cp) ) || 670 (0x10FFFF < cp) || 671 ( (0xD800 <= cp) && (0xDFFF >= cp) ) ) 672 return 0; /* overlong, out of range or a surrogate half */ 673 p += extra + 1; 674 } 675 return 1; 676 } 677 678 679 /** 680 * Report a meta data value, unless it is empty or a duplicate. 681 * 682 * @param rc our extraction state 683 * @param type meta data type to report the value as 684 * @param value the value; leading and trailing white space is removed 685 */ 686 static void 687 add_meta (struct RtfContext *rc, 688 enum EXTRACTOR_MetaType type, 689 const char *value) 690 { 691 char *tmp; 692 char *val; 693 694 if ( (0 != rc->stop) || 695 (NULL == value) ) 696 return; 697 if (MAX_VALUE < strlen (value)) 698 return; 699 if (NULL == (tmp = strdup (value))) 700 return; 701 val = trim (tmp); 702 if ( ('\0' == val[0]) || 703 (0 == is_utf8 (val)) ) 704 { 705 free (tmp); 706 return; 707 } 708 /* De-duplicate per type: the same name legitimately shows up as both 709 an author and a comment author, and both are worth reporting. */ 710 { 711 char full[MAX_VALUE + 64]; 712 713 snprintf (full, 714 sizeof (full), 715 "%d:%s", 716 (int) type, 717 val); 718 if (0 == mark_seen (rc, full)) 719 { 720 free (tmp); 721 return; 722 } 723 } 724 if (0 != rc->ec->proc (rc->ec->cls, 725 PLUGIN_NAME, 726 type, 727 EXTRACTOR_METAFORMAT_UTF8, 728 "text/plain", 729 val, 730 strlen (val) + 1)) 731 rc->stop = 1; 732 free (tmp); 733 } 734 735 736 /* ******************** reading ******************** */ 737 738 739 /** 740 * Obtain the next byte of the file. 741 * 742 * @param rc our extraction state 743 * @return the byte, or -1 at the end of the file 744 */ 745 static int 746 rtf_getc (struct RtfContext *rc) 747 { 748 void *data; 749 ssize_t got; 750 751 if (0 <= rc->pushback) 752 { 753 int c = rc->pushback; 754 755 rc->pushback = -1; 756 return c; 757 } 758 if (rc->rpos < rc->rhave) 759 return rc->rbuf[rc->rpos++]; 760 if (0 != rc->eof) 761 return -1; 762 if (MAX_SCAN_BYTES <= rc->consumed) 763 { 764 rc->eof = 1; 765 return -1; 766 } 767 got = rc->ec->read (rc->ec->cls, 768 &data, 769 READ_CHUNK); 770 if ( (0 >= got) || 771 (NULL == data) ) 772 { 773 rc->eof = 1; 774 return -1; 775 } 776 rc->rbuf = data; 777 rc->rhave = (size_t) got; 778 rc->rpos = 0; 779 rc->consumed += (uint64_t) got; 780 return rc->rbuf[rc->rpos++]; 781 } 782 783 784 /** 785 * Push @a c back, to be returned by the next #rtf_getc(). At most one 786 * byte can be pushed back at a time. 787 * 788 * @param rc our extraction state 789 * @param c byte to push back, or -1 for none 790 */ 791 static void 792 rtf_ungetc (struct RtfContext *rc, 793 int c) 794 { 795 if (0 <= c) 796 rc->pushback = c; 797 } 798 799 800 /* ******************** text accumulation ******************** */ 801 802 803 /** 804 * Convert the bytes accumulated in the current character set and 805 * append the result to the UTF-8 buffer. 806 * 807 * @param rc our extraction state 808 */ 809 static void 810 flush_raw (struct RtfContext *rc) 811 { 812 char *conv; 813 size_t len; 814 815 if (0 == rc->raw_len) 816 return; 817 conv = EXTRACTOR_common_convert_to_utf8 (rc->raw, 818 rc->raw_len, 819 rc->charset); 820 rc->raw_len = 0; 821 if (NULL == conv) 822 return; 823 len = strlen (conv); 824 if (len > MAX_VALUE - rc->utf8_len) 825 len = MAX_VALUE - rc->utf8_len; 826 memcpy (&rc->utf8[rc->utf8_len], 827 conv, 828 len); 829 rc->utf8_len += len; 830 rc->utf8[rc->utf8_len] = '\0'; 831 free (conv); 832 } 833 834 835 /** 836 * Append the byte @a b, which is in the document's character set, to 837 * the text of the current destination. 838 * 839 * @param rc our extraction state 840 * @param b byte to append 841 */ 842 static void 843 add_raw (struct RtfContext *rc, 844 unsigned char b) 845 { 846 if (MAX_VALUE <= rc->raw_len) 847 flush_raw (rc); 848 if (MAX_VALUE <= rc->raw_len) 849 return; 850 rc->raw[rc->raw_len++] = (char) b; 851 } 852 853 854 /** 855 * Append the Unicode code point @a cp to the text of the current 856 * destination. 857 * 858 * @param rc our extraction state 859 * @param cp code point to append 860 */ 861 static void 862 add_codepoint (struct RtfContext *rc, 863 unsigned int cp) 864 { 865 char buf[4]; 866 size_t n; 867 868 flush_raw (rc); 869 if (0x80 > cp) 870 { 871 buf[0] = (char) cp; 872 n = 1; 873 } 874 else if (0x800 > cp) 875 { 876 buf[0] = (char) (0xC0 | (cp >> 6)); 877 buf[1] = (char) (0x80 | (cp & 0x3F)); 878 n = 2; 879 } 880 else if (0x10000 > cp) 881 { 882 buf[0] = (char) (0xE0 | (cp >> 12)); 883 buf[1] = (char) (0x80 | ((cp >> 6) & 0x3F)); 884 buf[2] = (char) (0x80 | (cp & 0x3F)); 885 n = 3; 886 } 887 else 888 { 889 buf[0] = (char) (0xF0 | (cp >> 18)); 890 buf[1] = (char) (0x80 | ((cp >> 12) & 0x3F)); 891 buf[2] = (char) (0x80 | ((cp >> 6) & 0x3F)); 892 buf[3] = (char) (0x80 | (cp & 0x3F)); 893 n = 4; 894 } 895 if (n > MAX_VALUE - rc->utf8_len) 896 return; 897 memcpy (&rc->utf8[rc->utf8_len], 898 buf, 899 n); 900 rc->utf8_len += n; 901 rc->utf8[rc->utf8_len] = '\0'; 902 } 903 904 905 /** 906 * Discard whatever text was accumulated so far. 907 * 908 * @param rc our extraction state 909 */ 910 static void 911 reset_text (struct RtfContext *rc) 912 { 913 rc->utf8_len = 0; 914 rc->utf8[0] = '\0'; 915 rc->raw_len = 0; 916 rc->surrogate = 0; 917 } 918 919 920 /** 921 * Obtain the accumulated text of the current destination. 922 * 923 * @param rc our extraction state 924 * @return 0-terminated UTF-8 string owned by @a rc 925 */ 926 static const char * 927 get_text (struct RtfContext *rc) 928 { 929 flush_raw (rc); 930 return rc->utf8; 931 } 932 933 934 /* ******************** dates ******************** */ 935 936 937 /** 938 * Format the date currently being assembled. 939 * 940 * RTF stores local time and gives no time zone, which is exactly what 941 * makes these stamps interesting: compared against any absolute time 942 * in the same document they reveal where the author sat. We therefore 943 * report them verbatim rather than pretending they were UTC. 944 * 945 * @param rc our extraction state 946 * @param buf where to write the result 947 * @param buf_size number of bytes available in @a buf 948 * @return true if a plausible date was formatted 949 */ 950 static int 951 format_date (struct RtfContext *rc, 952 char *buf, 953 size_t buf_size) 954 { 955 if ( (0 == rc->have_date) || 956 (1600 > rc->d_yr) || 957 (9999 < rc->d_yr) || 958 (1 > rc->d_mo) || 959 (12 < rc->d_mo) || 960 (1 > rc->d_dy) || 961 (31 < rc->d_dy) || 962 (0 > rc->d_hr) || 963 (23 < rc->d_hr) || 964 (0 > rc->d_mi) || 965 (59 < rc->d_mi) || 966 (0 > rc->d_se) || 967 (59 < rc->d_se) ) 968 return 0; 969 return (0 < snprintf (buf, 970 buf_size, 971 "%04d-%02d-%02dT%02d:%02d:%02d", 972 rc->d_yr, 973 rc->d_mo, 974 rc->d_dy, 975 rc->d_hr, 976 rc->d_mi, 977 rc->d_se)); 978 } 979 980 981 /** 982 * Format the packed date and time @a dttm as used by the revision 983 * marks. Its layout is given in the RTF specification: minute in bits 984 * 0-5, hour in 6-10, day of month in 11-15, month in 16-19, year minus 985 * 1900 in 20-28 and the day of the week in 29-31. 986 * 987 * @param dttm packed value 988 * @param buf where to write the result 989 * @param buf_size number of bytes available in @a buf 990 * @return true if a plausible date was formatted 991 */ 992 static int 993 format_dttm (uint32_t dttm, 994 char *buf, 995 size_t buf_size) 996 { 997 unsigned int mi = dttm & 0x3F; 998 unsigned int hr = (dttm >> 6) & 0x1F; 999 unsigned int dy = (dttm >> 11) & 0x1F; 1000 unsigned int mo = (dttm >> 16) & 0x0F; 1001 unsigned int yr = 1900 + ((dttm >> 20) & 0x1FF); 1002 1003 if ( (1 > mo) || 1004 (12 < mo) || 1005 (1 > dy) || 1006 (31 < dy) || 1007 (23 < hr) || 1008 (59 < mi) || 1009 (1980 > yr) ) 1010 return 0; 1011 return (0 < snprintf (buf, 1012 buf_size, 1013 "%04u-%02u-%02uT%02u:%02u:00", 1014 yr, 1015 mo, 1016 dy, 1017 hr, 1018 mi)); 1019 } 1020 1021 1022 /* ******************** reporting ******************** */ 1023 1024 1025 /** 1026 * Report that @a author worked on the document at @a date. 1027 * 1028 * @param rc our extraction state 1029 * @param fmt format string taking the author and the date 1030 * @param author name of the person 1031 * @param date when they edited the document 1032 */ 1033 static void 1034 add_revision (struct RtfContext *rc, 1035 const char *fmt, 1036 const char *author, 1037 const char *date) 1038 { 1039 size_t bsize = strlen (author) + strlen (date) + strlen (fmt) + 1; 1040 char *line; 1041 1042 if (MAX_VALUE < bsize) 1043 return; 1044 if (NULL == (line = malloc (bsize))) 1045 return; 1046 if (0 < snprintf (line, 1047 bsize, 1048 fmt, 1049 author, 1050 date)) 1051 add_meta (rc, 1052 EXTRACTOR_METATYPE_REVISION_HISTORY, 1053 line); 1054 free (line); 1055 } 1056 1057 1058 /** 1059 * Remember an entry of the revision table and report the name. 1060 * 1061 * @param rc our extraction state 1062 * @param name name of the author, may be the placeholder `Unknown' 1063 */ 1064 static void 1065 add_revauthor (struct RtfContext *rc, 1066 const char *name) 1067 { 1068 char *dup; 1069 char *val; 1070 1071 if (NULL == (dup = strdup (name))) 1072 return; 1073 val = trim (dup); 1074 /* Strip the terminating semicolon of the `{Author;}' subgroup. */ 1075 { 1076 size_t len = strlen (val); 1077 1078 if ( (0 < len) && 1079 (';' == val[len - 1]) ) 1080 val[len - 1] = '\0'; 1081 val = trim (val); 1082 } 1083 if (MAX_REVAUTHORS > rc->revauth_len) 1084 { 1085 char *keep; 1086 1087 if (NULL != (keep = strdup (val))) 1088 rc->revauth[rc->revauth_len++] = keep; 1089 } 1090 /* Entry 0 of the table is the placeholder for `no author'. */ 1091 if (0 != strcmp (val, "Unknown")) 1092 add_meta (rc, 1093 EXTRACTOR_METATYPE_CONTRIBUTOR_NAME, 1094 val); 1095 free (dup); 1096 } 1097 1098 1099 /** 1100 * Remember a revision mark from the body, to be resolved against the 1101 * revision table once the whole file has been read. 1102 * 1103 * @param rc our extraction state 1104 * @param idx index into the revision table 1105 * @param dttm packed date and time of the edit 1106 */ 1107 static void 1108 add_revision_mark (struct RtfContext *rc, 1109 unsigned int idx, 1110 uint32_t dttm) 1111 { 1112 unsigned int i; 1113 1114 for (i = 0; i < rc->rev_len; i++) 1115 if ( (rc->rev[i].idx == idx) && 1116 (rc->rev[i].dttm == dttm) ) 1117 return; 1118 if (MAX_REVISIONS == rc->rev_len) 1119 return; 1120 rc->rev[rc->rev_len].idx = idx; 1121 rc->rev[rc->rev_len].dttm = dttm; 1122 rc->rev_len++; 1123 } 1124 1125 1126 /** 1127 * Report that the author of the comment we are in the middle of wrote 1128 * it at the packed time @a dttm. 1129 * 1130 * @param rc our extraction state 1131 * @param dttm packed date and time 1132 */ 1133 static void 1134 report_annotation_date (struct RtfContext *rc, 1135 uint32_t dttm) 1136 { 1137 char buf[32]; 1138 1139 if ('\0' == rc->atn_author[0]) 1140 return; 1141 if (0 == format_dttm (dttm, buf, sizeof (buf))) 1142 return; 1143 add_revision (rc, 1144 _ ("Author `%s' commented on the document on `%s'"), 1145 rc->atn_author, 1146 buf); 1147 } 1148 1149 1150 /** 1151 * Report the value of the custom document property @a rc->propname. 1152 * 1153 * @param rc our extraction state 1154 * @param value value of the property 1155 */ 1156 static void 1157 report_property (struct RtfContext *rc, 1158 const char *value) 1159 { 1160 unsigned int i; 1161 1162 if ('\0' == rc->propname[0]) 1163 return; 1164 for (i = 0; NULL != custom_map[i].name; i++) 1165 if (0 == strcmp (custom_map[i].name, rc->propname)) 1166 { 1167 add_meta (rc, custom_map[i].type, value); 1168 rc->propname[0] = '\0'; 1169 return; 1170 } 1171 /* Properties we have no type for are still worth reporting: this is 1172 where users park whatever their organisation asked them to. */ 1173 if ('_' != rc->propname[0]) 1174 { 1175 char line[MAX_VALUE + 1]; 1176 1177 if ( (0 < snprintf (line, 1178 sizeof (line), 1179 "%s: %s", 1180 rc->propname, 1181 value)) ) 1182 add_meta (rc, EXTRACTOR_METATYPE_UNKNOWN, line); 1183 } 1184 rc->propname[0] = '\0'; 1185 } 1186 1187 1188 /* ******************** group handling ******************** */ 1189 1190 1191 /** 1192 * Handle the end of the destination @a d. 1193 * 1194 * @param rc our extraction state 1195 * @param d destination that is being left 1196 */ 1197 static void 1198 close_destination (struct RtfContext *rc, 1199 enum Destination d) 1200 { 1201 unsigned int i; 1202 1203 if (is_text_dest (d)) 1204 { 1205 const char *text = get_text (rc); 1206 1207 switch (d) 1208 { 1209 case DEST_PROPNAME: 1210 { 1211 char *dup = strdup (text); 1212 1213 rc->propname[0] = '\0'; 1214 if (NULL != dup) 1215 { 1216 char *val = trim (dup); 1217 1218 if (sizeof (rc->propname) > strlen (val)) 1219 memcpy (rc->propname, val, strlen (val) + 1); 1220 free (dup); 1221 } 1222 break; 1223 } 1224 case DEST_STATICVAL: 1225 report_property (rc, text); 1226 break; 1227 case DEST_ATNDATE: 1228 { 1229 /* Some producers write the time stamp of a comment as the text 1230 of a `{\*\atndate ...}' destination rather than as the 1231 parameter of the control word. */ 1232 char *endp; 1233 long long v = strtoll (text, &endp, 10); 1234 1235 if ( (endp != text) && 1236 (0 <= v) && 1237 (0xFFFFFFFFLL >= v) ) 1238 report_annotation_date (rc, (uint32_t) v); 1239 break; 1240 } 1241 case DEST_ATNAUTHOR: 1242 case DEST_ATNID: 1243 { 1244 char *dup = strdup (text); 1245 1246 if (NULL != dup) 1247 { 1248 char *val = trim (dup); 1249 1250 if ( ('\0' != val[0]) && 1251 (sizeof (rc->atn_author) > strlen (val)) ) 1252 memcpy (rc->atn_author, val, strlen (val) + 1); 1253 free (dup); 1254 } 1255 add_meta (rc, EXTRACTOR_METATYPE_CONTRIBUTOR_NAME, text); 1256 break; 1257 } 1258 case DEST_GENERATOR: 1259 { 1260 /* Word terminates the generator string with a semicolon, the 1261 way it does the entries of the font and colour tables. */ 1262 char *dup = strdup (text); 1263 1264 if (NULL != dup) 1265 { 1266 size_t len = strlen (dup); 1267 1268 if ( (0 < len) && 1269 (';' == dup[len - 1]) ) 1270 dup[len - 1] = '\0'; 1271 add_meta (rc, EXTRACTOR_METATYPE_CREATED_BY_SOFTWARE, dup); 1272 free (dup); 1273 } 1274 break; 1275 } 1276 default: 1277 for (i = 0; DEST_NONE != text_map[i].dest; i++) 1278 if (text_map[i].dest == d) 1279 { 1280 add_meta (rc, text_map[i].type, text); 1281 break; 1282 } 1283 break; 1284 } 1285 reset_text (rc); 1286 return; 1287 } 1288 if (DEST_FIRST_DATE <= d) 1289 { 1290 char buf[32]; 1291 1292 if (0 != format_date (rc, buf, sizeof (buf))) 1293 for (i = 0; DEST_NONE != date_map[i].dest; i++) 1294 if (date_map[i].dest == d) 1295 { 1296 add_meta (rc, date_map[i].type, buf); 1297 break; 1298 } 1299 rc->have_date = 0; 1300 return; 1301 } 1302 } 1303 1304 1305 /** 1306 * Enter a new group. 1307 * 1308 * @param rc our extraction state 1309 */ 1310 static void 1311 push_group (struct RtfContext *rc) 1312 { 1313 rc->skip_units = 0; 1314 if (MAX_DEPTH - 1 > rc->depth) 1315 { 1316 rc->depth++; 1317 rc->stack[rc->depth] = rc->stack[rc->depth - 1]; 1318 rc->stack[rc->depth].owns = 0; 1319 /* Each subgroup of the revision table names one author. */ 1320 if (DEST_REVTBL == rc->stack[rc->depth].dest) 1321 reset_text (rc); 1322 } 1323 else 1324 { 1325 rc->extra_depth++; 1326 } 1327 } 1328 1329 1330 /** 1331 * Leave the innermost group. 1332 * 1333 * @param rc our extraction state 1334 */ 1335 static void 1336 pop_group (struct RtfContext *rc) 1337 { 1338 struct Group *g; 1339 1340 rc->skip_units = 0; 1341 if (0 < rc->extra_depth) 1342 { 1343 rc->extra_depth--; 1344 return; 1345 } 1346 if (0 == rc->depth) 1347 return; 1348 g = &rc->stack[rc->depth]; 1349 if (0 == g->skip) 1350 { 1351 if (0 != g->owns) 1352 close_destination (rc, g->dest); 1353 else if (DEST_REVTBL == g->dest) 1354 add_revauthor (rc, get_text (rc)); 1355 } 1356 rc->depth--; 1357 if (DEST_REVTBL == rc->stack[rc->depth].dest) 1358 reset_text (rc); 1359 } 1360 1361 1362 /* ******************** control words ******************** */ 1363 1364 1365 /** 1366 * Note that the document is written in the code page @a cpg. 1367 * 1368 * @param cpg code page number from `\ansicpg' 1369 * @return name of the character set, NULL if unknown 1370 */ 1371 static const char * 1372 codepage_to_charset (int64_t cpg) 1373 { 1374 switch (cpg) 1375 { 1376 case 437: 1377 return "CP437"; 1378 case 708: 1379 return "ISO-8859-6"; 1380 case 850: 1381 return "CP850"; 1382 case 852: 1383 return "CP852"; 1384 case 862: 1385 return "CP862"; 1386 case 864: 1387 return "CP864"; 1388 case 866: 1389 return "CP866"; 1390 case 874: 1391 return "CP874"; 1392 case 932: 1393 return "CP932"; 1394 case 936: 1395 return "CP936"; 1396 case 949: 1397 return "CP949"; 1398 case 950: 1399 return "CP950"; 1400 case 1250: 1401 return "CP1250"; 1402 case 1251: 1403 return "CP1251"; 1404 case 1252: 1405 return "CP1252"; 1406 case 1253: 1407 return "CP1253"; 1408 case 1254: 1409 return "CP1254"; 1410 case 1255: 1411 return "CP1255"; 1412 case 1256: 1413 return "CP1256"; 1414 case 1257: 1415 return "CP1257"; 1416 case 1258: 1417 return "CP1258"; 1418 case 10000: 1419 return "MACINTOSH"; 1420 case 65001: 1421 return "UTF-8"; 1422 default: 1423 return NULL; 1424 } 1425 } 1426 1427 1428 /** 1429 * Handle a control word that carries a component of the date currently 1430 * being assembled. 1431 * 1432 * @param rc our extraction state 1433 * @param word the control word, without the backslash 1434 * @param param its numeric parameter 1435 * @return true if @a word was a date component 1436 */ 1437 static int 1438 handle_date_word (struct RtfContext *rc, 1439 const char *word, 1440 int64_t param) 1441 { 1442 if (0 == strcmp (word, "yr")) 1443 rc->d_yr = (int) param; 1444 else if (0 == strcmp (word, "mo")) 1445 rc->d_mo = (int) param; 1446 else if (0 == strcmp (word, "dy")) 1447 rc->d_dy = (int) param; 1448 else if (0 == strcmp (word, "hr")) 1449 rc->d_hr = (int) param; 1450 else if (0 == strcmp (word, "min")) 1451 rc->d_mi = (int) param; 1452 else if (0 == strcmp (word, "sec")) 1453 rc->d_se = (int) param; 1454 else 1455 return 0; 1456 rc->have_date = 1; 1457 return 1; 1458 } 1459 1460 1461 /** 1462 * Handle a control word that reports a number from the `\info' group. 1463 * 1464 * @param rc our extraction state 1465 * @param word the control word, without the backslash 1466 * @param param its numeric parameter 1467 * @return true if @a word was such a control word 1468 */ 1469 static int 1470 handle_info_number (struct RtfContext *rc, 1471 const char *word, 1472 int64_t param) 1473 { 1474 static const struct 1475 { 1476 const char *word; 1477 enum EXTRACTOR_MetaType type; 1478 } map[] = { 1479 { "version", EXTRACTOR_METATYPE_REVISION_NUMBER }, 1480 { "edmins", EXTRACTOR_METATYPE_TOTAL_EDITING_TIME }, 1481 { "nofpages", EXTRACTOR_METATYPE_PAGE_COUNT }, 1482 { "nofwords", EXTRACTOR_METATYPE_WORD_COUNT }, 1483 { "nofchars", EXTRACTOR_METATYPE_CHARACTER_COUNT }, 1484 { NULL, EXTRACTOR_METATYPE_UNKNOWN } 1485 }; 1486 unsigned int i; 1487 1488 for (i = 0; NULL != map[i].word; i++) 1489 if (0 == strcmp (map[i].word, word)) 1490 { 1491 char buf[32]; 1492 1493 if (0 > param) 1494 return 1; 1495 snprintf (buf, sizeof (buf), "%llu", (unsigned long long) param); 1496 add_meta (rc, map[i].type, buf); 1497 return 1; 1498 } 1499 return 0; 1500 } 1501 1502 1503 /** 1504 * Handle a control word that identifies the author of a revision, or 1505 * the date of one. 1506 * 1507 * @param rc our extraction state 1508 * @param word the control word, without the backslash 1509 * @param param its numeric parameter 1510 * @return true if @a word was a revision mark 1511 */ 1512 static int 1513 handle_revision_word (struct RtfContext *rc, 1514 const char *word, 1515 int64_t param) 1516 { 1517 static const char *author_words[] = { 1518 "revauth", 1519 "revauthdel", 1520 "crauth", 1521 "trauth", 1522 "pnrauth", 1523 NULL 1524 }; 1525 static const char *date_words[] = { 1526 "revdttm", 1527 "revdttmdel", 1528 "crdate", 1529 "trdate", 1530 "pnrdate", 1531 NULL 1532 }; 1533 unsigned int i; 1534 1535 for (i = 0; NULL != author_words[i]; i++) 1536 if (0 == strcmp (author_words[i], word)) 1537 { 1538 rc->pending_author = ( (0 <= param) && 1539 (MAX_REVAUTHORS > param) ) 1540 ? (int) param 1541 : -1; 1542 return 1; 1543 } 1544 for (i = 0; NULL != date_words[i]; i++) 1545 if (0 == strcmp (date_words[i], word)) 1546 { 1547 if (0 <= rc->pending_author) 1548 add_revision_mark (rc, 1549 (unsigned int) rc->pending_author, 1550 (uint32_t) param); 1551 rc->pending_author = -1; 1552 return 1; 1553 } 1554 return 0; 1555 } 1556 1557 1558 /** 1559 * Act on the control word @a word. 1560 * 1561 * @param rc our extraction state 1562 * @param word the control word, without the backslash 1563 * @param has_param true if @a word had a numeric parameter 1564 * @param param the numeric parameter, 0 if there was none 1565 */ 1566 static void 1567 handle_word (struct RtfContext *rc, 1568 const char *word, 1569 int has_param, 1570 int64_t param) 1571 { 1572 struct Group *g = &rc->stack[rc->depth]; 1573 int ignorable = rc->pending_ignorable; 1574 unsigned int i; 1575 1576 rc->pending_ignorable = 0; 1577 if ( (0 != has_param) && 1578 (0 == strcmp (word, "atndate")) ) 1579 { 1580 report_annotation_date (rc, (uint32_t) param); 1581 return; 1582 } 1583 for (i = 0; NULL != dest_words[i].word; i++) 1584 if (0 == strcmp (dest_words[i].word, word)) 1585 { 1586 g->dest = dest_words[i].dest; 1587 g->owns = 1; 1588 if (is_text_dest (g->dest)) 1589 reset_text (rc); 1590 if (DEST_FIRST_DATE <= g->dest) 1591 { 1592 rc->have_date = 0; 1593 rc->d_yr = 0; 1594 rc->d_mo = 0; 1595 rc->d_dy = 0; 1596 rc->d_hr = 0; 1597 rc->d_mi = 0; 1598 rc->d_se = 0; 1599 } 1600 return; 1601 } 1602 if (0 != ignorable) 1603 { 1604 /* An ignorable destination we do not know: the specification says 1605 to discard it, which is what keeps us out of `\*\datastore', 1606 `\*\latentstyles', `\*\shppict' and the like. */ 1607 g->dest = DEST_SKIP; 1608 g->owns = 0; 1609 g->skip = 1; 1610 return; 1611 } 1612 for (i = 0; NULL != skip_words[i]; i++) 1613 if (0 == strcmp (skip_words[i], word)) 1614 { 1615 g->dest = DEST_SKIP; 1616 g->owns = 0; 1617 g->skip = 1; 1618 return; 1619 } 1620 if (0 == strcmp (word, "uc")) 1621 { 1622 if ( (0 != has_param) && 1623 (0 <= param) && 1624 (MAX_VALUE > param) ) 1625 g->uc = (int) param; 1626 return; 1627 } 1628 if (0 == strcmp (word, "ansicpg")) 1629 { 1630 const char *cs = codepage_to_charset (param); 1631 1632 if (NULL != cs) 1633 { 1634 rc->charset = cs; 1635 rc->charset_declared = 1; 1636 } 1637 return; 1638 } 1639 if (0 == strcmp (word, "mac")) 1640 { 1641 rc->charset = "MACINTOSH"; 1642 rc->charset_declared = 1; 1643 rc->authoring_os = "Macintosh"; 1644 return; 1645 } 1646 if (0 == strcmp (word, "pc")) 1647 { 1648 rc->charset = "CP437"; 1649 rc->charset_declared = 1; 1650 rc->authoring_os = "MS-DOS"; 1651 return; 1652 } 1653 if (0 == strcmp (word, "pca")) 1654 { 1655 rc->charset = "CP850"; 1656 rc->charset_declared = 1; 1657 rc->authoring_os = "MS-DOS"; 1658 return; 1659 } 1660 if (DEST_FIRST_DATE <= g->dest) 1661 { 1662 if (0 != handle_date_word (rc, word, param)) 1663 return; 1664 } 1665 if (DEST_INFO == g->dest) 1666 { 1667 if (0 != handle_info_number (rc, word, param)) 1668 return; 1669 } 1670 (void) handle_revision_word (rc, word, param); 1671 } 1672 1673 1674 /** 1675 * Act on a `\uN' Unicode escape. 1676 * 1677 * @param rc our extraction state 1678 * @param param the code point, as a signed 16 bit number 1679 */ 1680 static void 1681 handle_unicode (struct RtfContext *rc, 1682 int64_t param) 1683 { 1684 unsigned int cp; 1685 1686 if (0 > param) 1687 param += 0x10000; 1688 if ( (0 > param) || 1689 (0x10FFFF < param) ) 1690 { 1691 rc->surrogate = 0; 1692 return; 1693 } 1694 cp = (unsigned int) param; 1695 if ( (0xD800 <= cp) && 1696 (0xDBFF >= cp) ) 1697 { 1698 /* Word splits characters beyond the basic multilingual plane into 1699 a surrogate pair of two `\uN'; hold the first half. */ 1700 rc->surrogate = cp; 1701 return; 1702 } 1703 if ( (0xDC00 <= cp) && 1704 (0xDFFF >= cp) ) 1705 { 1706 if (0 != rc->surrogate) 1707 { 1708 unsigned int full = 0x10000 1709 + ((rc->surrogate - 0xD800) << 10) 1710 + (cp - 0xDC00); 1711 1712 rc->surrogate = 0; 1713 add_codepoint (rc, full); 1714 } 1715 /* An unpaired surrogate has no UTF-8 encoding; drop it. */ 1716 return; 1717 } 1718 rc->surrogate = 0; 1719 add_codepoint (rc, cp); 1720 } 1721 1722 1723 /** 1724 * Parse the control word or control symbol that follows a backslash. 1725 * 1726 * @param rc our extraction state 1727 * @param[out] word set to the control word, or to the single character 1728 * of a control symbol 1729 * @param[out] has_param set to true if a numeric parameter followed 1730 * @param[out] param set to the numeric parameter 1731 * @return 1 for a control word, 0 for a control symbol, -1 at the end 1732 * of the file 1733 */ 1734 static int 1735 parse_control (struct RtfContext *rc, 1736 char *word, 1737 int *has_param, 1738 int64_t *param) 1739 { 1740 int c = rtf_getc (rc); 1741 size_t n = 0; 1742 1743 word[0] = '\0'; 1744 *has_param = 0; 1745 *param = 0; 1746 if (0 > c) 1747 return -1; 1748 if (0 == isalpha ((unsigned char) c)) 1749 { 1750 word[0] = (char) c; 1751 word[1] = '\0'; 1752 return 0; 1753 } 1754 while ( (0 <= c) && 1755 (0 != isalpha ((unsigned char) c)) ) 1756 { 1757 if (MAX_CW > n) 1758 word[n++] = (char) c; 1759 c = rtf_getc (rc); 1760 } 1761 word[n] = '\0'; 1762 if ( (0 <= c) && 1763 ( ('-' == c) || 1764 (0 != isdigit ((unsigned char) c)) ) ) 1765 { 1766 int neg = ('-' == c); 1767 int64_t v = 0; 1768 unsigned int digits = 0; 1769 1770 if (0 != neg) 1771 c = rtf_getc (rc); 1772 while ( (0 <= c) && 1773 (0 != isdigit ((unsigned char) c)) ) 1774 { 1775 if (10 > digits) 1776 { 1777 v = v * 10 + (c - '0'); 1778 digits++; 1779 } 1780 c = rtf_getc (rc); 1781 } 1782 if (0 < digits) 1783 { 1784 *has_param = 1; 1785 *param = (0 != neg) ? -v : v; 1786 } 1787 } 1788 /* A single space after a control word is its delimiter and is not 1789 part of the document text. */ 1790 if (' ' != c) 1791 rtf_ungetc (rc, c); 1792 return 1; 1793 } 1794 1795 1796 /** 1797 * Read and discard the next @a n bytes; used for the binary data that 1798 * follows `\binN'. 1799 * 1800 * @param rc our extraction state 1801 * @param n number of bytes to discard 1802 */ 1803 static void 1804 skip_bytes (struct RtfContext *rc, 1805 int64_t n) 1806 { 1807 while (0 < n) 1808 { 1809 if (0 > rtf_getc (rc)) 1810 return; 1811 n--; 1812 } 1813 } 1814 1815 1816 /** 1817 * Act on a control symbol, that is a backslash followed by a character 1818 * that is not a letter. 1819 * 1820 * @param rc our extraction state 1821 * @param sym the character 1822 * @param collect true if we are inside a destination whose text we 1823 * accumulate 1824 */ 1825 static void 1826 handle_symbol (struct RtfContext *rc, 1827 char sym, 1828 int collect) 1829 { 1830 switch (sym) 1831 { 1832 case '*': 1833 rc->pending_ignorable = 1; 1834 break; 1835 case '\\': 1836 case '{': 1837 case '}': 1838 if (0 != collect) 1839 add_raw (rc, (unsigned char) sym); 1840 break; 1841 case '\'': 1842 { 1843 int hi = rtf_getc (rc); 1844 int lo; 1845 1846 if ( (0 > hi) || 1847 (0 == isxdigit ((unsigned char) hi)) ) 1848 { 1849 rtf_ungetc (rc, hi); 1850 break; 1851 } 1852 lo = rtf_getc (rc); 1853 if ( (0 > lo) || 1854 (0 == isxdigit ((unsigned char) lo)) ) 1855 { 1856 rtf_ungetc (rc, lo); 1857 break; 1858 } 1859 if (0 != collect) 1860 { 1861 int v = 0; 1862 1863 v = (0 != isdigit ((unsigned char) hi)) 1864 ? (hi - '0') 1865 : (tolower ((unsigned char) hi) - 'a' + 10); 1866 v <<= 4; 1867 v |= (0 != isdigit ((unsigned char) lo)) 1868 ? (lo - '0') 1869 : (tolower ((unsigned char) lo) - 'a' + 10); 1870 add_raw (rc, (unsigned char) v); 1871 } 1872 break; 1873 } 1874 case '~': 1875 if (0 != collect) 1876 add_codepoint (rc, 0x00A0); 1877 break; 1878 case '_': 1879 if (0 != collect) 1880 add_raw (rc, (unsigned char) '-'); 1881 break; 1882 case '\r': 1883 case '\n': 1884 if (0 != collect) 1885 add_raw (rc, (unsigned char) ' '); 1886 break; 1887 default: 1888 /* `\-' (optional hyphen), `\:' (subentry) and anything else we do 1889 not know produce no text. */ 1890 break; 1891 } 1892 } 1893 1894 1895 /* ******************** main loop ******************** */ 1896 1897 1898 /** 1899 * Report everything we learned from the revision marks, now that the 1900 * revision table is complete. 1901 * 1902 * @param rc our extraction state 1903 */ 1904 static void 1905 report_revisions (struct RtfContext *rc) 1906 { 1907 unsigned int i; 1908 1909 for (i = 0; i < rc->rev_len; i++) 1910 { 1911 char buf[32]; 1912 const char *name; 1913 1914 if (rc->rev[i].idx >= rc->revauth_len) 1915 continue; 1916 name = rc->revauth[rc->rev[i].idx]; 1917 if ( (NULL == name) || 1918 ('\0' == name[0]) || 1919 (0 == strcmp (name, "Unknown")) ) 1920 continue; 1921 if (0 == format_dttm (rc->rev[i].dttm, buf, sizeof (buf))) 1922 continue; 1923 add_revision (rc, 1924 _ ("Author `%s' edited the document on `%s'"), 1925 name, 1926 buf); 1927 } 1928 } 1929 1930 1931 /** 1932 * Release everything @a rc holds on to. 1933 * 1934 * @param rc our extraction state 1935 */ 1936 static void 1937 cleanup (struct RtfContext *rc) 1938 { 1939 unsigned int i; 1940 1941 for (i = 0; i < rc->seen_len; i++) 1942 free (rc->seen[i]); 1943 for (i = 0; i < rc->revauth_len; i++) 1944 free (rc->revauth[i]); 1945 } 1946 1947 1948 /** 1949 * Main entry method for the `rtf' extraction plugin. 1950 * 1951 * @param ec extraction context provided to the plugin 1952 */ 1953 void 1954 EXTRACTOR_rtf_extract_method (struct EXTRACTOR_ExtractContext *ec); 1955 1956 void 1957 EXTRACTOR_rtf_extract_method (struct EXTRACTOR_ExtractContext *ec) 1958 { 1959 struct RtfContext *rc; 1960 void *data; 1961 1962 if (5 > ec->read (ec->cls, &data, 5)) 1963 return; 1964 if (0 != memcmp (data, "{\\rtf", 5)) 1965 return; 1966 if (0 != ec->seek (ec->cls, 0, SEEK_SET)) 1967 return; 1968 if (NULL == (rc = malloc (sizeof (struct RtfContext)))) 1969 return; 1970 memset (rc, 0, sizeof (struct RtfContext)); 1971 rc->ec = ec; 1972 rc->pushback = -1; 1973 rc->charset = "CP1252"; 1974 rc->pending_author = -1; 1975 rc->stack[0].uc = 1; 1976 if (0 != ec->proc (ec->cls, 1977 PLUGIN_NAME, 1978 EXTRACTOR_METATYPE_MIMETYPE, 1979 EXTRACTOR_METAFORMAT_UTF8, 1980 "text/plain", 1981 "text/rtf", 1982 strlen ("text/rtf") + 1)) 1983 { 1984 free (rc); 1985 return; 1986 } 1987 while (0 == rc->stop) 1988 { 1989 int c = rtf_getc (rc); 1990 int collect; 1991 1992 if (0 > c) 1993 break; 1994 if ( ('{' == c) || 1995 ('}' == c) ) 1996 { 1997 /* A group boundary always ends the run of characters skipped 1998 because of a `\uN'. */ 1999 if ('{' == c) 2000 push_group (rc); 2001 else 2002 pop_group (rc); 2003 continue; 2004 } 2005 collect = ( (0 == rc->stack[rc->depth].skip) && 2006 ( (is_text_dest (rc->stack[rc->depth].dest)) || 2007 (DEST_REVTBL == rc->stack[rc->depth].dest) ) ); 2008 if ('\\' != c) 2009 { 2010 if ( ('\r' == c) || 2011 ('\n' == c) ) 2012 continue; /* not part of the document text */ 2013 if (0 < rc->skip_units) 2014 { 2015 rc->skip_units--; 2016 continue; 2017 } 2018 if (0 != collect) 2019 add_raw (rc, (unsigned char) c); 2020 continue; 2021 } 2022 { 2023 char word[MAX_CW + 1]; 2024 int has_param; 2025 int64_t param; 2026 int kind = parse_control (rc, word, &has_param, ¶m); 2027 2028 if (0 > kind) 2029 break; 2030 if (0 < rc->skip_units) 2031 { 2032 /* A control word or symbol counts as one of the characters 2033 that `\uN' asked us to skip; a `\'hh' takes its two hex 2034 digits with it. */ 2035 rc->skip_units--; 2036 if ( (0 == kind) && 2037 ('\'' == word[0]) ) 2038 { 2039 (void) rtf_getc (rc); 2040 (void) rtf_getc (rc); 2041 } 2042 continue; 2043 } 2044 if (0 == kind) 2045 { 2046 handle_symbol (rc, word[0], collect); 2047 continue; 2048 } 2049 if (0 == strcmp (word, "bin")) 2050 { 2051 if ( (0 != has_param) && 2052 (0 < param) ) 2053 skip_bytes (rc, param); 2054 continue; 2055 } 2056 if (0 == strcmp (word, "u")) 2057 { 2058 if (0 != has_param) 2059 { 2060 if (0 != collect) 2061 handle_unicode (rc, param); 2062 rc->skip_units = rc->stack[rc->depth].uc; 2063 } 2064 continue; 2065 } 2066 if (0 != rc->stack[rc->depth].skip) 2067 { 2068 /* Still track `\*' so that a nested unknown destination inside 2069 a skipped one does not confuse us. */ 2070 rc->pending_ignorable = 0; 2071 continue; 2072 } 2073 handle_word (rc, word, has_param, param); 2074 } 2075 } 2076 if (0 != rc->charset_declared) 2077 add_meta (rc, EXTRACTOR_METATYPE_CHARACTER_SET, rc->charset); 2078 if (NULL != rc->authoring_os) 2079 add_meta (rc, EXTRACTOR_METATYPE_AUTHORING_OS, rc->authoring_os); 2080 report_revisions (rc); 2081 cleanup (rc); 2082 free (rc); 2083 } 2084 2085 2086 /* end of rtf_extractor.c */