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], "<", 4)) ) 511 cp = '<'; 512 else if ( (4 == j + 1 - i) && 513 (0 == memcmp (&in[i], ">", 4)) ) 514 cp = '>'; 515 else if ( (5 == j + 1 - i) && 516 (0 == memcmp (&in[i], "&", 5)) ) 517 cp = '&'; 518 else if ( (6 == j + 1 - i) && 519 (0 == memcmp (&in[i], """, 6)) ) 520 cp = '"'; 521 else if ( (6 == j + 1 - i) && 522 (0 == memcmp (&in[i], "'", 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 */