libextractor

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

ole2_extractor.c (35110B)


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