libextractor

GNU libextractor
Log | Files | Refs | Submodules | README | LICENSE

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, &param);
   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 */