libextractor

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

gpx_extractor.c (42554B)


      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/gpx_extractor.c
     22  * @brief plugin to support GPX (GPS Exchange Format) track logs
     23  * @author Christian Grothoff
     24  *
     25  * GPX is raw location history: where a device was, when, and -- through
     26  * the `creator' attribute -- which device or application recorded it.
     27  * That makes the header of a GPX file one of the more identifying things
     28  * a volume can contain, which is why this plugin exists.
     29  *
     30  * This is deliberately *not* an XML parser.  libextractor has no XML
     31  * dependency and this plugin must stay unconditional, so what follows is
     32  * a bounded, single-pass scanner over at most #GPX_SCAN_CAP bytes.  Its
     33  * limits, stated once here rather than repeated at every call site:
     34  *
     35  * - Comments (`<!-- ... -->') and processing instructions are not
     36  *   skipped, so an element name that only occurs inside a comment is
     37  *   still seen.  For the fields below that means a false positive, never
     38  *   an out-of-bounds read.
     39  * - CDATA is recognised only when it opens the content of an element
     40  *   whose text we ask for; a CDATA section in the middle of mixed
     41  *   content is treated as markup.
     42  * - Only the five predefined entities and numeric character references
     43  *   are expanded; a document-defined entity is passed through verbatim.
     44  * - Element content is the run of text up to the next `<'; nested
     45  *   elements inside a value are not concatenated.
     46  * - Namespace prefixes are skipped, so `<gpx:name>' and `<name>' are the
     47  *   same element here.  A document that binds a prefix to some *other*
     48  *   namespace would be misread; GPX files in the wild do not.
     49  * - Attribute values are read as raw text; the scanner does not care
     50  *   whether a document is well-formed.
     51  *
     52  * None of that can make the scanner read outside the buffer or fail to
     53  * make forward progress: every loop below is bounded by the buffer
     54  * length and advances at least one byte per iteration.
     55  */
     56 #include "platform.h"
     57 #include "extractor.h"
     58 #include "forensics.h"
     59 
     60 #include <math.h>
     61 
     62 
     63 /**
     64  * How many bytes of the file we are willing to look at.  GPX track logs
     65  * routinely run to tens of megabytes; a first pass over a volume must
     66  * not read all of that.  Counts derived from a truncated scan are
     67  * reported as lower bounds, with a comment saying so.
     68  */
     69 #define GPX_SCAN_CAP (1024 * 1024)
     70 
     71 /**
     72  * Longest start tag we will scan for its closing `>'.  Keeps a file made
     73  * of one unterminated tag from turning the per-element work into a scan
     74  * of the whole buffer.
     75  */
     76 #define GPX_MAX_TAG 8192
     77 
     78 /**
     79  * Longest element text content we will look at.
     80  */
     81 #define GPX_MAX_TEXT 4096
     82 
     83 /**
     84  * How far into the file the `<gpx' element has to appear.
     85  */
     86 #define GPX_MAGIC_WINDOW 2048
     87 
     88 /**
     89  * Most attributes we parse out of a single start tag.
     90  */
     91 #define GPX_MAX_ATTRS 64
     92 
     93 /**
     94  * Mean Earth radius in metres, as used for the haversine sum.
     95  */
     96 #define GPX_EARTH_RADIUS 6371000.0
     97 
     98 
     99 /**
    100  * Everything we picked up while walking the track points.
    101  */
    102 struct GpxScan
    103 {
    104   /**
    105    * Bounding box of the points seen, west/south/east/north.
    106    */
    107   double minlon;
    108   double minlat;
    109   double maxlon;
    110   double maxlat;
    111 
    112   /**
    113    * Coordinates of the first point, which is where the recording
    114    * started.
    115    */
    116   double first_lat;
    117   double first_lon;
    118 
    119   /**
    120    * Elevation range seen inside track points.
    121    */
    122   double minele;
    123   double maxele;
    124 
    125   /**
    126    * Sum of the great-circle distances between consecutive track points.
    127    */
    128   double distance;
    129 
    130   /**
    131    * Latitude/longitude of the previous track point, for @e distance.
    132    */
    133   double prev_lat;
    134   double prev_lon;
    135 
    136   /**
    137    * First and last timestamp seen inside a track point.
    138    */
    139   int64_t first_time;
    140   int64_t last_time;
    141 
    142   /**
    143    * Number of `<trkpt>', `<wpt>' and `<rtept>' elements.
    144    */
    145   uint64_t trkpts;
    146   uint64_t wpts;
    147   uint64_t rtepts;
    148 
    149   /**
    150    * Non-zero once at least one point of any kind was seen; before that
    151    * an `<ele>' or `<time>' belongs to the metadata, not to a point.
    152    */
    153   int seen_point;
    154 
    155   /**
    156    * Non-zero once #prev_lat / #prev_lon hold a track point.
    157    */
    158   int have_prev;
    159 
    160   /**
    161    * Non-zero once the bounding box fields are meaningful.
    162    */
    163   int have_bbox;
    164 
    165   /**
    166    * Non-zero once the elevation range fields are meaningful.
    167    */
    168   int have_ele;
    169 };
    170 
    171 
    172 /**
    173  * Can @a c appear in an XML name (after the first character)?
    174  *
    175  * @param c character to test
    176  * @return 1 if @a c is a name character, 0 if not
    177  */
    178 static int
    179 gpx_name_char (char c)
    180 {
    181   return ( ( ('a' <= c) && ('z' >= c) ) ||
    182            ( ('A' <= c) && ('Z' >= c) ) ||
    183            ( ('0' <= c) && ('9' >= c) ) ||
    184            ('_' == c) || ('-' == c) || ('.' == c) );
    185 }
    186 
    187 
    188 /**
    189  * Is the element that starts at @a pos named @a tag?
    190  *
    191  * A namespace prefix is skipped, so `<gpx:name' matches "name".
    192  *
    193  * @param buf the buffer
    194  * @param len number of bytes in @a buf
    195  * @param pos offset of the `<'
    196  * @param tag element name to compare against
    197  * @return 1 on a match, 0 otherwise
    198  */
    199 static int
    200 gpx_tag_is (const char *buf,
    201             size_t len,
    202             size_t pos,
    203             const char *tag)
    204 {
    205   size_t i;
    206   size_t start;
    207   size_t tlen = strlen (tag);
    208 
    209   if ( (pos >= len) ||
    210        ('<' != buf[pos]) )
    211     return 0;
    212   i = pos + 1;
    213   start = i;
    214   while ( (i < len) &&
    215           (i - start < 64) &&
    216           gpx_name_char (buf[i]) )
    217     i++;
    218   if ( (i < len) &&
    219        (':' == buf[i]) )
    220   {
    221     i++;
    222     start = i;
    223     while ( (i < len) &&
    224             (i - start < 64) &&
    225             gpx_name_char (buf[i]) )
    226       i++;
    227   }
    228   if (i - start != tlen)
    229     return 0;
    230   return (0 == memcmp (&buf[start],
    231                        tag,
    232                        tlen));
    233 }
    234 
    235 
    236 /**
    237  * Find the `>' that closes the start tag beginning at @a pos.  Quoted
    238  * attribute values may contain `>', so quoting is tracked.
    239  *
    240  * @param buf the buffer
    241  * @param len number of bytes in @a buf
    242  * @param pos offset of the `<'
    243  * @return offset of the `>', or `(size_t) -1' if there is none within
    244  *         #GPX_MAX_TAG bytes
    245  */
    246 static size_t
    247 gpx_tag_end (const char *buf,
    248              size_t len,
    249              size_t pos)
    250 {
    251   char quote = 0;
    252 
    253   for (size_t i = pos; (i < len) && (i - pos < GPX_MAX_TAG); i++)
    254   {
    255     char c = buf[i];
    256 
    257     if (0 != quote)
    258     {
    259       if (c == quote)
    260         quote = 0;
    261       continue;
    262     }
    263     if ( ('"' == c) ||
    264          ('\'' == c) )
    265     {
    266       quote = c;
    267       continue;
    268     }
    269     if ('>' == c)
    270       return i;
    271   }
    272   return (size_t) -1;
    273 }
    274 
    275 
    276 /**
    277  * Find the value of attribute @a name in the start tag at @a pos.
    278  *
    279  * @param buf the buffer
    280  * @param len number of bytes in @a buf
    281  * @param pos offset of the `<'
    282  * @param name attribute name, without a namespace prefix
    283  * @param[out] vstart offset of the first byte of the value
    284  * @param[out] vlen number of bytes in the value
    285  * @return 1 if the attribute was found, 0 if not
    286  */
    287 static int
    288 gpx_attr (const char *buf,
    289           size_t len,
    290           size_t pos,
    291           const char *name,
    292           size_t *vstart,
    293           size_t *vlen)
    294 {
    295   size_t end = gpx_tag_end (buf,
    296                             len,
    297                             pos);
    298   size_t i;
    299   size_t nlen = strlen (name);
    300 
    301   if (((size_t) -1) == end)
    302     return 0;
    303   i = pos + 1;
    304   while ( (i < end) &&
    305           (gpx_name_char (buf[i]) ||
    306            (':' == buf[i]) ) )
    307     i++;
    308   for (unsigned int n = 0; n < GPX_MAX_ATTRS; n++)
    309   {
    310     size_t astart;
    311     size_t alen;
    312     size_t vs;
    313     size_t ve;
    314     char quote;
    315 
    316     while ( (i < end) &&
    317             ( (' ' == buf[i]) || ('\t' == buf[i]) ||
    318               ('\r' == buf[i]) || ('\n' == buf[i]) ) )
    319       i++;
    320     if (i >= end)
    321       break;
    322     astart = i;
    323     while ( (i < end) &&
    324             (gpx_name_char (buf[i]) ||
    325              (':' == buf[i]) ) )
    326       i++;
    327     if (i == astart)
    328     {
    329       i++;         /* not a name: skip a byte so we always progress */
    330       continue;
    331     }
    332     alen = i - astart;
    333     /* a prefixed attribute matches on its local part */
    334     for (size_t k = 0; k < alen; k++)
    335       if (':' == buf[astart + k])
    336       {
    337         astart += k + 1;
    338         alen -= k + 1;
    339         break;
    340       }
    341     while ( (i < end) &&
    342             ( (' ' == buf[i]) || ('\t' == buf[i]) ||
    343               ('\r' == buf[i]) || ('\n' == buf[i]) ) )
    344       i++;
    345     if ( (i >= end) ||
    346          ('=' != buf[i]) )
    347       continue;    /* valueless attribute; i already advanced past a name */
    348     i++;
    349     while ( (i < end) &&
    350             ( (' ' == buf[i]) || ('\t' == buf[i]) ||
    351               ('\r' == buf[i]) || ('\n' == buf[i]) ) )
    352       i++;
    353     if (i >= end)
    354       break;
    355     quote = buf[i];
    356     if ( ('"' == quote) ||
    357          ('\'' == quote) )
    358     {
    359       i++;
    360       vs = i;
    361       while ( (i < end) &&
    362               (quote != buf[i]) )
    363         i++;
    364       ve = i;
    365       if (i < end)
    366         i++;
    367     }
    368     else
    369     {
    370       vs = i;
    371       while ( (i < end) &&
    372               (' ' != buf[i]) && ('\t' != buf[i]) &&
    373               ('\r' != buf[i]) && ('\n' != buf[i]) &&
    374               ('/' != buf[i]) )
    375         i++;
    376       ve = i;
    377     }
    378     if ( (alen == nlen) &&
    379          (0 == memcmp (&buf[astart],
    380                        name,
    381                        nlen)) )
    382     {
    383       *vstart = vs;
    384       *vlen = ve - vs;
    385       return 1;
    386     }
    387   }
    388   return 0;
    389 }
    390 
    391 
    392 /**
    393  * Text content of the element whose start tag begins at @a pos.
    394  *
    395  * @param buf the buffer
    396  * @param len number of bytes in @a buf
    397  * @param pos offset of the `<'
    398  * @param[out] tstart offset of the first content byte
    399  * @param[out] tlen number of content bytes
    400  * @return 1 if content was found, 0 if the element is empty or the tag
    401  *         is malformed
    402  */
    403 static int
    404 gpx_text (const char *buf,
    405           size_t len,
    406           size_t pos,
    407           size_t *tstart,
    408           size_t *tlen)
    409 {
    410   size_t end = gpx_tag_end (buf,
    411                             len,
    412                             pos);
    413   size_t i;
    414   size_t j;
    415 
    416   if (((size_t) -1) == end)
    417     return 0;
    418   if ( (end > pos) &&
    419        ('/' == buf[end - 1]) )
    420     return 0;   /* self-closing element, no content */
    421   i = end + 1;
    422   if ( (i + 9 <= len) &&
    423        (0 == memcmp (&buf[i],
    424                      "<![CDATA[",
    425                      9)) )
    426   {
    427     j = i + 9;
    428     while ( (j + 3 <= len) &&
    429             (j - i < GPX_MAX_TEXT) )
    430     {
    431       if (0 == memcmp (&buf[j],
    432                        "]]>",
    433                        3))
    434       {
    435         *tstart = i + 9;
    436         *tlen = j - (i + 9);
    437         return 1;
    438       }
    439       j++;
    440     }
    441     return 0;
    442   }
    443   j = i;
    444   while ( (j < len) &&
    445           ('<' != buf[j]) &&
    446           (j - i < GPX_MAX_TEXT) )
    447     j++;
    448   *tstart = i;
    449   *tlen = j - i;
    450   return (0 != *tlen);
    451 }
    452 
    453 
    454 /**
    455  * Find the next element named @a tag at or after @a from, staying below
    456  * @a limit.
    457  *
    458  * @param buf the buffer
    459  * @param limit offset one past the last byte to search
    460  * @param from where to start searching
    461  * @param tag element name
    462  * @return offset of the `<', or `(size_t) -1' if not found
    463  */
    464 static size_t
    465 gpx_find (const char *buf,
    466           size_t limit,
    467           size_t from,
    468           const char *tag)
    469 {
    470   for (size_t i = from; i < limit; i++)
    471   {
    472     if ('<' != buf[i])
    473       continue;
    474     if (gpx_tag_is (buf,
    475                     limit,
    476                     i,
    477                     tag))
    478       return i;
    479   }
    480   return (size_t) -1;
    481 }
    482 
    483 
    484 /**
    485  * Expand the five predefined XML entities and numeric character
    486  * references in @a in.  Anything else is copied through unchanged.
    487  *
    488  * @param in input text
    489  * @param inlen number of bytes in @a in
    490  * @param[out] out where to write the result
    491  * @param outsize number of bytes available in @a out
    492  * @return number of bytes written to @a out
    493  */
    494 static size_t
    495 gpx_unescape (const char *in,
    496               size_t inlen,
    497               char *out,
    498               size_t outsize)
    499 {
    500   size_t o = 0;
    501   size_t i = 0;
    502 
    503   while ( (i < inlen) &&
    504           (o + 5 < outsize) )
    505   {
    506     size_t j;
    507     uint32_t cp = 0;
    508 
    509     if ('&' != in[i])
    510     {
    511       out[o++] = in[i++];
    512       continue;
    513     }
    514     /* find the terminating `;' within a short window */
    515     j = i + 1;
    516     while ( (j < inlen) &&
    517             (j - i < 12) &&
    518             (';' != in[j]) )
    519       j++;
    520     if ( (j >= inlen) ||
    521          (';' != in[j]) )
    522     {
    523       out[o++] = in[i++];
    524       continue;
    525     }
    526     /* j is the offset of the `;', so the reference is j + 1 - i bytes */
    527     if ( (4 == j + 1 - i) &&
    528          (0 == memcmp (&in[i], "&lt;", 4)) )
    529       cp = '<';
    530     else if ( (4 == j + 1 - i) &&
    531               (0 == memcmp (&in[i], "&gt;", 4)) )
    532       cp = '>';
    533     else if ( (5 == j + 1 - i) &&
    534               (0 == memcmp (&in[i], "&amp;", 5)) )
    535       cp = '&';
    536     else if ( (6 == j + 1 - i) &&
    537               (0 == memcmp (&in[i], "&quot;", 6)) )
    538       cp = '"';
    539     else if ( (6 == j + 1 - i) &&
    540               (0 == memcmp (&in[i], "&apos;", 6)) )
    541       cp = '\'';
    542     else if ( (j - i > 2) &&
    543               ('#' == in[i + 1]) )
    544     {
    545       size_t k = i + 2;
    546       int base = 10;
    547 
    548       if ( (k < j) &&
    549            ( ('x' == in[k]) || ('X' == in[k]) ) )
    550       {
    551         base = 16;
    552         k++;
    553       }
    554       if (k == j)
    555       {
    556         out[o++] = in[i++];
    557         continue;
    558       }
    559       while (k < j)
    560       {
    561         int d;
    562 
    563         if ( ('0' <= in[k]) && ('9' >= in[k]) )
    564           d = in[k] - '0';
    565         else if ( (16 == base) && ('a' <= in[k]) && ('f' >= in[k]) )
    566           d = in[k] - 'a' + 10;
    567         else if ( (16 == base) && ('A' <= in[k]) && ('F' >= in[k]) )
    568           d = in[k] - 'A' + 10;
    569         else
    570           break;
    571         if (cp > 0x110000 / (uint32_t) base)
    572         {
    573           cp = 0;
    574           break;
    575         }
    576         cp = cp * (uint32_t) base + (uint32_t) d;
    577         k++;
    578       }
    579       if ( (k != j) ||
    580            (0 == cp) ||
    581            (cp > 0x10FFFF) ||
    582            ( (0xD800 <= cp) && (0xDFFF >= cp) ) )
    583       {
    584         out[o++] = in[i++];
    585         continue;
    586       }
    587     }
    588     else
    589     {
    590       out[o++] = in[i++];
    591       continue;   /* unknown entity: leave it alone */
    592     }
    593     if (cp < 0x80)
    594     {
    595       out[o++] = (char) cp;
    596     }
    597     else if (cp < 0x800)
    598     {
    599       out[o++] = (char) (0xC0 | (cp >> 6));
    600       out[o++] = (char) (0x80 | (cp & 0x3F));
    601     }
    602     else if (cp < 0x10000)
    603     {
    604       out[o++] = (char) (0xE0 | (cp >> 12));
    605       out[o++] = (char) (0x80 | ((cp >> 6) & 0x3F));
    606       out[o++] = (char) (0x80 | (cp & 0x3F));
    607     }
    608     else
    609     {
    610       out[o++] = (char) (0xF0 | (cp >> 18));
    611       out[o++] = (char) (0x80 | ((cp >> 12) & 0x3F));
    612       out[o++] = (char) (0x80 | ((cp >> 6) & 0x3F));
    613       out[o++] = (char) (0x80 | (cp & 0x3F));
    614     }
    615     i = j + 1;
    616   }
    617   return o;
    618 }
    619 
    620 
    621 /**
    622  * Emit a stretch of XML text after expanding entity references.
    623  *
    624  * @param ec extraction context
    625  * @param type meta data type
    626  * @param data the text
    627  * @param len number of bytes in @a data
    628  * @return 1 if the caller should stop extracting, 0 to continue
    629  */
    630 static int
    631 gpx_emit_xml (struct EXTRACTOR_ExtractContext *ec,
    632               enum EXTRACTOR_MetaType type,
    633               const char *data,
    634               size_t len)
    635 {
    636   char buf[EXTRACTOR_FORENSIC_MAX_STRING];
    637   size_t out;
    638 
    639   if (len > sizeof (buf) - 8)
    640     len = sizeof (buf) - 8;
    641   out = gpx_unescape (data,
    642                       len,
    643                       buf,
    644                       sizeof (buf));
    645   return EXTRACTOR_forensic_emit_text_ (ec,
    646                                         "gpx",
    647                                         type,
    648                                         buf,
    649                                         out);
    650 }
    651 
    652 
    653 /**
    654  * Parse a decimal number out of a stretch of bytes that is not
    655  * NUL-terminated.
    656  *
    657  * @param data the bytes
    658  * @param len number of bytes in @a data
    659  * @param[out] value where to store the result
    660  * @return 1 on success, 0 if @a data does not start with a number
    661  */
    662 static int
    663 gpx_parse_double (const char *data,
    664                   size_t len,
    665                   double *value)
    666 {
    667   char tmp[64];
    668   char *endp;
    669   double v;
    670   size_t i = 0;
    671   size_t o = 0;
    672 
    673   while ( (i < len) &&
    674           ( (' ' == data[i]) || ('\t' == data[i]) ||
    675             ('\r' == data[i]) || ('\n' == data[i]) ) )
    676     i++;
    677   while ( (i < len) &&
    678           (o < sizeof (tmp) - 1) &&
    679           ( ( ('0' <= data[i]) && ('9' >= data[i]) ) ||
    680             ('+' == data[i]) || ('-' == data[i]) ||
    681             ('.' == data[i]) || ('e' == data[i]) || ('E' == data[i]) ) )
    682     tmp[o++] = data[i++];
    683   tmp[o] = '\0';
    684   if (0 == o)
    685     return 0;
    686   v = strtod (tmp,
    687               &endp);
    688   if ( (endp == tmp) ||
    689        (! isfinite (v)) )
    690     return 0;
    691   *value = v;
    692   return 1;
    693 }
    694 
    695 
    696 /**
    697  * Days since 1970-01-01 for a proleptic Gregorian date.  (Howard
    698  * Hinnant's `days_from_civil'.)
    699  *
    700  * @param y year
    701  * @param m month, 1-12
    702  * @param d day of month, 1-31
    703  * @return day number, negative before the epoch
    704  */
    705 static int64_t
    706 gpx_days_from_civil (int64_t y,
    707                      int64_t m,
    708                      int64_t d)
    709 {
    710   int64_t era;
    711   int64_t yoe;
    712   int64_t doy;
    713   int64_t doe;
    714 
    715   y -= (m <= 2);
    716   era = (y >= 0 ? y : y - 399) / 400;
    717   yoe = y - era * 400;
    718   doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
    719   doe = yoe * 365 + yoe / 4 - yoe / 100 + doy;
    720   return era * 146097 + doe - 719468;
    721 }
    722 
    723 
    724 /**
    725  * Parse an ISO 8601 timestamp as GPX writes it:
    726  * `YYYY-MM-DDThh:mm:ss[.fff][Z|(+|-)hh[:mm]]'.  A missing zone is taken
    727  * as UTC, which is what the GPX schema requires anyway.
    728  *
    729  * @param s the text
    730  * @param len number of bytes in @a s
    731  * @param[out] when where to store seconds since the Unix epoch
    732  * @return 1 on success, 0 if @a s is not a timestamp
    733  */
    734 static int
    735 gpx_parse_iso8601 (const char *s,
    736                    size_t len,
    737                    int64_t *when)
    738 {
    739   int64_t v[6] = { 0, 0, 0, 0, 0, 0 };
    740   static const size_t widths[6] = { 4, 2, 2, 2, 2, 2 };
    741   static const char seps[5] = { '-', '-', 'T', ':', ':' };
    742   size_t i = 0;
    743   int64_t off = 0;
    744 
    745   while ( (i < len) &&
    746           ( (' ' == s[i]) || ('\t' == s[i]) ||
    747             ('\r' == s[i]) || ('\n' == s[i]) ) )
    748     i++;
    749   for (unsigned int f = 0; f < 6; f++)
    750   {
    751     if (i + widths[f] > len)
    752       return 0;
    753     for (size_t k = 0; k < widths[f]; k++)
    754     {
    755       if ( ('0' > s[i + k]) ||
    756            ('9' < s[i + k]) )
    757         return 0;
    758       v[f] = v[f] * 10 + (s[i + k] - '0');
    759     }
    760     i += widths[f];
    761     if (f < 5)
    762     {
    763       if (i >= len)
    764         return 0;
    765       if ( (2 == f) &&
    766            (' ' == s[i]) )
    767         i++;   /* `YYYY-MM-DD hh:mm:ss' also occurs */
    768       else if (seps[f] == s[i])
    769         i++;
    770       else
    771         return 0;
    772     }
    773   }
    774   if ( (v[1] < 1) || (v[1] > 12) ||
    775        (v[2] < 1) || (v[2] > 31) ||
    776        (v[3] > 23) || (v[4] > 59) || (v[5] > 60) )
    777     return 0;
    778   if ( (i < len) &&
    779        ('.' == s[i]) )
    780   {
    781     i++;
    782     while ( (i < len) &&
    783             ('0' <= s[i]) && ('9' >= s[i]) )
    784       i++;
    785   }
    786   if ( (i < len) &&
    787        ( ('+' == s[i]) || ('-' == s[i]) ) )
    788   {
    789     int neg = ('-' == s[i]);
    790     int64_t oh = 0;
    791     int64_t om = 0;
    792 
    793     i++;
    794     if (i + 2 > len)
    795       return 0;
    796     for (size_t k = 0; k < 2; k++)
    797     {
    798       if ( ('0' > s[i + k]) || ('9' < s[i + k]) )
    799         return 0;
    800       oh = oh * 10 + (s[i + k] - '0');
    801     }
    802     i += 2;
    803     if ( (i < len) &&
    804          (':' == s[i]) )
    805       i++;
    806     if (i + 2 <= len)
    807     {
    808       if ( ('0' <= s[i]) && ('9' >= s[i]) &&
    809            ('0' <= s[i + 1]) && ('9' >= s[i + 1]) )
    810       {
    811         om = (s[i] - '0') * 10 + (s[i + 1] - '0');
    812         i += 2;
    813       }
    814     }
    815     if ( (oh > 23) || (om > 59) )
    816       return 0;
    817     off = oh * 3600 + om * 60;
    818     if (neg)
    819       off = -off;
    820   }
    821   *when = gpx_days_from_civil (v[0],
    822                                v[1],
    823                                v[2]) * 86400
    824           + v[3] * 3600 + v[4] * 60 + v[5]
    825           - off;
    826   return 1;
    827 }
    828 
    829 
    830 /**
    831  * Great-circle distance between two points, in metres.
    832  *
    833  * @param lat1 latitude of the first point, in degrees
    834  * @param lon1 longitude of the first point, in degrees
    835  * @param lat2 latitude of the second point, in degrees
    836  * @param lon2 longitude of the second point, in degrees
    837  * @return distance in metres
    838  */
    839 static double
    840 gpx_haversine (double lat1,
    841                double lon1,
    842                double lat2,
    843                double lon2)
    844 {
    845   double rad = M_PI / 180.0;
    846   double dlat = (lat2 - lat1) * rad;
    847   double dlon = (lon2 - lon1) * rad;
    848   double a;
    849 
    850   a = sin (dlat / 2.0) * sin (dlat / 2.0)
    851       + cos (lat1 * rad) * cos (lat2 * rad)
    852       * sin (dlon / 2.0) * sin (dlon / 2.0);
    853   if (a < 0.0)
    854     a = 0.0;
    855   if (a > 1.0)
    856     a = 1.0;
    857   return 2.0 * GPX_EARTH_RADIUS * asin (sqrt (a));
    858 }
    859 
    860 
    861 /**
    862  * Record a point of any kind in @a sc.
    863  *
    864  * @param sc scan state
    865  * @param lat latitude in degrees
    866  * @param lon longitude in degrees
    867  * @param track 1 if this is a `<trkpt>' (which also feeds the distance
    868  *        sum), 0 for a waypoint or route point
    869  */
    870 static void
    871 gpx_note_point (struct GpxScan *sc,
    872                 double lat,
    873                 double lon,
    874                 int track)
    875 {
    876   if ( (lat < -90.0) || (lat > 90.0) ||
    877        (lon < -180.0) || (lon > 180.0) )
    878     return;   /* out of range: not a coordinate, do not let it skew the box */
    879   if (! sc->have_bbox)
    880   {
    881     sc->minlat = sc->maxlat = lat;
    882     sc->minlon = sc->maxlon = lon;
    883     sc->first_lat = lat;
    884     sc->first_lon = lon;
    885     sc->have_bbox = 1;
    886   }
    887   else
    888   {
    889     if (lat < sc->minlat)
    890       sc->minlat = lat;
    891     if (lat > sc->maxlat)
    892       sc->maxlat = lat;
    893     if (lon < sc->minlon)
    894       sc->minlon = lon;
    895     if (lon > sc->maxlon)
    896       sc->maxlon = lon;
    897   }
    898   if (track)
    899   {
    900     if (sc->have_prev)
    901       sc->distance += gpx_haversine (sc->prev_lat,
    902                                      sc->prev_lon,
    903                                      lat,
    904                                      lon);
    905     sc->prev_lat = lat;
    906     sc->prev_lon = lon;
    907     sc->have_prev = 1;
    908   }
    909 }
    910 
    911 
    912 /**
    913  * Walk the buffer once, tallying points, coordinates, elevations and
    914  * timestamps.
    915  *
    916  * The walk is linear and every iteration advances at least one byte, so
    917  * it is bounded by @a len regardless of what the file contains.
    918  *
    919  * @param buf the buffer
    920  * @param len number of bytes in @a buf
    921  * @param[out] sc scan state to fill in
    922  */
    923 static void
    924 gpx_walk_points (const char *buf,
    925                  size_t len,
    926                  struct GpxScan *sc)
    927 {
    928   for (size_t i = 0; i < len; i++)
    929   {
    930     size_t vs;
    931     size_t vl;
    932     double lat;
    933     double lon;
    934     /* 0 = not a point, 1 = <trkpt>, 2 = <wpt>, 3 = <rtept> */
    935     unsigned int kind;
    936 
    937     if ('<' != buf[i])
    938       continue;
    939     if (gpx_tag_is (buf, len, i, "trkpt"))
    940     {
    941       kind = 1;
    942     }
    943     else if (gpx_tag_is (buf, len, i, "wpt"))
    944     {
    945       kind = 2;
    946     }
    947     else if (gpx_tag_is (buf, len, i, "rtept"))
    948     {
    949       kind = 3;
    950     }
    951     else if (sc->seen_point &&
    952              gpx_tag_is (buf, len, i, "ele") )
    953     {
    954       double ele;
    955 
    956       if (gpx_text (buf, len, i, &vs, &vl) &&
    957           gpx_parse_double (&buf[vs], vl, &ele) &&
    958           (ele > -20000.0) && (ele < 20000.0) )
    959       {
    960         if (! sc->have_ele)
    961         {
    962           sc->minele = sc->maxele = ele;
    963           sc->have_ele = 1;
    964         }
    965         else
    966         {
    967           if (ele < sc->minele)
    968             sc->minele = ele;
    969           if (ele > sc->maxele)
    970             sc->maxele = ele;
    971         }
    972       }
    973       continue;
    974     }
    975     else if (sc->seen_point &&
    976              gpx_tag_is (buf, len, i, "time") )
    977     {
    978       int64_t when;
    979 
    980       if (gpx_text (buf, len, i, &vs, &vl) &&
    981           gpx_parse_iso8601 (&buf[vs], vl, &when) )
    982       {
    983         if (0 == sc->first_time)
    984           sc->first_time = when;
    985         sc->last_time = when;
    986       }
    987       continue;
    988     }
    989     else
    990     {
    991       continue;
    992     }
    993     if (1 == kind)
    994       sc->trkpts++;
    995     else if (2 == kind)
    996       sc->wpts++;
    997     else
    998       sc->rtepts++;
    999     sc->seen_point = 1;
   1000     if (gpx_attr (buf, len, i, "lat", &vs, &vl) &&
   1001         gpx_parse_double (&buf[vs], vl, &lat) &&
   1002         gpx_attr (buf, len, i, "lon", &vs, &vl) &&
   1003         gpx_parse_double (&buf[vs], vl, &lon) )
   1004       gpx_note_point (sc,
   1005                       lat,
   1006                       lon,
   1007                       (1 == kind));
   1008   }
   1009 }
   1010 
   1011 
   1012 /**
   1013  * Does @a creator name a piece of hardware rather than an application?
   1014  *
   1015  * Purely a substring test against the GPS makers whose devices write
   1016  * GPX.  It only decides whether we *additionally* report the value as
   1017  * #EXTRACTOR_METATYPE_DEVICE_MODEL; the value is always reported as
   1018  * #EXTRACTOR_METATYPE_CREATED_BY_SOFTWARE.
   1019  *
   1020  * @param creator the `creator' attribute value
   1021  * @param len number of bytes in @a creator
   1022  * @return 1 if it looks like a device model, 0 otherwise
   1023  */
   1024 static int
   1025 gpx_looks_like_device (const char *creator,
   1026                        size_t len)
   1027 {
   1028   static const char *vendors[] = {
   1029     "garmin", "etrex", "oregon", "gpsmap", "edge", "fenix", "forerunner",
   1030     "montana", "dakota", "colorado", "magellan", "suunto", "polar",
   1031     "wahoo", "elemnt", "bryton", "lezyne", "sigma rox", "coros",
   1032     "tomtom", "holux", "qstarz", "columbus", "igotu", "trimble",
   1033     NULL
   1034   };
   1035   char low[128];
   1036   size_t n = len;
   1037 
   1038   if (n > sizeof (low) - 1)
   1039     n = sizeof (low) - 1;
   1040   for (size_t i = 0; i < n; i++)
   1041   {
   1042     char c = creator[i];
   1043 
   1044     if ( ('A' <= c) && ('Z' >= c) )
   1045       c = (char) (c - 'A' + 'a');
   1046     low[i] = c;
   1047   }
   1048   low[n] = '\0';
   1049   for (unsigned int i = 0; NULL != vendors[i]; i++)
   1050     if (NULL != strstr (low,
   1051                         vendors[i]))
   1052       return 1;
   1053   return 0;
   1054 }
   1055 
   1056 
   1057 /**
   1058  * Report everything the `<metadata>' block holds.
   1059  *
   1060  * @param ec extraction context
   1061  * @param buf the buffer
   1062  * @param len number of bytes in @a buf
   1063  * @return 1 if the caller should stop extracting, 0 to continue
   1064  */
   1065 static int
   1066 gpx_do_metadata (struct EXTRACTOR_ExtractContext *ec,
   1067                  const char *buf,
   1068                  size_t len)
   1069 {
   1070   size_t md;
   1071   size_t mdend;
   1072   size_t p;
   1073   size_t vs;
   1074   size_t vl;
   1075 
   1076   md = gpx_find (buf,
   1077                  len,
   1078                  0,
   1079                  "metadata");
   1080   if (((size_t) -1) == md)
   1081   {
   1082     /* GPX 1.0 has no <metadata> element: name, desc, author, email,
   1083        url, time, keywords and bounds are direct children of <gpx>.
   1084        Treat everything from the root element up to the first <wpt>,
   1085        <rte> or <trk> as the metadata block. */
   1086     md = gpx_find (buf,
   1087                    len,
   1088                    0,
   1089                    "gpx");
   1090     if (((size_t) -1) == md)
   1091       return 0;
   1092     mdend = len;
   1093     for (size_t i = md + 1; i < len; i++)
   1094     {
   1095       if ('<' != buf[i])
   1096         continue;
   1097       if (gpx_tag_is (buf, len, i, "wpt") ||
   1098           gpx_tag_is (buf, len, i, "rte") ||
   1099           gpx_tag_is (buf, len, i, "trk") )
   1100       {
   1101         mdend = i;
   1102         break;
   1103       }
   1104     }
   1105   }
   1106   else
   1107   {
   1108     /* the block ends at `</metadata>'; if that is missing (truncated
   1109        file), fall back to the end of the buffer */
   1110     mdend = len;
   1111     for (size_t i = md; i + 11 <= len; i++)
   1112       if (0 == memcmp (&buf[i],
   1113                        "</metadata>",
   1114                        11))
   1115       {
   1116         mdend = i;
   1117         break;
   1118       }
   1119   }
   1120   p = gpx_find (buf, mdend, md + 1, "name");
   1121   if ( (((size_t) -1) != p) &&
   1122        gpx_text (buf, mdend, p, &vs, &vl) &&
   1123        gpx_emit_xml (ec, EXTRACTOR_METATYPE_TITLE, &buf[vs], vl) )
   1124     return 1;
   1125   p = gpx_find (buf, mdend, md + 1, "desc");
   1126   if ( (((size_t) -1) != p) &&
   1127        gpx_text (buf, mdend, p, &vs, &vl) &&
   1128        gpx_emit_xml (ec, EXTRACTOR_METATYPE_DESCRIPTION, &buf[vs], vl) )
   1129     return 1;
   1130   p = gpx_find (buf, mdend, md + 1, "keywords");
   1131   if ( (((size_t) -1) != p) &&
   1132        gpx_text (buf, mdend, p, &vs, &vl) &&
   1133        gpx_emit_xml (ec, EXTRACTOR_METATYPE_KEYWORDS, &buf[vs], vl) )
   1134     return 1;
   1135   /* The author is `<author><name>' in GPX 1.1 and a plain
   1136      `<author>text</author>' in GPX 1.0. */
   1137   p = gpx_find (buf, mdend, md + 1, "author");
   1138   if (((size_t) -1) != p)
   1139   {
   1140     size_t a = gpx_find (buf, mdend, p + 1, "name");
   1141     int emitted = 0;
   1142 
   1143     if ( (((size_t) -1) != a) &&
   1144          gpx_text (buf, mdend, a, &vs, &vl) )
   1145     {
   1146       emitted = 1;
   1147       if (gpx_emit_xml (ec, EXTRACTOR_METATYPE_AUTHOR_NAME, &buf[vs], vl))
   1148         return 1;
   1149     }
   1150     if ( (! emitted) &&
   1151          gpx_text (buf, mdend, p, &vs, &vl) &&
   1152          gpx_emit_xml (ec, EXTRACTOR_METATYPE_AUTHOR_NAME, &buf[vs], vl) )
   1153       return 1;
   1154   }
   1155   /* `<email id="user" domain="host"/>' in GPX 1.1 -- split so that the
   1156      file does not read as a harvestable address -- and a plain
   1157      `<email>user@host</email>' in GPX 1.0. */
   1158   p = gpx_find (buf, mdend, md + 1, "email");
   1159   if (((size_t) -1) != p)
   1160   {
   1161     size_t ds;
   1162     size_t dl;
   1163 
   1164     if (gpx_attr (buf, mdend, p, "id", &vs, &vl) &&
   1165         gpx_attr (buf, mdend, p, "domain", &ds, &dl) )
   1166     {
   1167       if (EXTRACTOR_forensic_emit_ (ec,
   1168                                     "gpx",
   1169                                     EXTRACTOR_METATYPE_AUTHOR_EMAIL,
   1170                                     "%.*s@%.*s",
   1171                                     (int) vl,
   1172                                     &buf[vs],
   1173                                     (int) dl,
   1174                                     &buf[ds]))
   1175         return 1;
   1176     }
   1177     else if (gpx_text (buf, mdend, p, &vs, &vl) &&
   1178              gpx_emit_xml (ec,
   1179                            EXTRACTOR_METATYPE_AUTHOR_EMAIL,
   1180                            &buf[vs],
   1181                            vl) )
   1182     {
   1183       return 1;
   1184     }
   1185   }
   1186   /* `<copyright author="...">' with an optional `<license>' child in
   1187      GPX 1.1; a plain text element in GPX 1.0. */
   1188   p = gpx_find (buf, mdend, md + 1, "copyright");
   1189   if (((size_t) -1) != p)
   1190   {
   1191     if (gpx_attr (buf, mdend, p, "author", &vs, &vl))
   1192     {
   1193       if (gpx_emit_xml (ec, EXTRACTOR_METATYPE_COPYRIGHT, &buf[vs], vl))
   1194         return 1;
   1195     }
   1196     else if (gpx_text (buf, mdend, p, &vs, &vl) &&
   1197              gpx_emit_xml (ec, EXTRACTOR_METATYPE_COPYRIGHT, &buf[vs], vl) )
   1198     {
   1199       return 1;
   1200     }
   1201     {
   1202       size_t l = gpx_find (buf, mdend, p + 1, "license");
   1203 
   1204       if ( (((size_t) -1) != l) &&
   1205            gpx_text (buf, mdend, l, &vs, &vl) &&
   1206            gpx_emit_xml (ec, EXTRACTOR_METATYPE_LICENSE, &buf[vs], vl) )
   1207         return 1;
   1208     }
   1209   }
   1210   /* every `<link href="...">' in the block, capped */
   1211   {
   1212     size_t l = md;
   1213     unsigned int n = 0;
   1214 
   1215     while (n < EXTRACTOR_FORENSIC_MAX_ITEMS)
   1216     {
   1217       l = gpx_find (buf, mdend, l + 1, "link");
   1218       if (((size_t) -1) == l)
   1219         break;
   1220       if (gpx_attr (buf, mdend, l, "href", &vs, &vl))
   1221       {
   1222         n++;
   1223         if (gpx_emit_xml (ec, EXTRACTOR_METATYPE_URL, &buf[vs], vl))
   1224           return 1;
   1225       }
   1226     }
   1227   }
   1228   /* GPX 1.0 spells the same thing `<url>' */
   1229   p = gpx_find (buf, mdend, md + 1, "url");
   1230   if ( (((size_t) -1) != p) &&
   1231        gpx_text (buf, mdend, p, &vs, &vl) &&
   1232        gpx_emit_xml (ec, EXTRACTOR_METATYPE_URL, &buf[vs], vl) )
   1233     return 1;
   1234   p = gpx_find (buf, mdend, md + 1, "time");
   1235   if (((size_t) -1) != p)
   1236   {
   1237     int64_t when;
   1238 
   1239     if (gpx_text (buf, mdend, p, &vs, &vl))
   1240     {
   1241       if (gpx_parse_iso8601 (&buf[vs], vl, &when))
   1242       {
   1243         if (EXTRACTOR_forensic_emit_unix_time_ (ec,
   1244                                                 "gpx",
   1245                                                 EXTRACTOR_METATYPE_CREATION_DATE,
   1246                                                 when))
   1247           return 1;
   1248       }
   1249       else if (gpx_emit_xml (ec,
   1250                              EXTRACTOR_METATYPE_UNKNOWN_DATE,
   1251                              &buf[vs],
   1252                              vl))
   1253       {
   1254         return 1;
   1255       }
   1256     }
   1257   }
   1258   return 0;
   1259 }
   1260 
   1261 
   1262 /**
   1263  * Main entry method for the GPX extraction plugin.
   1264  *
   1265  * @param ec extraction context provided to the plugin
   1266  */
   1267 void
   1268 EXTRACTOR_gpx_extract_method (struct EXTRACTOR_ExtractContext *ec);
   1269 
   1270 void
   1271 EXTRACTOR_gpx_extract_method (struct EXTRACTOR_ExtractContext *ec)
   1272 {
   1273   char head[GPX_MAGIC_WINDOW];
   1274   char *buf = NULL;
   1275   size_t hlen = 0;
   1276   size_t len = 0;
   1277   size_t cap;
   1278   uint64_t fsize;
   1279   size_t root;
   1280   size_t vs;
   1281   size_t vl;
   1282   struct GpxScan sc;
   1283   int truncated;
   1284 
   1285   /* Bail out on the first few bytes: almost nothing we are handed is
   1286      GPX, and we must not pay for the ones that are not. */
   1287   {
   1288     void *data;
   1289     ssize_t ret;
   1290 
   1291     if (0 != ec->seek (ec->cls,
   1292                        0,
   1293                        SEEK_SET))
   1294       return;
   1295     while (hlen < sizeof (head))
   1296     {
   1297       ret = ec->read (ec->cls,
   1298                       &data,
   1299                       sizeof (head) - hlen);
   1300       if (0 >= ret)
   1301         break;
   1302       if (((size_t) ret) > sizeof (head) - hlen)
   1303         return;   /* the IPC layer is misbehaving */
   1304       memcpy (&head[hlen],
   1305               data,
   1306               (size_t) ret);
   1307       hlen += (size_t) ret;
   1308     }
   1309   }
   1310   if (hlen < 16)
   1311     return;
   1312   root = gpx_find (head,
   1313                    hlen,
   1314                    0,
   1315                    "gpx");
   1316   if (((size_t) -1) == root)
   1317     return;   /* not GPX */
   1318   /* GPX is XML; require the declaration or at least that the document
   1319      starts with markup, so that a text file merely containing "<gpx" is
   1320      not claimed. */
   1321   {
   1322     size_t k = 0;
   1323 
   1324     if ( (hlen >= 3) &&
   1325          (0 == memcmp (head, "\xef\xbb\xbf", 3)) )
   1326       k = 3;   /* UTF-8 BOM */
   1327     while ( (k < hlen) &&
   1328             ( (' ' == head[k]) || ('\t' == head[k]) ||
   1329               ('\r' == head[k]) || ('\n' == head[k]) ) )
   1330       k++;
   1331     if ( (k >= hlen) ||
   1332          ('<' != head[k]) )
   1333       return;
   1334   }
   1335 
   1336   fsize = ec->get_size (ec->cls);
   1337   cap = GPX_SCAN_CAP;
   1338   if ( (UINT64_MAX != fsize) &&
   1339        (fsize < (uint64_t) cap) )
   1340     cap = (size_t) fsize;
   1341   if (cap < hlen)
   1342     cap = hlen;
   1343   buf = malloc (cap);
   1344   if (NULL == buf)
   1345     return;
   1346   if (0 != ec->seek (ec->cls,
   1347                      0,
   1348                      SEEK_SET))
   1349   {
   1350     free (buf);
   1351     return;
   1352   }
   1353   while (len < cap)
   1354   {
   1355     void *data;
   1356     ssize_t ret;
   1357 
   1358     ret = ec->read (ec->cls,
   1359                     &data,
   1360                     cap - len);
   1361     if (0 >= ret)
   1362       break;
   1363     if (((size_t) ret) > cap - len)
   1364       break;   /* the IPC layer is misbehaving */
   1365     memcpy (&buf[len],
   1366             data,
   1367             (size_t) ret);
   1368     len += (size_t) ret;
   1369   }
   1370   if (len < 16)
   1371   {
   1372     free (buf);
   1373     return;
   1374   }
   1375   truncated = ( (UINT64_MAX == fsize) ||
   1376                 (fsize > (uint64_t) len) );
   1377 
   1378   if (0 != ec->proc (ec->cls,
   1379                      "gpx",
   1380                      EXTRACTOR_METATYPE_MIMETYPE,
   1381                      EXTRACTOR_METAFORMAT_UTF8,
   1382                      "text/plain",
   1383                      "application/gpx+xml",
   1384                      strlen ("application/gpx+xml") + 1))
   1385     goto out;
   1386 
   1387   root = gpx_find (buf,
   1388                    (len < GPX_MAGIC_WINDOW) ? len : GPX_MAGIC_WINDOW,
   1389                    0,
   1390                    "gpx");
   1391   if (((size_t) -1) != root)
   1392   {
   1393     /* The `creator' attribute is the single most identifying field in
   1394        the format: it names the device or the application that produced
   1395        the track. */
   1396     if (gpx_attr (buf, len, root, "creator", &vs, &vl))
   1397     {
   1398       if (gpx_emit_xml (ec,
   1399                         EXTRACTOR_METATYPE_CREATED_BY_SOFTWARE,
   1400                         &buf[vs],
   1401                         vl))
   1402         goto out;
   1403       if (gpx_looks_like_device (&buf[vs], vl) &&
   1404           gpx_emit_xml (ec,
   1405                         EXTRACTOR_METATYPE_DEVICE_MODEL,
   1406                         &buf[vs],
   1407                         vl) )
   1408         goto out;
   1409     }
   1410     if (gpx_attr (buf, len, root, "version", &vs, &vl) &&
   1411         gpx_emit_xml (ec,
   1412                       EXTRACTOR_METATYPE_FORMAT_VERSION,
   1413                       &buf[vs],
   1414                       vl) )
   1415       goto out;
   1416   }
   1417   /* GPX coordinates are always WGS 84; the schema does not allow
   1418      anything else, so this is a statement about the format. */
   1419   if (EXTRACTOR_forensic_emit_ (ec,
   1420                                 "gpx",
   1421                                 EXTRACTOR_METATYPE_COORDINATE_SYSTEM,
   1422                                 "WGS 84"))
   1423     goto out;
   1424   if (gpx_do_metadata (ec,
   1425                        buf,
   1426                        len))
   1427     goto out;
   1428 
   1429   memset (&sc,
   1430           0,
   1431           sizeof (sc));
   1432   gpx_walk_points (buf,
   1433                    len,
   1434                    &sc);
   1435 
   1436   /* An explicit <bounds> beats what we computed: it covers the whole
   1437      file, ours only covers what we scanned. */
   1438   {
   1439     size_t b = gpx_find (buf, len, 0, "bounds");
   1440     double minlat;
   1441     double minlon;
   1442     double maxlat;
   1443     double maxlon;
   1444     int have = 0;
   1445 
   1446     if (((size_t) -1) != b)
   1447     {
   1448       if (gpx_attr (buf, len, b, "minlat", &vs, &vl) &&
   1449           gpx_parse_double (&buf[vs], vl, &minlat) &&
   1450           gpx_attr (buf, len, b, "minlon", &vs, &vl) &&
   1451           gpx_parse_double (&buf[vs], vl, &minlon) &&
   1452           gpx_attr (buf, len, b, "maxlat", &vs, &vl) &&
   1453           gpx_parse_double (&buf[vs], vl, &maxlat) &&
   1454           gpx_attr (buf, len, b, "maxlon", &vs, &vl) &&
   1455           gpx_parse_double (&buf[vs], vl, &maxlon) &&
   1456           (minlat >= -90.0) && (maxlat <= 90.0) &&
   1457           (minlon >= -180.0) && (maxlon <= 180.0) )
   1458         have = 1;
   1459     }
   1460     if ( (! have) &&
   1461          sc.have_bbox)
   1462     {
   1463       minlat = sc.minlat;
   1464       minlon = sc.minlon;
   1465       maxlat = sc.maxlat;
   1466       maxlon = sc.maxlon;
   1467       have = 1;
   1468     }
   1469     if (have &&
   1470         EXTRACTOR_forensic_emit_ (ec,
   1471                                   "gpx",
   1472                                   EXTRACTOR_METATYPE_BOUNDING_BOX,
   1473                                   "%.6f,%.6f,%.6f,%.6f",
   1474                                   minlon,
   1475                                   minlat,
   1476                                   maxlon,
   1477                                   maxlat) )
   1478       goto out;
   1479   }
   1480   if (sc.have_bbox)
   1481   {
   1482     /* Where the recording started; in a personal track log that is very
   1483        often the recorder's home. */
   1484     if (EXTRACTOR_forensic_emit_ (ec,
   1485                                   "gpx",
   1486                                   EXTRACTOR_METATYPE_GPS_LATITUDE,
   1487                                   "%.6f",
   1488                                   sc.first_lat))
   1489       goto out;
   1490     if (EXTRACTOR_forensic_emit_ (ec,
   1491                                   "gpx",
   1492                                   EXTRACTOR_METATYPE_GPS_LONGITUDE,
   1493                                   "%.6f",
   1494                                   sc.first_lon))
   1495       goto out;
   1496   }
   1497   if (EXTRACTOR_forensic_emit_ (ec,
   1498                                 "gpx",
   1499                                 EXTRACTOR_METATYPE_ENTRY_COUNT,
   1500                                 "%llu",
   1501                                 (unsigned long long) sc.trkpts))
   1502     goto out;
   1503   if (EXTRACTOR_forensic_emit_ (ec,
   1504                                 "gpx",
   1505                                 EXTRACTOR_METATYPE_COMMENT,
   1506                                 "%llu track points, %llu waypoints,"
   1507                                 " %llu route points",
   1508                                 (unsigned long long) sc.trkpts,
   1509                                 (unsigned long long) sc.wpts,
   1510                                 (unsigned long long) sc.rtepts))
   1511     goto out;
   1512   if ( (0 != sc.first_time) &&
   1513        (sc.last_time > sc.first_time) )
   1514   {
   1515     int64_t d = sc.last_time - sc.first_time;
   1516 
   1517     if (EXTRACTOR_forensic_emit_ (ec,
   1518                                   "gpx",
   1519                                   EXTRACTOR_METATYPE_DURATION,
   1520                                   "%lld:%02lld:%02lld",
   1521                                   (long long) (d / 3600),
   1522                                   (long long) ((d / 60) % 60),
   1523                                   (long long) (d % 60)))
   1524       goto out;
   1525   }
   1526   if (0 != sc.first_time)
   1527   {
   1528     /* When the first point was recorded; for a track without a
   1529        <metadata><time> this is the only timestamp in the file. */
   1530     if (EXTRACTOR_forensic_emit_unix_time_ (ec,
   1531                                             "gpx",
   1532                                             EXTRACTOR_METATYPE_CREATION_DATE,
   1533                                             sc.first_time))
   1534       goto out;
   1535     if (EXTRACTOR_forensic_emit_unix_time_ (ec,
   1536                                             "gpx",
   1537                                             EXTRACTOR_METATYPE_MODIFICATION_DATE,
   1538                                             sc.last_time))
   1539       goto out;
   1540   }
   1541   if (sc.have_ele)
   1542   {
   1543     if (sc.maxele - sc.minele < 0.05)
   1544     {
   1545       if (EXTRACTOR_forensic_emit_ (ec,
   1546                                     "gpx",
   1547                                     EXTRACTOR_METATYPE_LOCATION_ELEVATION,
   1548                                     "%.1f m",
   1549                                     sc.minele))
   1550         goto out;
   1551     }
   1552     else if (EXTRACTOR_forensic_emit_ (ec,
   1553                                        "gpx",
   1554                                        EXTRACTOR_METATYPE_LOCATION_ELEVATION,
   1555                                        "%.1f-%.1f m",
   1556                                        sc.minele,
   1557                                        sc.maxele))
   1558     {
   1559       goto out;
   1560     }
   1561   }
   1562   if (sc.distance >= 1.0)
   1563   {
   1564     /* Haversine sum over the track points we scanned -- free, since we
   1565        walked them anyway.  Under the scan cap this is a lower bound. */
   1566     if (EXTRACTOR_forensic_emit_ (ec,
   1567                                   "gpx",
   1568                                   EXTRACTOR_METATYPE_DISTANCE,
   1569                                   "%.0f m",
   1570                                   sc.distance))
   1571       goto out;
   1572   }
   1573   /* Counts from a partial scan are lower bounds, and saying so is the
   1574      whole point: silently under-reporting how many points a track has
   1575      would be worse than not reporting at all. */
   1576   if (truncated &&
   1577       EXTRACTOR_forensic_emit_ (ec,
   1578                                 "gpx",
   1579                                 EXTRACTOR_METATYPE_COMMENT,
   1580                                 "scan truncated at 1 MiB; the counts,"
   1581                                 " bounding box, duration and distance"
   1582                                 " above cover only that prefix of the"
   1583                                 " file") )
   1584     goto out;
   1585 out:
   1586   free (buf);
   1587 }
   1588 
   1589 
   1590 /* end of gpx_extractor.c */