libextractor

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

kml_extractor.c (37518B)


      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/kml_extractor.c
     22  * @brief plugin to support KML (Keyhole Markup Language) documents
     23  * @author Christian Grothoff
     24  *
     25  * KML is what Google Earth, Google Maps exports, most GIS tools and a
     26  * fair number of tracking applications write.  Like GPX it carries
     27  * coordinates, but it also carries `<NetworkLink>' elements, which make
     28  * opening the document fetch content from a remote host -- worth
     29  * flagging on its own.
     30  *
     31  * KMZ, the zipped form, is a zip archive whose first member is
     32  * `doc.kml'.  This plugin does not look inside it: libextractor's zip
     33  * support lives in the core decompressor, not here, and a KMZ therefore
     34  * does not reach this plugin as KML.  Nothing below will mistake a zip
     35  * for KML, since the magic check requires markup at the start of the
     36  * file.
     37  *
     38  * As in `gpx_extractor.c', this is a bounded scanner, not an XML parser
     39  * -- libextractor has no XML dependency and this plugin must stay
     40  * unconditional.  What that costs, stated once:
     41  *
     42  * - Comments (`<!-- ... -->') and processing instructions are not
     43  *   skipped, so an element that only occurs inside a comment is still
     44  *   counted.  A false positive, never an out-of-bounds read.
     45  * - CDATA is recognised only when it opens the content of an element we
     46  *   ask for -- which is the case that matters, because `<description>'
     47  *   almost always wraps its HTML in CDATA.
     48  * - Only the five predefined entities and numeric character references
     49  *   are expanded.
     50  * - Element content is the run of text up to the next `<'; nested
     51  *   elements inside a value are not concatenated.
     52  * - Namespace prefixes are ignored, so `<atom:name>' and `<name>' are
     53  *   the same element here.  That is what lets `<atom:author><atom:name>'
     54  *   be found without a namespace table, and it is why `<Link>' (KML) and
     55  *   `<link>' (Atom) have to be told apart by case, which XML guarantees.
     56  * - An `<Update>' or a `<Schema>' can hold elements with the same names
     57  *   as the ones we look for; we do not track the containing element, so
     58  *   such a document would be reported approximately.
     59  *
     60  * Every loop below is bounded by the buffer length and advances at least
     61  * one byte per iteration.
     62  */
     63 #include "platform.h"
     64 #include "extractor.h"
     65 #include "forensics.h"
     66 
     67 #include <math.h>
     68 
     69 
     70 /**
     71  * How many bytes of the file we are willing to look at.  KML documents
     72  * exported from a GIS routinely run to many megabytes of coordinates.
     73  */
     74 #define KML_SCAN_CAP (1024 * 1024)
     75 
     76 /**
     77  * Longest start tag we will scan for its closing `>'.
     78  */
     79 #define KML_MAX_TAG 8192
     80 
     81 /**
     82  * Longest element text content we will look at, for everything except
     83  * `<coordinates>' (which gets #KML_MAX_COORD_TEXT).
     84  */
     85 #define KML_MAX_TEXT 8192
     86 
     87 /**
     88  * Longest `<coordinates>' body we will walk.
     89  */
     90 #define KML_MAX_COORD_TEXT (128 * 1024)
     91 
     92 /**
     93  * Most coordinate tuples we will parse out of one `<coordinates>'
     94  * element.
     95  */
     96 #define KML_MAX_TUPLES 100000
     97 
     98 /**
     99  * How far into the file the `<kml' element has to appear.
    100  */
    101 #define KML_MAGIC_WINDOW 2048
    102 
    103 /**
    104  * Most attributes we parse out of a single start tag.
    105  */
    106 #define KML_MAX_ATTRS 64
    107 
    108 
    109 /**
    110  * Everything the single pass over the document collected.
    111  */
    112 struct KmlScan
    113 {
    114   /**
    115    * Bounding box over every `<coordinates>' tuple seen.
    116    */
    117   double minlon;
    118   double minlat;
    119   double maxlon;
    120   double maxlat;
    121 
    122   /**
    123    * The first coordinate in the document.
    124    */
    125   double first_lat;
    126   double first_lon;
    127 
    128   /**
    129    * Number of `<Placemark>' elements.
    130    */
    131   uint64_t placemarks;
    132 
    133   /**
    134    * Number of `<NetworkLink>' elements.
    135    */
    136   uint64_t networklinks;
    137 
    138   /**
    139    * Offset of the first `<Placemark>', or `(size_t) -1'.
    140    */
    141   size_t first_placemark;
    142 
    143   /**
    144    * Offset of the first `<LookAt>' or `<Camera>', or `(size_t) -1'.
    145    */
    146   size_t viewpoint;
    147 
    148   /**
    149    * Non-zero once the bounding box fields are meaningful.
    150    */
    151   int have_bbox;
    152 };
    153 
    154 
    155 /**
    156  * Can @a c appear in an XML name (after the first character)?
    157  *
    158  * @param c character to test
    159  * @return 1 if @a c is a name character, 0 if not
    160  */
    161 static int
    162 kml_name_char (char c)
    163 {
    164   return ( ( ('a' <= c) && ('z' >= c) ) ||
    165            ( ('A' <= c) && ('Z' >= c) ) ||
    166            ( ('0' <= c) && ('9' >= c) ) ||
    167            ('_' == c) || ('-' == c) || ('.' == c) );
    168 }
    169 
    170 
    171 /**
    172  * Is the element that starts at @a pos named @a tag?  A namespace prefix
    173  * is skipped; the comparison is case-sensitive, as XML requires.
    174  *
    175  * @param buf the buffer
    176  * @param len number of bytes in @a buf
    177  * @param pos offset of the `<'
    178  * @param tag element name to compare against
    179  * @return 1 on a match, 0 otherwise
    180  */
    181 static int
    182 kml_tag_is (const char *buf,
    183             size_t len,
    184             size_t pos,
    185             const char *tag)
    186 {
    187   size_t i;
    188   size_t start;
    189   size_t tlen = strlen (tag);
    190 
    191   if ( (pos >= len) ||
    192        ('<' != buf[pos]) )
    193     return 0;
    194   i = pos + 1;
    195   start = i;
    196   while ( (i < len) &&
    197           (i - start < 64) &&
    198           kml_name_char (buf[i]) )
    199     i++;
    200   if ( (i < len) &&
    201        (':' == buf[i]) )
    202   {
    203     i++;
    204     start = i;
    205     while ( (i < len) &&
    206             (i - start < 64) &&
    207             kml_name_char (buf[i]) )
    208       i++;
    209   }
    210   if (i - start != tlen)
    211     return 0;
    212   return (0 == memcmp (&buf[start],
    213                        tag,
    214                        tlen));
    215 }
    216 
    217 
    218 /**
    219  * Find the `>' that closes the start tag beginning at @a pos.  Quoted
    220  * attribute values may contain `>', so quoting is tracked.
    221  *
    222  * @param buf the buffer
    223  * @param len number of bytes in @a buf
    224  * @param pos offset of the `<'
    225  * @return offset of the `>', or `(size_t) -1' if there is none within
    226  *         #KML_MAX_TAG bytes
    227  */
    228 static size_t
    229 kml_tag_end (const char *buf,
    230              size_t len,
    231              size_t pos)
    232 {
    233   char quote = 0;
    234 
    235   for (size_t i = pos; (i < len) && (i - pos < KML_MAX_TAG); i++)
    236   {
    237     char c = buf[i];
    238 
    239     if (0 != quote)
    240     {
    241       if (c == quote)
    242         quote = 0;
    243       continue;
    244     }
    245     if ( ('"' == c) ||
    246          ('\'' == c) )
    247     {
    248       quote = c;
    249       continue;
    250     }
    251     if ('>' == c)
    252       return i;
    253   }
    254   return (size_t) -1;
    255 }
    256 
    257 
    258 /**
    259  * Find the value of attribute @a name in the start tag at @a pos.
    260  *
    261  * @param buf the buffer
    262  * @param len number of bytes in @a buf
    263  * @param pos offset of the `<'
    264  * @param name attribute name, without a namespace prefix
    265  * @param[out] vstart offset of the first byte of the value
    266  * @param[out] vlen number of bytes in the value
    267  * @return 1 if the attribute was found, 0 if not
    268  */
    269 static int
    270 kml_attr (const char *buf,
    271           size_t len,
    272           size_t pos,
    273           const char *name,
    274           size_t *vstart,
    275           size_t *vlen)
    276 {
    277   size_t end = kml_tag_end (buf,
    278                             len,
    279                             pos);
    280   size_t i;
    281   size_t nlen = strlen (name);
    282 
    283   if (((size_t) -1) == end)
    284     return 0;
    285   i = pos + 1;
    286   while ( (i < end) &&
    287           (kml_name_char (buf[i]) ||
    288            (':' == buf[i]) ) )
    289     i++;
    290   for (unsigned int n = 0; n < KML_MAX_ATTRS; n++)
    291   {
    292     size_t astart;
    293     size_t alen;
    294     size_t vs;
    295     size_t ve;
    296     char quote;
    297 
    298     while ( (i < end) &&
    299             ( (' ' == buf[i]) || ('\t' == buf[i]) ||
    300               ('\r' == buf[i]) || ('\n' == buf[i]) ) )
    301       i++;
    302     if (i >= end)
    303       break;
    304     astart = i;
    305     while ( (i < end) &&
    306             (kml_name_char (buf[i]) ||
    307              (':' == buf[i]) ) )
    308       i++;
    309     if (i == astart)
    310     {
    311       i++;         /* not a name: skip a byte so we always progress */
    312       continue;
    313     }
    314     alen = i - astart;
    315     for (size_t k = 0; k < alen; k++)
    316       if (':' == buf[astart + k])
    317       {
    318         astart += k + 1;
    319         alen -= k + 1;
    320         break;
    321       }
    322     while ( (i < end) &&
    323             ( (' ' == buf[i]) || ('\t' == buf[i]) ||
    324               ('\r' == buf[i]) || ('\n' == buf[i]) ) )
    325       i++;
    326     if ( (i >= end) ||
    327          ('=' != buf[i]) )
    328       continue;    /* valueless attribute; i already advanced past a name */
    329     i++;
    330     while ( (i < end) &&
    331             ( (' ' == buf[i]) || ('\t' == buf[i]) ||
    332               ('\r' == buf[i]) || ('\n' == buf[i]) ) )
    333       i++;
    334     if (i >= end)
    335       break;
    336     quote = buf[i];
    337     if ( ('"' == quote) ||
    338          ('\'' == quote) )
    339     {
    340       i++;
    341       vs = i;
    342       while ( (i < end) &&
    343               (quote != buf[i]) )
    344         i++;
    345       ve = i;
    346       if (i < end)
    347         i++;
    348     }
    349     else
    350     {
    351       vs = i;
    352       while ( (i < end) &&
    353               (' ' != buf[i]) && ('\t' != buf[i]) &&
    354               ('\r' != buf[i]) && ('\n' != buf[i]) &&
    355               ('/' != buf[i]) )
    356         i++;
    357       ve = i;
    358     }
    359     if ( (alen == nlen) &&
    360          (0 == memcmp (&buf[astart],
    361                        name,
    362                        nlen)) )
    363     {
    364       *vstart = vs;
    365       *vlen = ve - vs;
    366       return 1;
    367     }
    368   }
    369   return 0;
    370 }
    371 
    372 
    373 /**
    374  * Text content of the element whose start tag begins at @a pos.
    375  *
    376  * @param buf the buffer
    377  * @param len number of bytes in @a buf
    378  * @param pos offset of the `<'
    379  * @param maxtext most content bytes to return
    380  * @param[out] tstart offset of the first content byte
    381  * @param[out] tlen number of content bytes
    382  * @return 1 if content was found, 0 if the element is empty or the tag
    383  *         is malformed
    384  */
    385 static int
    386 kml_text (const char *buf,
    387           size_t len,
    388           size_t pos,
    389           size_t maxtext,
    390           size_t *tstart,
    391           size_t *tlen)
    392 {
    393   size_t end = kml_tag_end (buf,
    394                             len,
    395                             pos);
    396   size_t i;
    397   size_t j;
    398 
    399   if (((size_t) -1) == end)
    400     return 0;
    401   if ( (end > pos) &&
    402        ('/' == buf[end - 1]) )
    403     return 0;   /* self-closing element, no content */
    404   i = end + 1;
    405   if ( (i + 9 <= len) &&
    406        (0 == memcmp (&buf[i],
    407                      "<![CDATA[",
    408                      9)) )
    409   {
    410     j = i + 9;
    411     while ( (j + 3 <= len) &&
    412             (j - i < maxtext) )
    413     {
    414       if (0 == memcmp (&buf[j],
    415                        "]]>",
    416                        3))
    417       {
    418         *tstart = i + 9;
    419         *tlen = j - (i + 9);
    420         return (0 != *tlen);
    421       }
    422       j++;
    423     }
    424     return 0;
    425   }
    426   j = i;
    427   while ( (j < len) &&
    428           ('<' != buf[j]) &&
    429           (j - i < maxtext) )
    430     j++;
    431   *tstart = i;
    432   *tlen = j - i;
    433   return (0 != *tlen);
    434 }
    435 
    436 
    437 /**
    438  * Find the next element named @a tag at or after @a from, staying below
    439  * @a limit.
    440  *
    441  * @param buf the buffer
    442  * @param limit offset one past the last byte to search
    443  * @param from where to start searching
    444  * @param tag element name
    445  * @return offset of the `<', or `(size_t) -1' if not found
    446  */
    447 static size_t
    448 kml_find (const char *buf,
    449           size_t limit,
    450           size_t from,
    451           const char *tag)
    452 {
    453   for (size_t i = from; i < limit; i++)
    454   {
    455     if ('<' != buf[i])
    456       continue;
    457     if (kml_tag_is (buf,
    458                     limit,
    459                     i,
    460                     tag))
    461       return i;
    462   }
    463   return (size_t) -1;
    464 }
    465 
    466 
    467 /**
    468  * Expand the five predefined XML entities and numeric character
    469  * references in @a in.  Anything else is copied through unchanged.
    470  *
    471  * @param in input text
    472  * @param inlen number of bytes in @a in
    473  * @param[out] out where to write the result
    474  * @param outsize number of bytes available in @a out
    475  * @return number of bytes written to @a out
    476  */
    477 static size_t
    478 kml_unescape (const char *in,
    479               size_t inlen,
    480               char *out,
    481               size_t outsize)
    482 {
    483   size_t o = 0;
    484   size_t i = 0;
    485 
    486   while ( (i < inlen) &&
    487           (o + 5 < outsize) )
    488   {
    489     size_t j;
    490     uint32_t cp = 0;
    491 
    492     if ('&' != in[i])
    493     {
    494       out[o++] = in[i++];
    495       continue;
    496     }
    497     j = i + 1;
    498     while ( (j < inlen) &&
    499             (j - i < 12) &&
    500             (';' != in[j]) )
    501       j++;
    502     if ( (j >= inlen) ||
    503          (';' != in[j]) )
    504     {
    505       out[o++] = in[i++];
    506       continue;
    507     }
    508     /* j is the offset of the `;', so the reference is j + 1 - i bytes */
    509     if ( (4 == j + 1 - i) &&
    510          (0 == memcmp (&in[i], "&lt;", 4)) )
    511       cp = '<';
    512     else if ( (4 == j + 1 - i) &&
    513               (0 == memcmp (&in[i], "&gt;", 4)) )
    514       cp = '>';
    515     else if ( (5 == j + 1 - i) &&
    516               (0 == memcmp (&in[i], "&amp;", 5)) )
    517       cp = '&';
    518     else if ( (6 == j + 1 - i) &&
    519               (0 == memcmp (&in[i], "&quot;", 6)) )
    520       cp = '"';
    521     else if ( (6 == j + 1 - i) &&
    522               (0 == memcmp (&in[i], "&apos;", 6)) )
    523       cp = '\'';
    524     else if ( (j - i > 2) &&
    525               ('#' == in[i + 1]) )
    526     {
    527       size_t k = i + 2;
    528       int base = 10;
    529 
    530       if ( (k < j) &&
    531            ( ('x' == in[k]) || ('X' == in[k]) ) )
    532       {
    533         base = 16;
    534         k++;
    535       }
    536       if (k == j)
    537       {
    538         out[o++] = in[i++];
    539         continue;
    540       }
    541       while (k < j)
    542       {
    543         int d;
    544 
    545         if ( ('0' <= in[k]) && ('9' >= in[k]) )
    546           d = in[k] - '0';
    547         else if ( (16 == base) && ('a' <= in[k]) && ('f' >= in[k]) )
    548           d = in[k] - 'a' + 10;
    549         else if ( (16 == base) && ('A' <= in[k]) && ('F' >= in[k]) )
    550           d = in[k] - 'A' + 10;
    551         else
    552           break;
    553         if (cp > 0x110000 / (uint32_t) base)
    554         {
    555           cp = 0;
    556           break;
    557         }
    558         cp = cp * (uint32_t) base + (uint32_t) d;
    559         k++;
    560       }
    561       if ( (k != j) ||
    562            (0 == cp) ||
    563            (cp > 0x10FFFF) ||
    564            ( (0xD800 <= cp) && (0xDFFF >= cp) ) )
    565       {
    566         out[o++] = in[i++];
    567         continue;
    568       }
    569     }
    570     else
    571     {
    572       out[o++] = in[i++];
    573       continue;   /* unknown entity: leave it alone */
    574     }
    575     if (cp < 0x80)
    576     {
    577       out[o++] = (char) cp;
    578     }
    579     else if (cp < 0x800)
    580     {
    581       out[o++] = (char) (0xC0 | (cp >> 6));
    582       out[o++] = (char) (0x80 | (cp & 0x3F));
    583     }
    584     else if (cp < 0x10000)
    585     {
    586       out[o++] = (char) (0xE0 | (cp >> 12));
    587       out[o++] = (char) (0x80 | ((cp >> 6) & 0x3F));
    588       out[o++] = (char) (0x80 | (cp & 0x3F));
    589     }
    590     else
    591     {
    592       out[o++] = (char) (0xF0 | (cp >> 18));
    593       out[o++] = (char) (0x80 | ((cp >> 12) & 0x3F));
    594       out[o++] = (char) (0x80 | ((cp >> 6) & 0x3F));
    595       out[o++] = (char) (0x80 | (cp & 0x3F));
    596     }
    597     i = j + 1;
    598   }
    599   return o;
    600 }
    601 
    602 
    603 /**
    604  * Drop HTML markup and collapse runs of white space.  KML
    605  * `<description>' values are HTML far more often than they are plain
    606  * text, and the balloon markup is noise for our purposes.
    607  *
    608  * Tags are removed by bracket matching, which is exactly as approximate
    609  * as it sounds: a `<' in the prose that is not markup eats up to the
    610  * next `>'.  That is the usual trade-off for markup that was never
    611  * required to be well-formed in the first place.
    612  *
    613  * @param in input text
    614  * @param inlen number of bytes in @a in
    615  * @param[out] out where to write the result
    616  * @param outsize number of bytes available in @a out
    617  * @return number of bytes written to @a out
    618  */
    619 static size_t
    620 kml_strip_html (const char *in,
    621                 size_t inlen,
    622                 char *out,
    623                 size_t outsize)
    624 {
    625   size_t o = 0;
    626   size_t i = 0;
    627   int space = 0;
    628 
    629   while ( (i < inlen) &&
    630           (o + 1 < outsize) )
    631   {
    632     if ('<' == in[i])
    633     {
    634       while ( (i < inlen) &&
    635               ('>' != in[i]) )
    636         i++;
    637       if (i < inlen)
    638         i++;
    639       space = 1;
    640       continue;
    641     }
    642     if ( (' ' == in[i]) || ('\t' == in[i]) ||
    643          ('\r' == in[i]) || ('\n' == in[i]) )
    644     {
    645       space = 1;
    646       i++;
    647       continue;
    648     }
    649     if (space &&
    650         (0 != o) &&
    651         (NULL == strchr (".,;:!?)]}",
    652                          in[i])) )
    653       out[o++] = ' ';
    654     space = 0;
    655     if (o + 1 < outsize)
    656       out[o++] = in[i];
    657     i++;
    658   }
    659   return o;
    660 }
    661 
    662 
    663 /**
    664  * Emit a stretch of XML text after expanding entity references.
    665  *
    666  * @param ec extraction context
    667  * @param type meta data type
    668  * @param data the text
    669  * @param len number of bytes in @a data
    670  * @return 1 if the caller should stop extracting, 0 to continue
    671  */
    672 static int
    673 kml_emit_xml (struct EXTRACTOR_ExtractContext *ec,
    674               enum EXTRACTOR_MetaType type,
    675               const char *data,
    676               size_t len)
    677 {
    678   char buf[EXTRACTOR_FORENSIC_MAX_STRING];
    679   size_t out;
    680 
    681   if (len > sizeof (buf) - 8)
    682     len = sizeof (buf) - 8;
    683   out = kml_unescape (data,
    684                       len,
    685                       buf,
    686                       sizeof (buf));
    687   return EXTRACTOR_forensic_emit_text_ (ec,
    688                                         "kml",
    689                                         type,
    690                                         buf,
    691                                         out);
    692 }
    693 
    694 
    695 /**
    696  * Parse a decimal number out of a stretch of bytes that is not
    697  * NUL-terminated.
    698  *
    699  * @param data the bytes
    700  * @param len number of bytes in @a data
    701  * @param[out] used number of bytes consumed, may be NULL
    702  * @param[out] value where to store the result
    703  * @return 1 on success, 0 if @a data does not start with a number
    704  */
    705 static int
    706 kml_parse_double (const char *data,
    707                   size_t len,
    708                   size_t *used,
    709                   double *value)
    710 {
    711   char tmp[64];
    712   char *endp;
    713   double v;
    714   size_t i = 0;
    715   size_t o = 0;
    716 
    717   while ( (i < len) &&
    718           ( (' ' == data[i]) || ('\t' == data[i]) ||
    719             ('\r' == data[i]) || ('\n' == data[i]) ) )
    720     i++;
    721   while ( (i < len) &&
    722           (o < sizeof (tmp) - 1) &&
    723           ( ( ('0' <= data[i]) && ('9' >= data[i]) ) ||
    724             ('+' == data[i]) || ('-' == data[i]) ||
    725             ('.' == data[i]) || ('e' == data[i]) || ('E' == data[i]) ) )
    726     tmp[o++] = data[i++];
    727   tmp[o] = '\0';
    728   if (0 == o)
    729     return 0;
    730   v = strtod (tmp,
    731               &endp);
    732   if ( (endp == tmp) ||
    733        (! isfinite (v)) )
    734     return 0;
    735   if (NULL != used)
    736     *used = i;
    737   *value = v;
    738   return 1;
    739 }
    740 
    741 
    742 /**
    743  * Days since 1970-01-01 for a proleptic Gregorian date.  (Howard
    744  * Hinnant's `days_from_civil'.)
    745  *
    746  * @param y year
    747  * @param m month, 1-12
    748  * @param d day of month, 1-31
    749  * @return day number, negative before the epoch
    750  */
    751 static int64_t
    752 kml_days_from_civil (int64_t y,
    753                      int64_t m,
    754                      int64_t d)
    755 {
    756   int64_t era;
    757   int64_t yoe;
    758   int64_t doy;
    759   int64_t doe;
    760 
    761   y -= (m <= 2);
    762   era = (y >= 0 ? y : y - 399) / 400;
    763   yoe = y - era * 400;
    764   doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
    765   doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
    766   return era * 146097 + doe - 719468;
    767 }
    768 
    769 
    770 /**
    771  * Parse an ISO 8601 timestamp as KML writes it:
    772  * `YYYY-MM-DDThh:mm:ss[.fff][Z|(+|-)hh[:mm]]'.  A missing zone is taken
    773  * as UTC.  (KML also allows a bare `YYYY', `YYYY-MM' or `YYYY-MM-DD';
    774  * those are rejected here rather than guessed at.)
    775  *
    776  * @param s the text
    777  * @param len number of bytes in @a s
    778  * @param[out] when where to store seconds since the Unix epoch
    779  * @return 1 on success, 0 if @a s is not a full timestamp
    780  */
    781 static int
    782 kml_parse_iso8601 (const char *s,
    783                    size_t len,
    784                    int64_t *when)
    785 {
    786   int64_t v[6] = { 0, 0, 0, 0, 0, 0 };
    787   static const size_t widths[6] = { 4, 2, 2, 2, 2, 2 };
    788   static const char seps[5] = { '-', '-', 'T', ':', ':' };
    789   size_t i = 0;
    790   int64_t off = 0;
    791 
    792   while ( (i < len) &&
    793           ( (' ' == s[i]) || ('\t' == s[i]) ||
    794             ('\r' == s[i]) || ('\n' == s[i]) ) )
    795     i++;
    796   for (unsigned int f = 0; f < 6; f++)
    797   {
    798     if (i + widths[f] > len)
    799       return 0;
    800     for (size_t k = 0; k < widths[f]; k++)
    801     {
    802       if ( ('0' > s[i + k]) ||
    803            ('9' < s[i + k]) )
    804         return 0;
    805       v[f] = v[f] * 10 + (s[i + k] - '0');
    806     }
    807     i += widths[f];
    808     if (f < 5)
    809     {
    810       if (i >= len)
    811         return 0;
    812       if ( (2 == f) &&
    813            (' ' == s[i]) )
    814         i++;
    815       else if (seps[f] == s[i])
    816         i++;
    817       else
    818         return 0;
    819     }
    820   }
    821   if ( (v[1] < 1) || (v[1] > 12) ||
    822        (v[2] < 1) || (v[2] > 31) ||
    823        (v[3] > 23) || (v[4] > 59) || (v[5] > 60) )
    824     return 0;
    825   if ( (i < len) &&
    826        ('.' == s[i]) )
    827   {
    828     i++;
    829     while ( (i < len) &&
    830             ('0' <= s[i]) && ('9' >= s[i]) )
    831       i++;
    832   }
    833   if ( (i < len) &&
    834        ( ('+' == s[i]) || ('-' == s[i]) ) )
    835   {
    836     int neg = ('-' == s[i]);
    837     int64_t oh = 0;
    838     int64_t om = 0;
    839 
    840     i++;
    841     if (i + 2 > len)
    842       return 0;
    843     for (size_t k = 0; k < 2; k++)
    844     {
    845       if ( ('0' > s[i + k]) || ('9' < s[i + k]) )
    846         return 0;
    847       oh = oh * 10 + (s[i + k] - '0');
    848     }
    849     i += 2;
    850     if ( (i < len) &&
    851          (':' == s[i]) )
    852       i++;
    853     if ( (i + 2 <= len) &&
    854          ('0' <= s[i]) && ('9' >= s[i]) &&
    855          ('0' <= s[i + 1]) && ('9' >= s[i + 1]) )
    856     {
    857       om = (s[i] - '0') * 10 + (s[i + 1] - '0');
    858       i += 2;
    859     }
    860     if ( (oh > 23) || (om > 59) )
    861       return 0;
    862     off = oh * 3600 + om * 60;
    863     if (neg)
    864       off = -off;
    865   }
    866   *when = kml_days_from_civil (v[0],
    867                                v[1],
    868                                v[2]) * 86400
    869           + v[3] * 3600 + v[4] * 60 + v[5]
    870           - off;
    871   return 1;
    872 }
    873 
    874 
    875 /**
    876  * Fold one coordinate into the bounding box.
    877  *
    878  * @param sc scan state
    879  * @param lon longitude in degrees
    880  * @param lat latitude in degrees
    881  */
    882 static void
    883 kml_note_coord (struct KmlScan *sc,
    884                 double lon,
    885                 double lat)
    886 {
    887   if ( (lat < -90.0) || (lat > 90.0) ||
    888        (lon < -180.0) || (lon > 180.0) )
    889     return;   /* out of range: not a coordinate */
    890   if (! sc->have_bbox)
    891   {
    892     sc->minlat = sc->maxlat = lat;
    893     sc->minlon = sc->maxlon = lon;
    894     sc->first_lat = lat;
    895     sc->first_lon = lon;
    896     sc->have_bbox = 1;
    897     return;
    898   }
    899   if (lat < sc->minlat)
    900     sc->minlat = lat;
    901   if (lat > sc->maxlat)
    902     sc->maxlat = lat;
    903   if (lon < sc->minlon)
    904     sc->minlon = lon;
    905   if (lon > sc->maxlon)
    906     sc->maxlon = lon;
    907 }
    908 
    909 
    910 /**
    911  * Walk the body of one `<coordinates>' element.
    912  *
    913  * KML tuples are `lon,lat[,alt]', white-space separated -- note that
    914  * this is the *reverse* of the GPX attribute order, which is the classic
    915  * way to get a KML bounding box wrong.
    916  *
    917  * @param buf the buffer
    918  * @param len number of bytes in @a buf
    919  * @param pos offset of the `<' of the `<coordinates>' start tag
    920  * @param sc scan state to update
    921  */
    922 static void
    923 kml_walk_coordinates (const char *buf,
    924                       size_t len,
    925                       size_t pos,
    926                       struct KmlScan *sc)
    927 {
    928   size_t ts;
    929   size_t tl;
    930   size_t i = 0;
    931 
    932   if (! kml_text (buf,
    933                   len,
    934                   pos,
    935                   KML_MAX_COORD_TEXT,
    936                   &ts,
    937                   &tl))
    938     return;
    939   for (unsigned long n = 0; (n < KML_MAX_TUPLES) && (i < tl); n++)
    940   {
    941     size_t used;
    942     double lon;
    943     double lat;
    944 
    945     while ( (i < tl) &&
    946             ( (' ' == buf[ts + i]) || ('\t' == buf[ts + i]) ||
    947               ('\r' == buf[ts + i]) || ('\n' == buf[ts + i]) ||
    948               (',' == buf[ts + i]) ) )
    949       i++;
    950     if (i >= tl)
    951       break;
    952     if (! kml_parse_double (&buf[ts + i],
    953                             tl - i,
    954                             &used,
    955                             &lon))
    956     {
    957       i++;      /* garbage: skip a byte so the loop always progresses */
    958       continue;
    959     }
    960     i += used;
    961     if ( (i >= tl) ||
    962          (',' != buf[ts + i]) )
    963       continue;
    964     i++;
    965     if (! kml_parse_double (&buf[ts + i],
    966                             tl - i,
    967                             &used,
    968                             &lat))
    969       continue;
    970     i += used;
    971     kml_note_coord (sc,
    972                     lon,
    973                     lat);
    974     /* an optional altitude follows; skip it if it is there */
    975     if ( (i < tl) &&
    976          (',' == buf[ts + i]) )
    977     {
    978       i++;
    979       if (kml_parse_double (&buf[ts + i],
    980                             tl - i,
    981                             &used,
    982                             &lon))
    983         i += used;
    984     }
    985   }
    986 }
    987 
    988 
    989 /**
    990  * Walk the document once, counting placemarks and network links and
    991  * folding every coordinate into the bounding box.
    992  *
    993  * @param buf the buffer
    994  * @param len number of bytes in @a buf
    995  * @param[out] sc scan state to fill in
    996  */
    997 static void
    998 kml_walk (const char *buf,
    999           size_t len,
   1000           struct KmlScan *sc)
   1001 {
   1002   for (size_t i = 0; i < len; i++)
   1003   {
   1004     if ('<' != buf[i])
   1005       continue;
   1006     if (kml_tag_is (buf, len, i, "Placemark"))
   1007     {
   1008       sc->placemarks++;
   1009       if (((size_t) -1) == sc->first_placemark)
   1010         sc->first_placemark = i;
   1011     }
   1012     else if (kml_tag_is (buf, len, i, "NetworkLink"))
   1013     {
   1014       sc->networklinks++;
   1015     }
   1016     else if (kml_tag_is (buf, len, i, "coordinates"))
   1017     {
   1018       kml_walk_coordinates (buf,
   1019                             len,
   1020                             i,
   1021                             sc);
   1022     }
   1023     else if ( (((size_t) -1) == sc->viewpoint) &&
   1024               (kml_tag_is (buf, len, i, "LookAt") ||
   1025                kml_tag_is (buf, len, i, "Camera") ) )
   1026     {
   1027       sc->viewpoint = i;
   1028     }
   1029   }
   1030 }
   1031 
   1032 
   1033 /**
   1034  * Report the `<href>' of every `<NetworkLink>' and the `<atom:link
   1035  * href=>' of the document, capped.
   1036  *
   1037  * @param ec extraction context
   1038  * @param buf the buffer
   1039  * @param len number of bytes in @a buf
   1040  * @return 1 if the caller should stop extracting, 0 to continue
   1041  */
   1042 static int
   1043 kml_do_links (struct EXTRACTOR_ExtractContext *ec,
   1044               const char *buf,
   1045               size_t len)
   1046 {
   1047   size_t p;
   1048   size_t vs;
   1049   size_t vl;
   1050   unsigned int n = 0;
   1051 
   1052   /* <atom:link href="..."/>: the document's own canonical location */
   1053   p = 0;
   1054   while (n < EXTRACTOR_FORENSIC_MAX_ITEMS)
   1055   {
   1056     p = kml_find (buf,
   1057                   len,
   1058                   p,
   1059                   "link");
   1060     if (((size_t) -1) == p)
   1061       break;
   1062     if (kml_attr (buf, len, p, "href", &vs, &vl))
   1063     {
   1064       n++;
   1065       if (kml_emit_xml (ec,
   1066                         EXTRACTOR_METATYPE_URL,
   1067                         &buf[vs],
   1068                         vl))
   1069         return 1;
   1070     }
   1071     p++;
   1072   }
   1073   /* <href> elements: KML's own <Link>, <Icon> and <NetworkLink> all use
   1074      one.  These are what make opening the document talk to a network. */
   1075   p = 0;
   1076   n = 0;
   1077   while (n < EXTRACTOR_FORENSIC_MAX_ITEMS)
   1078   {
   1079     p = kml_find (buf,
   1080                   len,
   1081                   p,
   1082                   "href");
   1083     if (((size_t) -1) == p)
   1084       break;
   1085     if (kml_text (buf, len, p, KML_MAX_TEXT, &vs, &vl))
   1086     {
   1087       n++;
   1088       if (kml_emit_xml (ec,
   1089                         EXTRACTOR_METATYPE_URL,
   1090                         &buf[vs],
   1091                         vl))
   1092         return 1;
   1093     }
   1094     p++;
   1095   }
   1096   return 0;
   1097 }
   1098 
   1099 
   1100 /**
   1101  * Main entry method for the KML extraction plugin.
   1102  *
   1103  * @param ec extraction context provided to the plugin
   1104  */
   1105 void
   1106 EXTRACTOR_kml_extract_method (struct EXTRACTOR_ExtractContext *ec);
   1107 
   1108 void
   1109 EXTRACTOR_kml_extract_method (struct EXTRACTOR_ExtractContext *ec)
   1110 {
   1111   char head[KML_MAGIC_WINDOW];
   1112   char *buf = NULL;
   1113   size_t hlen = 0;
   1114   size_t len = 0;
   1115   size_t cap;
   1116   size_t limit;
   1117   size_t p;
   1118   size_t vs;
   1119   size_t vl;
   1120   uint64_t fsize;
   1121   struct KmlScan sc;
   1122   int truncated;
   1123 
   1124   /* Bail out on the first few bytes: almost nothing we are handed is
   1125      KML, and we must not pay for the ones that are not. */
   1126   {
   1127     void *data;
   1128     ssize_t ret;
   1129 
   1130     if (0 != ec->seek (ec->cls,
   1131                        0,
   1132                        SEEK_SET))
   1133       return;
   1134     while (hlen < sizeof (head))
   1135     {
   1136       ret = ec->read (ec->cls,
   1137                       &data,
   1138                       sizeof (head) - hlen);
   1139       if (0 >= ret)
   1140         break;
   1141       if (((size_t) ret) > sizeof (head) - hlen)
   1142         return;   /* the IPC layer is misbehaving */
   1143       memcpy (&head[hlen],
   1144               data,
   1145               (size_t) ret);
   1146       hlen += (size_t) ret;
   1147     }
   1148   }
   1149   if (hlen < 16)
   1150     return;
   1151   if (((size_t) -1) == kml_find (head,
   1152                                  hlen,
   1153                                  0,
   1154                                  "kml"))
   1155     return;   /* not KML */
   1156   {
   1157     size_t k = 0;
   1158 
   1159     if ( (hlen >= 3) &&
   1160          (0 == memcmp (head, "\xef\xbb\xbf", 3)) )
   1161       k = 3;   /* UTF-8 BOM */
   1162     while ( (k < hlen) &&
   1163             ( (' ' == head[k]) || ('\t' == head[k]) ||
   1164               ('\r' == head[k]) || ('\n' == head[k]) ) )
   1165       k++;
   1166     if ( (k >= hlen) ||
   1167          ('<' != head[k]) )
   1168       return;   /* not markup; in particular, not a KMZ (which is a zip) */
   1169   }
   1170 
   1171   fsize = ec->get_size (ec->cls);
   1172   cap = KML_SCAN_CAP;
   1173   if ( (UINT64_MAX != fsize) &&
   1174        (fsize < (uint64_t) cap) )
   1175     cap = (size_t) fsize;
   1176   if (cap < hlen)
   1177     cap = hlen;
   1178   buf = malloc (cap);
   1179   if (NULL == buf)
   1180     return;
   1181   if (0 != ec->seek (ec->cls,
   1182                      0,
   1183                      SEEK_SET))
   1184   {
   1185     free (buf);
   1186     return;
   1187   }
   1188   while (len < cap)
   1189   {
   1190     void *data;
   1191     ssize_t ret;
   1192 
   1193     ret = ec->read (ec->cls,
   1194                     &data,
   1195                     cap - len);
   1196     if (0 >= ret)
   1197       break;
   1198     if (((size_t) ret) > cap - len)
   1199       break;   /* the IPC layer is misbehaving */
   1200     memcpy (&buf[len],
   1201             data,
   1202             (size_t) ret);
   1203     len += (size_t) ret;
   1204   }
   1205   if (len < 16)
   1206   {
   1207     free (buf);
   1208     return;
   1209   }
   1210   truncated = ( (UINT64_MAX == fsize) ||
   1211                 (fsize > (uint64_t) len) );
   1212 
   1213   if (0 != ec->proc (ec->cls,
   1214                      "kml",
   1215                      EXTRACTOR_METATYPE_MIMETYPE,
   1216                      EXTRACTOR_METAFORMAT_UTF8,
   1217                      "text/plain",
   1218                      "application/vnd.google-earth.kml+xml",
   1219                      strlen ("application/vnd.google-earth.kml+xml") + 1))
   1220     goto out;
   1221   /* KML is defined on WGS 84 only; the specification does not offer an
   1222      alternative, so this says something about the format. */
   1223   if (EXTRACTOR_forensic_emit_ (ec,
   1224                                 "kml",
   1225                                 EXTRACTOR_METATYPE_COORDINATE_SYSTEM,
   1226                                 "WGS 84"))
   1227     goto out;
   1228 
   1229   memset (&sc,
   1230           0,
   1231           sizeof (sc));
   1232   sc.first_placemark = (size_t) -1;
   1233   sc.viewpoint = (size_t) -1;
   1234   kml_walk (buf,
   1235             len,
   1236             &sc);
   1237 
   1238   /* The document title is the <name> of the enclosing <Document> or
   1239      <Folder>, which in practice is the first <name> before the first
   1240      <Placemark>. */
   1241   limit = (((size_t) -1) == sc.first_placemark) ? len : sc.first_placemark;
   1242   p = kml_find (buf, limit, 0, "name");
   1243   if ( (((size_t) -1) != p) &&
   1244        kml_text (buf, limit, p, KML_MAX_TEXT, &vs, &vl) &&
   1245        kml_emit_xml (ec, EXTRACTOR_METATYPE_TITLE, &buf[vs], vl) )
   1246     goto out;
   1247   p = kml_find (buf, limit, 0, "description");
   1248   if ( (((size_t) -1) != p) &&
   1249        kml_text (buf, limit, p, KML_MAX_TEXT, &vs, &vl) )
   1250   {
   1251     char stripped[EXTRACTOR_FORENSIC_MAX_STRING];
   1252     size_t slen;
   1253 
   1254     /* <description> is HTML far more often than not; the markup is
   1255        noise, but the text inside it is exactly what we want. */
   1256     slen = kml_strip_html (&buf[vs],
   1257                            vl,
   1258                            stripped,
   1259                            sizeof (stripped));
   1260     if (kml_emit_xml (ec,
   1261                       EXTRACTOR_METATYPE_DESCRIPTION,
   1262                       stripped,
   1263                       slen))
   1264       goto out;
   1265   }
   1266   /* <atom:author><atom:name> */
   1267   p = kml_find (buf, len, 0, "author");
   1268   if (((size_t) -1) != p)
   1269   {
   1270     size_t a = kml_find (buf, len, p + 1, "name");
   1271 
   1272     if ( (((size_t) -1) != a) &&
   1273          kml_text (buf, len, a, KML_MAX_TEXT, &vs, &vl) &&
   1274          kml_emit_xml (ec, EXTRACTOR_METATYPE_AUTHOR_NAME, &buf[vs], vl) )
   1275       goto out;
   1276   }
   1277   if (kml_do_links (ec,
   1278                     buf,
   1279                     len))
   1280     goto out;
   1281   if (0 != sc.networklinks)
   1282   {
   1283     /* Worth saying out loud: a NetworkLink means the document fetches
   1284        content from somewhere else when it is opened. */
   1285     if (EXTRACTOR_forensic_emit_ (ec,
   1286                                   "kml",
   1287                                   EXTRACTOR_METATYPE_COMMENT,
   1288                                   "%llu NetworkLink element%s;"
   1289                                   " opening this document fetches remote"
   1290                                   " content",
   1291                                   (unsigned long long) sc.networklinks,
   1292                                   (1 == sc.networklinks) ? "" : "s"))
   1293       goto out;
   1294   }
   1295   /* <TimeStamp><when> or, failing that, <TimeSpan><begin> */
   1296   {
   1297     size_t t = kml_find (buf, len, 0, "TimeStamp");
   1298     size_t w = (size_t) -1;
   1299 
   1300     if (((size_t) -1) != t)
   1301       w = kml_find (buf, len, t + 1, "when");
   1302     if (((size_t) -1) == w)
   1303     {
   1304       t = kml_find (buf, len, 0, "TimeSpan");
   1305       if (((size_t) -1) != t)
   1306         w = kml_find (buf, len, t + 1, "begin");
   1307     }
   1308     if ( (((size_t) -1) != w) &&
   1309          kml_text (buf, len, w, KML_MAX_TEXT, &vs, &vl) )
   1310     {
   1311       int64_t when;
   1312 
   1313       if (kml_parse_iso8601 (&buf[vs], vl, &when))
   1314       {
   1315         if (EXTRACTOR_forensic_emit_unix_time_ (ec,
   1316                                                 "kml",
   1317                                                 EXTRACTOR_METATYPE_CREATION_DATE,
   1318                                                 when))
   1319           goto out;
   1320       }
   1321       else if (kml_emit_xml (ec,
   1322                              EXTRACTOR_METATYPE_UNKNOWN_DATE,
   1323                              &buf[vs],
   1324                              vl))
   1325       {
   1326         goto out;
   1327       }
   1328     }
   1329   }
   1330   if (((size_t) -1) != sc.first_placemark)
   1331   {
   1332     /* the first Placemark's <name>, bounded by its </Placemark> */
   1333     size_t pend = len;
   1334     size_t nm;
   1335 
   1336     for (size_t i = sc.first_placemark; i + 12 <= len; i++)
   1337       if (0 == memcmp (&buf[i],
   1338                        "</Placemark>",
   1339                        12))
   1340       {
   1341         pend = i;
   1342         break;
   1343       }
   1344     nm = kml_find (buf, pend, sc.first_placemark + 1, "name");
   1345     if ( (((size_t) -1) != nm) &&
   1346          kml_text (buf, pend, nm, KML_MAX_TEXT, &vs, &vl) &&
   1347          kml_emit_xml (ec, EXTRACTOR_METATYPE_LOCATION_NAME, &buf[vs], vl) )
   1348       goto out;
   1349   }
   1350   if (sc.have_bbox)
   1351   {
   1352     if (EXTRACTOR_forensic_emit_ (ec,
   1353                                   "kml",
   1354                                   EXTRACTOR_METATYPE_BOUNDING_BOX,
   1355                                   "%.6f,%.6f,%.6f,%.6f",
   1356                                   sc.minlon,
   1357                                   sc.minlat,
   1358                                   sc.maxlon,
   1359                                   sc.maxlat))
   1360       goto out;
   1361     if (EXTRACTOR_forensic_emit_ (ec,
   1362                                   "kml",
   1363                                   EXTRACTOR_METATYPE_GPS_LATITUDE,
   1364                                   "%.6f",
   1365                                   sc.first_lat))
   1366       goto out;
   1367     if (EXTRACTOR_forensic_emit_ (ec,
   1368                                   "kml",
   1369                                   EXTRACTOR_METATYPE_GPS_LONGITUDE,
   1370                                   "%.6f",
   1371                                   sc.first_lon))
   1372       goto out;
   1373   }
   1374   if (((size_t) -1) != sc.viewpoint)
   1375   {
   1376     /* A <LookAt> or <Camera> is where the *viewer* is put, not where the
   1377        data is; reporting it as a GPS position would conflate the two, so
   1378        it goes into a comment that says which it is. */
   1379     size_t lonp = kml_find (buf, len, sc.viewpoint + 1, "longitude");
   1380     size_t latp = kml_find (buf, len, sc.viewpoint + 1, "latitude");
   1381     double vlon;
   1382     double vlat;
   1383     size_t s2;
   1384     size_t l2;
   1385 
   1386     if ( (((size_t) -1) != lonp) &&
   1387          (((size_t) -1) != latp) &&
   1388          kml_text (buf, len, lonp, KML_MAX_TEXT, &vs, &vl) &&
   1389          kml_parse_double (&buf[vs], vl, NULL, &vlon) &&
   1390          kml_text (buf, len, latp, KML_MAX_TEXT, &s2, &l2) &&
   1391          kml_parse_double (&buf[s2], l2, NULL, &vlat) &&
   1392          (vlat >= -90.0) && (vlat <= 90.0) &&
   1393          (vlon >= -180.0) && (vlon <= 180.0) )
   1394     {
   1395       if (EXTRACTOR_forensic_emit_ (ec,
   1396                                     "kml",
   1397                                     EXTRACTOR_METATYPE_COMMENT,
   1398                                     "%s viewpoint at %.6f, %.6f"
   1399                                     " (latitude, longitude)",
   1400                                     kml_tag_is (buf, len, sc.viewpoint,
   1401                                                 "Camera")
   1402                                     ? "Camera" : "LookAt",
   1403                                     vlat,
   1404                                     vlon))
   1405         goto out;
   1406     }
   1407   }
   1408   if (EXTRACTOR_forensic_emit_ (ec,
   1409                                 "kml",
   1410                                 EXTRACTOR_METATYPE_ENTRY_COUNT,
   1411                                 "%llu",
   1412                                 (unsigned long long) sc.placemarks))
   1413     goto out;
   1414   if (truncated &&
   1415       EXTRACTOR_forensic_emit_ (ec,
   1416                                 "kml",
   1417                                 EXTRACTOR_METATYPE_COMMENT,
   1418                                 "scan truncated at 1 MiB; the placemark"
   1419                                 " count and bounding box above cover"
   1420                                 " only that prefix of the file") )
   1421     goto out;
   1422 out:
   1423   free (buf);
   1424 }
   1425 
   1426 
   1427 /* end of kml_extractor.c */