ebook_extractor.c (90401B)
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/ebook_extractor.c 22 * @brief plugin to support EPUB and MOBI/AZW electronic books 23 * @author Christian Grothoff 24 * 25 * Two unrelated containers share this plugin because they answer the 26 * same question. An EPUB is a ZIP whose OPF package document carries 27 * Dublin Core; a MOBI is a Palm database whose EXTH header carries the 28 * same fields plus, on files that came from a store, a watermark. What 29 * is worth having here is not the title but the provenance: which tool 30 * built the file, when, for whom. 31 * 32 * With the "fulltext" option the book's text is reported as one 33 * #EXTRACTOR_METATYPE_FULL_TEXT item: 34 * - EPUB: the linear entries of the OPF spine, in spine order (not 35 * manifest or archive order); each (X)HTML content document is 36 * converted by a tolerant markup scanner that drops head, script, 37 * style, nav, aside, page-level header/footer, hidden elements, ARIA 38 * landmark roles for the page margins and page break markers 39 * (epub:type="pagebreak"); block elements, <br><br> and chapter 40 * boundaries end paragraphs. Documents listed in encryption.xml 41 * (DRM) are skipped. 42 * - MOBI/AZW: the text records, uncompressed or PalmDOC compressed, 43 * with the trailing entries stripped, run through the same scanner; 44 * <mbp:pagebreak/> ends a paragraph. Encrypted books and HUFF/CDIC 45 * compression (used by some Amazon-built files) are not supported. 46 * For KF8 (AZW3) the raw markup is scanned in storage order, which 47 * is the reading order of the text. 48 * 49 * References: 50 * - EPUB 3 OCF and Package Document, https://www.w3.org/TR/epub-33/ 51 * - MOBI format, https://wiki.mobileread.com/wiki/MOBI 52 * - PalmDOC / PDB, https://wiki.mobileread.com/wiki/PDB 53 */ 54 #include "platform.h" 55 #include "extractor.h" 56 #include "forensics.h" 57 #include "fulltext.h" 58 #include "unzip.h" 59 60 61 /** 62 * Name we report our meta data under. 63 */ 64 #define PLUGIN_NAME "ebook" 65 66 /** 67 * Never read more than this from `META-INF/container.xml'. It holds a 68 * handful of paths; anything larger is padding. 69 */ 70 #define MAX_CONTAINER (64 * 1024) 71 72 /** 73 * Never read more than this from the OPF package document. Real ones 74 * are a few kilobytes plus the manifest. 75 */ 76 #define MAX_OPF (256 * 1024) 77 78 /** 79 * Never read more than this from record 0 of a Palm database. The MOBI 80 * and EXTH headers together are a few kilobytes. 81 */ 82 #define MAX_RECORD0 (64 * 1024) 83 84 /** 85 * Upper bound on the number of EXTH records we will walk. 86 */ 87 #define MAX_EXTH 1024 88 89 /** 90 * Upper bound on the number of XML elements we will walk in the OPF. 91 */ 92 #define MAX_ELEMENTS 8192 93 94 /** 95 * Seconds between the Palm epoch (1904-01-01) and the Unix epoch. 96 */ 97 #define PALM_EPOCH_OFFSET 2082844800LL 98 99 100 /** 101 * Find @a needle in @a hay. `memmem()' is a GNU extension, and this is 102 * only ever run over buffers of a few hundred kilobytes. 103 * 104 * @param hay buffer to search 105 * @param hlen number of bytes in @a hay 106 * @param needle bytes to look for 107 * @param nlen number of bytes in @a needle 108 * @return pointer into @a hay, NULL if @a needle does not occur 109 */ 110 static const char * 111 mem_find (const char *hay, 112 size_t hlen, 113 const char *needle, 114 size_t nlen) 115 { 116 if ( (0 == nlen) || 117 (nlen > hlen) ) 118 return NULL; 119 for (size_t i = 0; i + nlen <= hlen; i++) 120 if (0 == memcmp (&hay[i], 121 needle, 122 nlen)) 123 return &hay[i]; 124 return NULL; 125 } 126 127 128 /** 129 * Is @a c a character that may appear in an XML name? 130 * 131 * @param c character to test 132 * @return 1 if @a c may be part of a name 133 */ 134 static int 135 is_name_char (char c) 136 { 137 return ( ( ('a' <= c) && ('z' >= c) ) || 138 ( ('A' <= c) && ('Z' >= c) ) || 139 ( ('0' <= c) && ('9' >= c) ) || 140 ('_' == c) || ('-' == c) || ('.' == c) || (':' == c) ); 141 } 142 143 144 /** 145 * Is @a c XML white space? 146 * 147 * @param c character to test 148 * @return 1 if @a c is white space 149 */ 150 static int 151 is_space (char c) 152 { 153 return (' ' == c) || ('\t' == c) || ('\r' == c) || ('\n' == c); 154 } 155 156 157 /** 158 * Append @a cp to @a out as UTF-8. 159 * 160 * @param out output buffer 161 * @param outsz number of bytes available in @a out 162 * @param pos current write position 163 * @param cp code point to append 164 * @return new write position, unchanged if @a cp did not fit 165 */ 166 static size_t 167 utf8_put (char *out, 168 size_t outsz, 169 size_t pos, 170 uint32_t cp) 171 { 172 if ( (cp > 0x10FFFF) || 173 ( (0xD800 <= cp) && (0xDFFF >= cp) ) ) 174 return pos; 175 if (cp < 0x80) 176 { 177 if (pos + 1 > outsz) 178 return pos; 179 out[pos++] = (char) cp; 180 } 181 else if (cp < 0x800) 182 { 183 if (pos + 2 > outsz) 184 return pos; 185 out[pos++] = (char) (0xC0 | (cp >> 6)); 186 out[pos++] = (char) (0x80 | (cp & 0x3F)); 187 } 188 else if (cp < 0x10000) 189 { 190 if (pos + 3 > outsz) 191 return pos; 192 out[pos++] = (char) (0xE0 | (cp >> 12)); 193 out[pos++] = (char) (0x80 | ((cp >> 6) & 0x3F)); 194 out[pos++] = (char) (0x80 | (cp & 0x3F)); 195 } 196 else 197 { 198 if (pos + 4 > outsz) 199 return pos; 200 out[pos++] = (char) (0xF0 | (cp >> 18)); 201 out[pos++] = (char) (0x80 | ((cp >> 12) & 0x3F)); 202 out[pos++] = (char) (0x80 | ((cp >> 6) & 0x3F)); 203 out[pos++] = (char) (0x80 | (cp & 0x3F)); 204 } 205 return pos; 206 } 207 208 209 /** 210 * Copy XML character data, resolving the five predefined entities and 211 * numeric character references and collapsing runs of white space. 212 * 213 * The OPF fields we read are laid out across indented lines, so without 214 * the white space collapsing every value would come back with the 215 * source file's line breaks embedded in it. 216 * 217 * @param data the character data 218 * @param len number of bytes in @a data 219 * @param[out] out where to write the result, NUL-terminated 220 * @param outsz number of bytes available in @a out, at least 2 221 * @return number of bytes written, not counting the NUL 222 */ 223 static size_t 224 xml_text (const char *data, 225 size_t len, 226 char *out, 227 size_t outsz) 228 { 229 size_t o = 0; 230 size_t i = 0; 231 int pending_space = 0; 232 233 while ( (i < len) && 234 (o + 1 < outsz) ) 235 { 236 if (is_space (data[i])) 237 { 238 if (0 != o) 239 pending_space = 1; 240 i++; 241 continue; 242 } 243 if (pending_space) 244 { 245 out[o++] = ' '; 246 pending_space = 0; 247 if (o + 1 >= outsz) 248 break; 249 } 250 if ('&' != data[i]) 251 { 252 out[o++] = data[i++]; 253 continue; 254 } 255 { 256 const char *semi = memchr (&data[i], 257 ';', 258 len - i); 259 size_t elen; 260 261 if ( (NULL == semi) || 262 ( (size_t) (semi - &data[i]) > 12) ) 263 { 264 out[o++] = data[i++]; /* a bare '&', which is not legal XML 265 but does occur; pass it through */ 266 continue; 267 } 268 elen = (size_t) (semi - &data[i]) - 1; /* between '&' and ';' */ 269 if ( (3 == elen) && (0 == memcmp (&data[i + 1], "amp", 3)) ) 270 out[o++] = '&'; 271 else if ( (2 == elen) && (0 == memcmp (&data[i + 1], "lt", 2)) ) 272 out[o++] = '<'; 273 else if ( (2 == elen) && (0 == memcmp (&data[i + 1], "gt", 2)) ) 274 out[o++] = '>'; 275 else if ( (4 == elen) && (0 == memcmp (&data[i + 1], "quot", 4)) ) 276 out[o++] = '"'; 277 else if ( (4 == elen) && (0 == memcmp (&data[i + 1], "apos", 4)) ) 278 out[o++] = '\''; 279 else if ( (2 <= elen) && ('#' == data[i + 1]) ) 280 { 281 uint32_t cp = 0; 282 size_t k = i + 2; 283 int base = 10; 284 285 if ( ('x' == data[k]) || ('X' == data[k]) ) 286 { 287 base = 16; 288 k++; 289 } 290 for (; k < (size_t) (semi - data); k++) 291 { 292 unsigned int d; 293 294 if ( ('0' <= data[k]) && ('9' >= data[k]) ) 295 d = (unsigned int) (data[k] - '0'); 296 else if ( (16 == base) && ('a' <= data[k]) && ('f' >= data[k]) ) 297 d = (unsigned int) (data[k] - 'a') + 10; 298 else if ( (16 == base) && ('A' <= data[k]) && ('F' >= data[k]) ) 299 d = (unsigned int) (data[k] - 'A') + 10; 300 else 301 { 302 cp = 0; 303 break; 304 } 305 if (cp > 0x10FFFF) 306 { 307 cp = 0; 308 break; 309 } 310 cp = cp * (uint32_t) base + d; 311 } 312 o = utf8_put (out, 313 outsz - 1, 314 o, 315 cp); 316 } 317 else 318 { 319 /* an entity we do not know; drop it rather than guess */ 320 } 321 i += elen + 2; 322 } 323 } 324 out[o] = '\0'; 325 return o; 326 } 327 328 329 /** 330 * Read the value of attribute @a name out of the body of a start tag. 331 * A namespace prefix on the attribute is ignored, so a request for 332 * `role' also matches `opf:role'. 333 * 334 * @param data the start tag body, after the element name 335 * @param len number of bytes in @a data 336 * @param name local name of the attribute 337 * @param[out] out where to write the value, NUL-terminated 338 * @param outsz number of bytes available in @a out, at least 2 339 * @return 1 if the attribute was found, 0 if not 340 */ 341 static int 342 xml_attr (const char *data, 343 size_t len, 344 const char *name, 345 char *out, 346 size_t outsz) 347 { 348 size_t nlen = strlen (name); 349 350 out[0] = '\0'; 351 if (nlen >= len) 352 return 0; 353 for (size_t i = 0; i + nlen < len; i++) 354 { 355 size_t k; 356 char quote; 357 const char *vend; 358 359 if (0 != memcmp (&data[i], 360 name, 361 nlen)) 362 continue; 363 /* the name must start where an attribute may start: right after 364 white space, or right after a namespace prefix that does */ 365 if (0 == i) 366 continue; 367 if (is_space (data[i - 1])) 368 { 369 /* fine */ 370 } 371 else if (':' == data[i - 1]) 372 { 373 size_t j = i - 1; 374 375 while ( (0 < j) && 376 is_name_char (data[j - 1]) && 377 (':' != data[j - 1]) ) 378 j--; 379 if ( (0 == j) || 380 (! is_space (data[j - 1])) ) 381 continue; 382 } 383 else 384 { 385 continue; 386 } 387 k = i + nlen; 388 while ( (k < len) && 389 is_space (data[k]) ) 390 k++; 391 if ( (k >= len) || 392 ('=' != data[k]) ) 393 continue; 394 k++; 395 while ( (k < len) && 396 is_space (data[k]) ) 397 k++; 398 if ( (k >= len) || 399 ( ('"' != data[k]) && ('\'' != data[k]) ) ) 400 continue; 401 quote = data[k]; 402 k++; 403 vend = memchr (&data[k], 404 quote, 405 len - k); 406 if (NULL == vend) 407 return 0; 408 (void) xml_text (&data[k], 409 (size_t) (vend - &data[k]), 410 out, 411 outsz); 412 return 1; 413 } 414 return 0; 415 } 416 417 418 /** 419 * Does @a s, after an optional `urn:isbn:' prefix, look like an ISBN? 420 * 421 * @param s NUL-terminated candidate 422 * @return 1 if @a s is 10 or 13 digits with optional separators 423 */ 424 static int 425 looks_like_isbn (const char *s) 426 { 427 size_t digits = 0; 428 429 if (0 == strncasecmp (s, 430 "urn:isbn:", 431 strlen ("urn:isbn:"))) 432 s += strlen ("urn:isbn:"); 433 else if (0 == strncasecmp (s, 434 "isbn:", 435 strlen ("isbn:"))) 436 s += strlen ("isbn:"); 437 for (size_t i = 0; '\0' != s[i]; i++) 438 { 439 if ( ('-' == s[i]) || (' ' == s[i]) ) 440 continue; 441 if ( ('0' <= s[i]) && ('9' >= s[i]) ) 442 { 443 digits++; 444 continue; 445 } 446 if ( ( ('X' == s[i]) || ('x' == s[i]) ) && 447 (9 == digits) && 448 ('\0' == s[i + 1]) ) 449 { 450 digits++; 451 continue; 452 } 453 return 0; 454 } 455 return (10 == digits) || (13 == digits); 456 } 457 458 459 /** 460 * Read the current member of a ZIP archive. 461 * 462 * @param uf the archive, positioned on the member 463 * @param cap never read more than this many bytes 464 * @param[out] len number of bytes read 465 * @return the member's contents with a NUL appended, NULL on error; 466 * caller must free 467 */ 468 static char * 469 read_current (struct EXTRACTOR_UnzipFile *uf, 470 size_t cap, 471 size_t *len) 472 { 473 struct EXTRACTOR_UnzipFileInfo fi; 474 char *buf; 475 size_t size; 476 size_t got = 0; 477 478 *len = 0; 479 if (EXTRACTOR_UNZIP_OK != 480 EXTRACTOR_common_unzip_get_current_file_info (uf, 481 &fi, 482 NULL, 0, 483 NULL, 0, 484 NULL, 0)) 485 return NULL; 486 size = (size_t) fi.uncompressed_size; 487 if (0 == size) 488 return NULL; 489 if (size > cap) 490 size = cap; 491 if (NULL == (buf = malloc (size + 1))) 492 return NULL; 493 if (EXTRACTOR_UNZIP_OK != 494 EXTRACTOR_common_unzip_open_current_file (uf)) 495 { 496 free (buf); 497 return NULL; 498 } 499 while (got < size) 500 { 501 ssize_t ret; 502 503 ret = EXTRACTOR_common_unzip_read_current_file (uf, 504 &buf[got], 505 size - got); 506 if (0 >= ret) 507 break; /* error or end of member */ 508 if (((size_t) ret) > size - got) 509 break; /* cannot happen, but do not overrun if it does */ 510 got += (size_t) ret; 511 } 512 (void) EXTRACTOR_common_unzip_close_current_file (uf); 513 if (0 == got) 514 { 515 free (buf); 516 return NULL; 517 } 518 buf[got] = '\0'; 519 *len = got; 520 return buf; 521 } 522 523 524 /** 525 * Read a whole member out of a ZIP archive. 526 * 527 * @param uf the archive 528 * @param name member to read 529 * @param cap never read more than this many bytes 530 * @param[out] len number of bytes read 531 * @return the member's contents with a NUL appended, NULL on error; 532 * caller must free 533 */ 534 static char * 535 read_member (struct EXTRACTOR_UnzipFile *uf, 536 const char *name, 537 size_t cap, 538 size_t *len) 539 { 540 *len = 0; 541 if (EXTRACTOR_UNZIP_OK != 542 EXTRACTOR_common_unzip_go_find_local_file (uf, 543 name, 544 2)) 545 return NULL; 546 return read_current (uf, 547 cap, 548 len); 549 } 550 551 552 /** 553 * Does the archive contain a member with this name? 554 * 555 * @param uf the archive 556 * @param name member to look for 557 * @return 1 if present 558 */ 559 static int 560 has_member (struct EXTRACTOR_UnzipFile *uf, 561 const char *name) 562 { 563 return (EXTRACTOR_UNZIP_OK == 564 EXTRACTOR_common_unzip_go_find_local_file (uf, 565 name, 566 2)); 567 } 568 569 570 /** 571 * Walk the `<metadata>' element of an OPF package document and report 572 * what it holds. 573 * 574 * This is a bounded text scan, not an XML parser: the OPF is untrusted 575 * input and everything we want out of it is a flat list of leaf 576 * elements, so pulling in a parser would buy nothing but attack surface. 577 * 578 * @param ec extraction context 579 * @param opf the package document 580 * @param len number of bytes in @a opf 581 * @return 1 if the caller should stop extracting, 0 to continue 582 */ 583 static int 584 parse_opf_metadata (struct EXTRACTOR_ExtractContext *ec, 585 const char *opf, 586 size_t len) 587 { 588 char value[EXTRACTOR_FORENSIC_MAX_STRING]; 589 char attr[256]; 590 const char *mstart; 591 const char *mend; 592 const char *p; 593 unsigned int elements = 0; 594 /* the repeating elements are capped separately: a file with ten 595 thousand <dc:subject> entries is characterised well enough by the 596 first few dozen */ 597 unsigned int subjects = 0; 598 unsigned int identifiers = 0; 599 unsigned int people = 0; 600 601 mstart = mem_find (opf, 602 len, 603 "<metadata", 604 strlen ("<metadata")); 605 if (NULL == mstart) 606 return 0; 607 mend = mem_find (mstart, 608 len - (size_t) (mstart - opf), 609 "</metadata", 610 strlen ("</metadata")); 611 if (NULL == mend) 612 mend = opf + len; 613 p = mstart; 614 while ( (p < mend) && 615 (elements++ < MAX_ELEMENTS) ) 616 { 617 const char *name; 618 const char *local; 619 const char *body; 620 const char *text; 621 size_t name_len; 622 size_t body_len; 623 size_t text_len; 624 int self_closing; 625 char quote = '\0'; 626 enum EXTRACTOR_MetaType type; 627 628 p = memchr (p, 629 '<', 630 (size_t) (mend - p)); 631 if (NULL == p) 632 break; 633 p++; 634 if (p >= mend) 635 break; 636 if ( ('/' == *p) || ('?' == *p) || ('!' == *p) ) 637 continue; 638 name = p; 639 while ( (p < mend) && 640 is_name_char (*p) ) 641 p++; 642 name_len = (size_t) (p - name); 643 if (0 == name_len) 644 continue; 645 body = p; 646 while (p < mend) 647 { 648 if ('\0' != quote) 649 { 650 if (*p == quote) 651 quote = '\0'; 652 } 653 else if ( ('"' == *p) || ('\'' == *p) ) 654 { 655 quote = *p; 656 } 657 else if ('>' == *p) 658 { 659 break; 660 } 661 p++; 662 } 663 if (p >= mend) 664 break; 665 body_len = (size_t) (p - body); 666 self_closing = ( (0 < body_len) && ('/' == p[-1]) ); 667 if (self_closing) 668 body_len--; 669 p++; /* step past '>' */ 670 text = p; 671 text_len = 0; 672 if (! self_closing) 673 { 674 const char *e = memchr (p, 675 '<', 676 (size_t) (mend - p)); 677 678 text_len = (NULL == e) 679 ? (size_t) (mend - p) 680 : (size_t) (e - p); 681 } 682 /* strip the namespace prefix; `dc:title' and `title' are the same 683 element for our purposes because we are already inside 684 <metadata> */ 685 local = name; 686 for (size_t i = 0; i < name_len; i++) 687 if (':' == name[i]) 688 local = &name[i + 1]; 689 name_len -= (size_t) (local - name); 690 691 #define IS(s) ( (strlen (s) == name_len) && \ 692 (0 == memcmp (local, s, name_len)) ) 693 694 if (IS ("meta")) 695 { 696 if ( (xml_attr (body, 697 body_len, 698 "name", 699 attr, 700 sizeof (attr))) && 701 (xml_attr (body, 702 body_len, 703 "content", 704 value, 705 sizeof (value))) ) 706 { 707 if (0 == strcasecmp (attr, 708 "calibre:timestamp")) 709 type = EXTRACTOR_METATYPE_CREATION_DATE; 710 else if ( (0 == strcasecmp (attr, 711 "generator")) || 712 (0 == strcasecmp (attr, 713 "calibre:generator")) ) 714 type = EXTRACTOR_METATYPE_CREATED_BY_SOFTWARE; 715 else 716 continue; 717 if (0 != 718 EXTRACTOR_forensic_emit_text_ (ec, 719 PLUGIN_NAME, 720 type, 721 value, 722 strlen (value))) 723 return 1; 724 } 725 else if (xml_attr (body, 726 body_len, 727 "property", 728 attr, 729 sizeof (attr))) 730 { 731 if (0 != strcasecmp (attr, 732 "dcterms:modified")) 733 continue; 734 (void) xml_text (text, 735 text_len, 736 value, 737 sizeof (value)); 738 if (0 != 739 EXTRACTOR_forensic_emit_text_ ( 740 ec, 741 PLUGIN_NAME, 742 EXTRACTOR_METATYPE_MODIFICATION_DATE, 743 value, 744 strlen (value))) 745 return 1; 746 } 747 continue; 748 } 749 if (0 == text_len) 750 continue; 751 (void) xml_text (text, 752 text_len, 753 value, 754 sizeof (value)); 755 if ('\0' == value[0]) 756 continue; 757 if (IS ("title")) 758 type = EXTRACTOR_METATYPE_TITLE; 759 else if (IS ("creator")) 760 { 761 if (people++ >= EXTRACTOR_FORENSIC_MAX_ITEMS) 762 continue; 763 type = EXTRACTOR_METATYPE_AUTHOR_NAME; 764 } 765 else if (IS ("publisher")) 766 type = EXTRACTOR_METATYPE_PUBLISHER; 767 else if (IS ("language")) 768 type = EXTRACTOR_METATYPE_LANGUAGE; 769 else if (IS ("date")) 770 type = EXTRACTOR_METATYPE_PUBLICATION_DATE; 771 else if (IS ("subject")) 772 { 773 if (subjects++ >= EXTRACTOR_FORENSIC_MAX_ITEMS) 774 continue; 775 type = EXTRACTOR_METATYPE_KEYWORDS; 776 } 777 else if (IS ("description")) 778 type = EXTRACTOR_METATYPE_DESCRIPTION; 779 else if (IS ("rights")) 780 type = EXTRACTOR_METATYPE_RIGHTS; 781 else if (IS ("source")) 782 type = EXTRACTOR_METATYPE_URI; 783 else if (IS ("contributor")) 784 { 785 /* `bkp' is the MARC relator for the agent that made the file -- 786 which is the tool, and thus the provenance we are after */ 787 if ( (xml_attr (body, 788 body_len, 789 "role", 790 attr, 791 sizeof (attr))) && 792 (0 == strcasecmp (attr, 793 "bkp")) ) 794 { 795 type = EXTRACTOR_METATYPE_CREATED_BY_SOFTWARE; 796 } 797 else 798 { 799 if (people++ >= EXTRACTOR_FORENSIC_MAX_ITEMS) 800 continue; 801 type = EXTRACTOR_METATYPE_CONTRIBUTOR_NAME; 802 } 803 } 804 else if (IS ("identifier")) 805 { 806 int isbn = 0; 807 808 if (identifiers++ >= EXTRACTOR_FORENSIC_MAX_ITEMS) 809 continue; 810 811 if (xml_attr (body, 812 body_len, 813 "scheme", 814 attr, 815 sizeof (attr))) 816 isbn = (0 == strcasecmp (attr, 817 "ISBN")); 818 if (! isbn) 819 isbn = looks_like_isbn (value); 820 type = isbn 821 ? EXTRACTOR_METATYPE_ISBN 822 : EXTRACTOR_METATYPE_URI; 823 } 824 else 825 { 826 continue; 827 } 828 if (0 != 829 EXTRACTOR_forensic_emit_text_ (ec, 830 PLUGIN_NAME, 831 type, 832 value, 833 strlen (value))) 834 return 1; 835 } 836 #undef IS 837 return 0; 838 } 839 840 841 /* ------------------------------------------------------------------ */ 842 /* Full text: character sets */ 843 /* ------------------------------------------------------------------ */ 844 845 /** 846 * Never inflate more than this from one EPUB content document. 847 */ 848 #define MAX_CHAPTER (8 * 1024 * 1024) 849 850 /** 851 * Never inflate more than this from all EPUB content documents 852 * together, nor decompress more than this from the text records of a 853 * MOBI file. The full text itself is capped at #EXTRACTOR_FULLTEXT_MAX, 854 * and markup rarely more than triples the size of the text. 855 */ 856 #define MAX_BOOK_MARKUP (64 * 1024 * 1024) 857 858 /** 859 * Upper bound on the number of manifest items we keep. 860 */ 861 #define MAX_MANIFEST 4096 862 863 /** 864 * Upper bound on the number of spine entries we follow. 865 */ 866 #define MAX_SPINE 4096 867 868 /** 869 * Upper bound on the size of one MOBI text record (normally 4096 bytes 870 * of text plus a few trailing bytes). 871 */ 872 #define MAX_TEXT_RECORD (64 * 1024) 873 874 /** 875 * Upper bound on what one MOBI text record may decompress to. 876 */ 877 #define MAX_TEXT_RECORD_OUT (64 * 1024) 878 879 /** 880 * How many attributes of one element we look at. 881 */ 882 #define MAX_ATTRS 64 883 884 885 /** 886 * Code points of windows-1252 bytes 0x80 to 0x9F. 887 */ 888 static const uint16_t cp1252[32] = { 889 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021, 890 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F, 891 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 892 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178 893 }; 894 895 896 /** 897 * Convert windows-1252 (a superset of ISO-8859-1 in the printable 898 * range) to UTF-8. 899 * 900 * @param data input 901 * @param len number of bytes in @a data 902 * @param[out] olen number of bytes in the result 903 * @return converted text, NULL on allocation failure; caller frees 904 */ 905 static char * 906 cp1252_to_utf8 (const char *data, 907 size_t len, 908 size_t *olen) 909 { 910 char *out; 911 size_t o = 0; 912 913 if (NULL == (out = malloc (3 * len + 1))) 914 return NULL; 915 for (size_t i = 0; i < len; i++) 916 { 917 unsigned char c = (unsigned char) data[i]; 918 919 if (c < 0x80) 920 out[o++] = (char) c; 921 else if (c < 0xA0) 922 o = utf8_put (out, 3 * len, o, cp1252[c - 0x80]); 923 else 924 o = utf8_put (out, 3 * len, o, c); 925 } 926 *olen = o; 927 return out; 928 } 929 930 931 /** 932 * Convert UTF-16 to UTF-8. Unpaired surrogates become U+FFFD. 933 * 934 * @param data input 935 * @param len number of bytes in @a data 936 * @param big_endian 1 for UTF-16BE, 0 for UTF-16LE 937 * @param[out] olen number of bytes in the result 938 * @return converted text, NULL on allocation failure; caller frees 939 */ 940 static char * 941 utf16_to_utf8 (const char *data, 942 size_t len, 943 int big_endian, 944 size_t *olen) 945 { 946 const unsigned char *u = (const unsigned char *) data; 947 char *out; 948 size_t o = 0; 949 size_t cap = 2 * len + 4; /* 2 bytes in never make more than 3 out */ 950 951 if (NULL == (out = malloc (cap))) 952 return NULL; 953 for (size_t i = 0; i + 1 < len; i += 2) 954 { 955 uint32_t cp = big_endian 956 ? (uint32_t) ((u[i] << 8) | u[i + 1]) 957 : (uint32_t) ((u[i + 1] << 8) | u[i]); 958 959 if ( (cp >= 0xD800) && (cp <= 0xDBFF) && (i + 3 < len) ) 960 { 961 uint32_t lo = big_endian 962 ? (uint32_t) ((u[i + 2] << 8) | u[i + 3]) 963 : (uint32_t) ((u[i + 3] << 8) | u[i + 2]); 964 965 if ( (lo >= 0xDC00) && (lo <= 0xDFFF) ) 966 { 967 cp = 0x10000 + ((cp - 0xD800) << 10) + (lo - 0xDC00); 968 i += 2; 969 } 970 } 971 if ( (cp >= 0xD800) && (cp <= 0xDFFF) ) 972 cp = 0xFFFD; 973 o = utf8_put (out, cap, o, cp); 974 } 975 *olen = o; 976 return out; 977 } 978 979 980 /** 981 * Convert @a data from the named character set to UTF-8. UTF-8 input 982 * is copied as is (invalid sequences are dealt with when the text is 983 * appended to the full text); UTF-16 and windows-1252 are converted 984 * here, anything else by iconv if we have it. 985 * 986 * @param data input 987 * @param len number of bytes in @a data 988 * @param cs lower-case charset name 989 * @param[out] olen number of bytes in the result 990 * @return converted text, NULL on allocation failure; caller frees 991 */ 992 static char * 993 convert_to_utf8 (const char *data, 994 size_t len, 995 const char *cs, 996 size_t *olen) 997 { 998 char *out; 999 1000 *olen = 0; 1001 if ( (0 == strcmp (cs, "iso-8859-1")) || 1002 (0 == strcmp (cs, "iso8859-1")) || 1003 (0 == strcmp (cs, "latin1")) || 1004 (0 == strcmp (cs, "us-ascii")) || 1005 (0 == strcmp (cs, "ascii")) || 1006 (0 == strcmp (cs, "cp1252")) || 1007 (0 == strcmp (cs, "windows-1252")) ) 1008 return cp1252_to_utf8 (data, len, olen); 1009 if ( (0 == strcmp (cs, "utf-16be")) || 1010 (0 == strcmp (cs, "utf-16")) ) 1011 return utf16_to_utf8 (data, len, 1, olen); 1012 if (0 == strcmp (cs, "utf-16le")) 1013 return utf16_to_utf8 (data, len, 0, olen); 1014 #if HAVE_ICONV 1015 if ( ('\0' != cs[0]) && 1016 (0 != strcmp (cs, "utf-8")) && 1017 (0 != strcmp (cs, "utf8")) ) 1018 { 1019 iconv_t cd = iconv_open ("UTF-8", cs); 1020 1021 if ((iconv_t) -1 != cd) 1022 { 1023 char *ib = (char *) data; 1024 size_t il = len; 1025 char *ob; 1026 size_t ol; 1027 1028 /* no charset produces more than 4 bytes of UTF-8 per byte */ 1029 if (NULL == (out = malloc (4 * len + 4))) 1030 { 1031 iconv_close (cd); 1032 return NULL; 1033 } 1034 ob = out; 1035 ol = 4 * len; 1036 while (il > 0) 1037 { 1038 if ((size_t) -1 != 1039 iconv (cd, &ib, &il, &ob, &ol)) 1040 break; 1041 if ( (E2BIG == errno) || 1042 (ol < 3) ) 1043 break; 1044 /* skip one undecodable byte */ 1045 *ob++ = (char) 0xEF; 1046 *ob++ = (char) 0xBF; 1047 *ob++ = (char) 0xBD; 1048 ol -= 3; 1049 ib++; 1050 il--; 1051 } 1052 iconv_close (cd); 1053 *olen = (size_t) (ob - out); 1054 return out; 1055 } 1056 } 1057 #endif 1058 if (NULL == (out = malloc (len + 1))) 1059 return NULL; 1060 memcpy (out, data, len); 1061 *olen = len; 1062 return out; 1063 } 1064 1065 1066 /** 1067 * Read a charset name after `encoding' or `charset' at @a p: optional 1068 * white space, '=', optional white space and quotes, then the name. 1069 * 1070 * @param p where to start 1071 * @param end end of the buffer 1072 * @param[out] cs where to write the lower-case name 1073 * @param cs_size number of bytes available in @a cs 1074 * @return 1 if a name was found 1075 */ 1076 static int 1077 charset_value (const char *p, 1078 const char *end, 1079 char *cs, 1080 size_t cs_size) 1081 { 1082 size_t o = 0; 1083 1084 while ( (p < end) && is_space (*p)) 1085 p++; 1086 if ( (p >= end) || ('=' != *p) ) 1087 return 0; 1088 p++; 1089 while ( (p < end) && 1090 (is_space (*p) || ('"' == *p) || ('\'' == *p) ) ) 1091 p++; 1092 while ( (p < end) && 1093 (o + 1 < cs_size) && 1094 (isalnum ((unsigned char) *p) || 1095 ('-' == *p) || ('_' == *p) || ('.' == *p) || (':' == *p) ) ) 1096 cs[o++] = (char) tolower ((unsigned char) *p++); 1097 cs[o] = '\0'; 1098 return (0 != o); 1099 } 1100 1101 1102 /** 1103 * Convert an EPUB content document to UTF-8: honour a byte order mark, 1104 * UTF-16 without one, and the encoding in the XML declaration or a 1105 * META charset declaration; default to UTF-8. 1106 * 1107 * @param data the document 1108 * @param len number of bytes in @a data 1109 * @param[out] olen number of bytes in the result 1110 * @return the document in UTF-8, NULL on allocation failure 1111 */ 1112 static char * 1113 xhtml_to_utf8 (const char *data, 1114 size_t len, 1115 size_t *olen) 1116 { 1117 const unsigned char *u = (const unsigned char *) data; 1118 size_t scan = (len > 1024) ? 1024 : len; 1119 char cs[64]; 1120 1121 if ( (len >= 3) && (0xEF == u[0]) && (0xBB == u[1]) && (0xBF == u[2]) ) 1122 return convert_to_utf8 (&data[3], len - 3, "utf-8", olen); 1123 if ( (len >= 2) && (0xFE == u[0]) && (0xFF == u[1]) ) 1124 return convert_to_utf8 (&data[2], len - 2, "utf-16be", olen); 1125 if ( (len >= 2) && (0xFF == u[0]) && (0xFE == u[1]) ) 1126 return convert_to_utf8 (&data[2], len - 2, "utf-16le", olen); 1127 if ( (len >= 4) && (0 == u[0]) && ('<' == u[1]) && (0 == u[2]) ) 1128 return convert_to_utf8 (data, len, "utf-16be", olen); 1129 if ( (len >= 4) && ('<' == u[0]) && (0 == u[1]) && (0 == u[3]) ) 1130 return convert_to_utf8 (data, len, "utf-16le", olen); 1131 cs[0] = '\0'; 1132 if ( (scan > 5) && (0 == memcmp (data, "<?xml", 5)) ) 1133 { 1134 const char *gt = memchr (data, '>', scan); 1135 size_t dlen = (NULL == gt) ? scan : (size_t) (gt - data); 1136 const char *e = mem_find (data, dlen, "encoding", strlen ("encoding")); 1137 1138 if (NULL != e) 1139 (void) charset_value (e + strlen ("encoding"), 1140 data + dlen, 1141 cs, 1142 sizeof (cs)); 1143 } 1144 if ('\0' == cs[0]) 1145 { 1146 const char *m = mem_find (data, scan, "charset", strlen ("charset")); 1147 1148 if (NULL != m) 1149 (void) charset_value (m + strlen ("charset"), 1150 data + scan, 1151 cs, 1152 sizeof (cs)); 1153 } 1154 /* a document in UTF-8 that claims UTF-16 cannot be UTF-16: we would 1155 have seen the zero bytes */ 1156 if (0 == strncmp (cs, "utf-16", 6)) 1157 cs[0] = '\0'; 1158 return convert_to_utf8 (data, len, cs, olen); 1159 } 1160 1161 1162 /* ------------------------------------------------------------------ */ 1163 /* Full text: (X)HTML markup */ 1164 /* ------------------------------------------------------------------ */ 1165 1166 /** 1167 * HTML named character references for U+00A0 to U+00FF, in order. 1168 */ 1169 static const char *const latin1_entities[96] = { 1170 "nbsp", "iexcl", "cent", "pound", "curren", "yen", "brvbar", "sect", 1171 "uml", "copy", "ordf", "laquo", "not", "shy", "reg", "macr", 1172 "deg", "plusmn", "sup2", "sup3", "acute", "micro", "para", "middot", 1173 "cedil", "sup1", "ordm", "raquo", "frac14", "frac12", "frac34", "iquest", 1174 "Agrave", "Aacute", "Acirc", "Atilde", "Auml", "Aring", "AElig", "Ccedil", 1175 "Egrave", "Eacute", "Ecirc", "Euml", "Igrave", "Iacute", "Icirc", "Iuml", 1176 "ETH", "Ntilde", "Ograve", "Oacute", "Ocirc", "Otilde", "Ouml", "times", 1177 "Oslash", "Ugrave", "Uacute", "Ucirc", "Uuml", "Yacute", "THORN", "szlig", 1178 "agrave", "aacute", "acirc", "atilde", "auml", "aring", "aelig", "ccedil", 1179 "egrave", "eacute", "ecirc", "euml", "igrave", "iacute", "icirc", "iuml", 1180 "eth", "ntilde", "ograve", "oacute", "ocirc", "otilde", "ouml", "divide", 1181 "oslash", "ugrave", "uacute", "ucirc", "uuml", "yacute", "thorn", "yuml" 1182 }; 1183 1184 1185 /** 1186 * Other HTML named character references common in books. 1187 */ 1188 static const struct 1189 { 1190 /** 1191 * Entity name. 1192 */ 1193 const char *name; 1194 1195 /** 1196 * Code point. 1197 */ 1198 uint32_t cp; 1199 } other_entities[] = { 1200 { "amp", '&' }, { "lt", '<' }, { "gt", '>' }, { "quot", '"' }, 1201 { "apos", '\'' }, { "OElig", 0x152 }, { "oelig", 0x153 }, 1202 { "Scaron", 0x160 }, { "scaron", 0x161 }, { "Yuml", 0x178 }, 1203 { "fnof", 0x192 }, { "circ", 0x2C6 }, { "tilde", 0x2DC }, 1204 { "ensp", 0x2002 }, { "emsp", 0x2003 }, { "thinsp", 0x2009 }, 1205 { "zwnj", 0x200C }, { "zwj", 0x200D }, { "lrm", 0x200E }, 1206 { "rlm", 0x200F }, { "ndash", 0x2013 }, { "mdash", 0x2014 }, 1207 { "lsquo", 0x2018 }, { "rsquo", 0x2019 }, { "sbquo", 0x201A }, 1208 { "ldquo", 0x201C }, { "rdquo", 0x201D }, { "bdquo", 0x201E }, 1209 { "dagger", 0x2020 }, { "Dagger", 0x2021 }, { "bull", 0x2022 }, 1210 { "hellip", 0x2026 }, { "permil", 0x2030 }, { "prime", 0x2032 }, 1211 { "Prime", 0x2033 }, { "lsaquo", 0x2039 }, { "rsaquo", 0x203A }, 1212 { "oline", 0x203E }, { "euro", 0x20AC }, { "trade", 0x2122 }, 1213 { "larr", 0x2190 }, { "rarr", 0x2192 }, { "minus", 0x2212 }, 1214 { NULL, 0 } 1215 }; 1216 1217 1218 /** 1219 * Decode the character reference at @a p (just after the '&'). 1220 * 1221 * @param p text after the '&' 1222 * @param len number of bytes available at @a p 1223 * @param[out] cp the code point 1224 * @return number of bytes consumed after the '&' (including the ';'), 1225 * 0 if this is not a character reference we know 1226 */ 1227 static size_t 1228 decode_entity (const char *p, 1229 size_t len, 1230 uint32_t *cp) 1231 { 1232 size_t n = 0; 1233 1234 if ( (len >= 2) && ('#' == p[0]) ) 1235 { 1236 uint32_t v = 0; 1237 int base = 10; 1238 size_t k = 1; 1239 size_t digits = 0; 1240 1241 if ( ('x' == p[1]) || ('X' == p[1]) ) 1242 { 1243 base = 16; 1244 k = 2; 1245 } 1246 for (; (k < len) && (digits < 8); k++, digits++) 1247 { 1248 unsigned int d; 1249 1250 if ( ('0' <= p[k]) && ('9' >= p[k]) ) 1251 d = (unsigned int) (p[k] - '0'); 1252 else if ( (16 == base) && ('a' <= p[k]) && ('f' >= p[k]) ) 1253 d = (unsigned int) (p[k] - 'a') + 10; 1254 else if ( (16 == base) && ('A' <= p[k]) && ('F' >= p[k]) ) 1255 d = (unsigned int) (p[k] - 'A') + 10; 1256 else 1257 break; 1258 v = v * (uint32_t) base + d; 1259 } 1260 if ( (0 == digits) || 1261 (k >= len) || 1262 (';' != p[k]) ) 1263 return 0; 1264 *cp = (v > 0x10FFFF) ? 0xFFFD : v; 1265 return k + 1; 1266 } 1267 while ( (n < len) && 1268 (n < 10) && 1269 isalnum ((unsigned char) p[n]) ) 1270 n++; 1271 if ( (0 == n) || 1272 (n >= len) || 1273 (';' != p[n]) ) 1274 return 0; 1275 for (unsigned int i = 0; i < 96; i++) 1276 if ( (strlen (latin1_entities[i]) == n) && 1277 (0 == memcmp (latin1_entities[i], p, n)) ) 1278 { 1279 *cp = 0xA0 + i; 1280 return n + 1; 1281 } 1282 for (unsigned int i = 0; NULL != other_entities[i].name; i++) 1283 if ( (strlen (other_entities[i].name) == n) && 1284 (0 == memcmp (other_entities[i].name, p, n)) ) 1285 { 1286 *cp = other_entities[i].cp; 1287 return n + 1; 1288 } 1289 return 0; 1290 } 1291 1292 1293 /** 1294 * State of the conversion of one (X)HTML document to text. 1295 */ 1296 struct MarkupState 1297 { 1298 /** 1299 * Where the text goes. 1300 */ 1301 struct EXTRACTOR_FullText *ft; 1302 1303 /** 1304 * Local name of the element whose content we are skipping, "" if 1305 * none. 1306 */ 1307 char skip_name[32]; 1308 1309 /** 1310 * Nesting level of @e skip_name elements inside the skipped one. 1311 */ 1312 unsigned int skip_nest; 1313 1314 /** 1315 * Number of open sectioning elements (article, section, main, ...); 1316 * a header or footer inside one is not page furniture. 1317 */ 1318 unsigned int sections; 1319 1320 /** 1321 * 1 if the last thing we saw was a <br>, so another one ends the 1322 * paragraph. 1323 */ 1324 int after_br; 1325 }; 1326 1327 1328 /** 1329 * Is @a name (lower case) in the 0-terminated list @a list? 1330 * 1331 * @param name element name 1332 * @param list list of names 1333 * @return 1 if found 1334 */ 1335 static int 1336 name_in (const char *name, 1337 const char *const *list) 1338 { 1339 for (unsigned int i = 0; NULL != list[i]; i++) 1340 if (0 == strcmp (name, list[i])) 1341 return 1; 1342 return 0; 1343 } 1344 1345 1346 /** 1347 * Elements that end a paragraph. 1348 */ 1349 static const char *const block_elements[] = { 1350 "address", "article", "aside", "blockquote", "body", "caption", 1351 "center", "dd", "details", "dialog", "dir", "div", "dl", "dt", 1352 "fieldset", "figcaption", "figure", "footer", "form", "h1", "h2", 1353 "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "html", "legend", 1354 "li", "listing", "main", "menu", "nav", "ol", "p", "pre", "section", 1355 "summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul", 1356 "xmp", "title", "head", "noscript", 1357 /* DTBook and FB2-style structure some EPUBs carry */ 1358 "level", "level1", "level2", "level3", "level4", "level5", "level6", 1359 "hd", "h", "note", "annotation", "epigraph", "poem", "stanza", "v", 1360 NULL 1361 }; 1362 1363 /** 1364 * Elements whose content is never document text. 1365 */ 1366 static const char *const skip_elements[] = { 1367 "head", "title", "script", "style", "noscript", "template", "nav", 1368 "aside", "select", "datalist", "iframe", "object", "applet", "rp", "rt", 1369 "guide", "svg", "math", NULL 1370 }; 1371 1372 /** 1373 * Elements whose content is not markup (the end tag is searched for 1374 * literally). 1375 */ 1376 static const char *const raw_text_elements[] = { 1377 "script", "style", "xmp", NULL 1378 }; 1379 1380 /** 1381 * HTML elements without an end tag; they must never open a skipped 1382 * region. 1383 */ 1384 static const char *const void_elements[] = { 1385 "area", "base", "br", "col", "embed", "hr", "img", "input", "link", 1386 "meta", "param", "source", "track", "wbr", NULL 1387 }; 1388 1389 /** 1390 * Elements that start a sectioning context. 1391 */ 1392 static const char *const sectioning_elements[] = { 1393 "article", "section", "main", "aside", "nav", NULL 1394 }; 1395 1396 1397 /** 1398 * Does a start tag's attribute list mark the element as not part of 1399 * the text: hidden, aria-hidden="true", a hiding inline style, an ARIA 1400 * landmark role for the page margins, or a page break marker 1401 * (epub:type="pagebreak", role="doc-pagebreak")? 1402 * 1403 * @param a attribute text of the start tag (after the name) 1404 * @param len number of bytes in @a a 1405 * @return 1 to skip the element 1406 */ 1407 static int 1408 attrs_skip (const char *a, 1409 size_t len) 1410 { 1411 static const char *const roles[] = { 1412 "navigation", "banner", "contentinfo", "complementary", "search", 1413 "doc-pagebreak", NULL 1414 }; 1415 size_t i = 0; 1416 unsigned int n = 0; 1417 1418 while ( (i < len) && (n++ < MAX_ATTRS) ) 1419 { 1420 const char *name; 1421 size_t nlen; 1422 const char *val = NULL; 1423 size_t vlen = 0; 1424 char lname[32]; 1425 char lval[256]; 1426 1427 while ( (i < len) && (is_space (a[i]) || ('/' == a[i]) ) ) 1428 i++; 1429 name = &a[i]; 1430 while ( (i < len) && (! is_space (a[i])) && 1431 ('=' != a[i]) && ('/' != a[i]) ) 1432 i++; 1433 nlen = (size_t) (&a[i] - name); 1434 if (0 == nlen) 1435 break; 1436 while ( (i < len) && is_space (a[i])) 1437 i++; 1438 if ( (i < len) && ('=' == a[i]) ) 1439 { 1440 i++; 1441 while ( (i < len) && is_space (a[i])) 1442 i++; 1443 if ( (i < len) && ( ('"' == a[i]) || ('\'' == a[i]) ) ) 1444 { 1445 const char *q = memchr (&a[i + 1], a[i], len - i - 1); 1446 1447 val = &a[i + 1]; 1448 vlen = (NULL == q) ? len - i - 1 : (size_t) (q - val); 1449 i += vlen + 2; 1450 } 1451 else 1452 { 1453 val = &a[i]; 1454 while ( (i < len) && (! is_space (a[i])) ) 1455 i++; 1456 vlen = (size_t) (&a[i] - val); 1457 } 1458 } 1459 if (nlen >= sizeof (lname)) 1460 continue; 1461 for (size_t k = 0; k < nlen; k++) 1462 lname[k] = (char) tolower ((unsigned char) name[k]); 1463 lname[nlen] = '\0'; 1464 { 1465 size_t o = 0; 1466 1467 /* lower-case, and drop white space so that `display: none' 1468 matches */ 1469 for (size_t k = 0; (k < vlen) && (o + 1 < sizeof (lval)); k++) 1470 if (! is_space (val[k])) 1471 lval[o++] = (char) tolower ((unsigned char) val[k]); 1472 else if ( (0 != o) && (' ' != lval[o - 1]) ) 1473 lval[o++] = ' '; 1474 lval[o] = '\0'; 1475 } 1476 if (0 == strcmp (lname, "hidden")) 1477 { 1478 /* hidden="until-found" is collapsed content that find-in-page 1479 reveals, i.e. part of the text */ 1480 if (0 != strcmp (lval, "until-found")) 1481 return 1; 1482 continue; 1483 } 1484 if (0 == strcmp (lname, "aria-hidden")) 1485 { 1486 if (0 == strcmp (lval, "true")) 1487 return 1; 1488 continue; 1489 } 1490 if (0 == strcmp (lname, "style")) 1491 { 1492 char css[256]; 1493 size_t o = 0; 1494 1495 for (size_t k = 0; '\0' != lval[k]; k++) 1496 if (' ' != lval[k]) 1497 css[o++] = lval[k]; 1498 css[o] = '\0'; 1499 if ( (NULL != strstr (css, "display:none")) || 1500 (NULL != strstr (css, "visibility:hidden")) ) 1501 return 1; 1502 continue; 1503 } 1504 if ( (0 == strcmp (lname, "role")) || 1505 (0 == strcmp (lname, "epub:type")) ) 1506 { 1507 const char *p = lval; 1508 1509 while ('\0' != *p) 1510 { 1511 size_t tl = strcspn (p, " "); 1512 1513 if ( (tl == strlen ("pagebreak")) && 1514 (0 == memcmp (p, "pagebreak", tl)) ) 1515 return 1; 1516 for (unsigned int r = 0; NULL != roles[r]; r++) 1517 if ( (tl == strlen (roles[r])) && 1518 (0 == memcmp (p, roles[r], tl)) ) 1519 return 1; 1520 p += tl; 1521 while (' ' == *p) 1522 p++; 1523 } 1524 } 1525 } 1526 return 0; 1527 } 1528 1529 1530 /** 1531 * Handle a <br>: one is a space, two in a row end the paragraph. 1532 * 1533 * @param ms conversion state 1534 */ 1535 static void 1536 markup_br (struct MarkupState *ms) 1537 { 1538 if (ms->after_br) 1539 { 1540 EXTRACTOR_fulltext_paragraph_ (ms->ft); 1541 ms->after_br = 0; 1542 } 1543 else 1544 { 1545 EXTRACTOR_fulltext_space_ (ms->ft); 1546 ms->after_br = 1; 1547 } 1548 } 1549 1550 1551 /** 1552 * End the current paragraph. 1553 * 1554 * @param ms conversion state 1555 */ 1556 static void 1557 markup_par (struct MarkupState *ms) 1558 { 1559 EXTRACTOR_fulltext_paragraph_ (ms->ft); 1560 ms->after_br = 0; 1561 } 1562 1563 1564 /** 1565 * Append character data (between tags) to the text. 1566 * 1567 * @param ms conversion state 1568 * @param p the text, may contain character references 1569 * @param len number of bytes at @a p 1570 */ 1571 static void 1572 markup_chars (struct MarkupState *ms, 1573 const char *p, 1574 size_t len) 1575 { 1576 size_t i = 0; 1577 1578 while (i < len) 1579 { 1580 size_t run = i; 1581 uint32_t cp; 1582 size_t used; 1583 1584 while ( (run < len) && ('&' != p[run]) ) 1585 { 1586 if (ms->after_br && (! is_space (p[run])) ) 1587 ms->after_br = 0; 1588 run++; 1589 } 1590 EXTRACTOR_fulltext_append_ (ms->ft, &p[i], run - i); 1591 if (run >= len) 1592 return; 1593 used = decode_entity (&p[run + 1], len - run - 1, &cp); 1594 if (0 == used) 1595 { 1596 EXTRACTOR_fulltext_append_ (ms->ft, "&", 1); 1597 i = run + 1; 1598 ms->after_br = 0; 1599 continue; 1600 } 1601 EXTRACTOR_fulltext_append_cp_ (ms->ft, cp); 1602 if ( (0xA0 != cp) && (' ' != cp) ) 1603 ms->after_br = 0; 1604 i = run + 1 + used; 1605 } 1606 } 1607 1608 1609 /** 1610 * Find the end of the tag starting at @a p, honouring quoted 1611 * attribute values. 1612 * 1613 * @param p start of the tag body (after the name) 1614 * @param end end of the buffer 1615 * @return pointer to the closing '>', or @a end 1616 */ 1617 static const char * 1618 tag_end (const char *p, 1619 const char *end) 1620 { 1621 char quote = '\0'; 1622 1623 for (; p < end; p++) 1624 { 1625 if ('\0' != quote) 1626 { 1627 if (*p == quote) 1628 quote = '\0'; 1629 } 1630 else if ( ('"' == *p) || ('\'' == *p) ) 1631 quote = *p; 1632 else if ('>' == *p) 1633 return p; 1634 } 1635 return end; 1636 } 1637 1638 1639 /** 1640 * Find the case-insensitive end tag `</name' at or after @a p. 1641 * 1642 * @param p where to search from 1643 * @param end end of the buffer 1644 * @param name lower-case element name 1645 * @return pointer to the '<' of the end tag, or @a end 1646 */ 1647 static const char * 1648 find_end_tag (const char *p, 1649 const char *end, 1650 const char *name) 1651 { 1652 size_t nlen = strlen (name); 1653 1654 for (; p + 2 + nlen <= end; p++) 1655 { 1656 if ( ('<' != p[0]) || ('/' != p[1]) ) 1657 continue; 1658 if (0 == strncasecmp (&p[2], name, nlen)) 1659 return p; 1660 } 1661 return end; 1662 } 1663 1664 1665 /** 1666 * Convert one (X)HTML document to text. This is a tolerant scanner, 1667 * not a parser: EPUB content documents are XHTML, but MOBI text is 1668 * HTML 3.2 with unclosed elements, so all we track is which element's 1669 * content we are skipping and whether we are inside a sectioning 1670 * element. Block-level elements end paragraphs, as do MOBI page 1671 * breaks (<mbp:pagebreak/>); one <br> is a space and two end a 1672 * paragraph. Memory and time are linear in @a len; there is no 1673 * recursion. 1674 * 1675 * @param ft where the text goes 1676 * @param data the document in UTF-8 1677 * @param len number of bytes in @a data 1678 */ 1679 static void 1680 markup_to_text (struct EXTRACTOR_FullText *ft, 1681 const char *data, 1682 size_t len) 1683 { 1684 struct MarkupState ms; 1685 const char *p = data; 1686 const char *end = data + len; 1687 1688 memset (&ms, 0, sizeof (ms)); 1689 ms.ft = ft; 1690 while ( (p < end) && (! ft->full) ) 1691 { 1692 const char *lt; 1693 const char *name; 1694 const char *gt; 1695 char local[32]; 1696 size_t nlen; 1697 int closing; 1698 int self_closing; 1699 1700 lt = memchr (p, '<', (size_t) (end - p)); 1701 if (NULL == lt) 1702 lt = end; 1703 if ('\0' == ms.skip_name[0]) 1704 markup_chars (&ms, p, (size_t) (lt - p)); 1705 p = lt; 1706 if (p >= end) 1707 break; 1708 if ( (end - p >= 4) && (0 == memcmp (p, "<!--", 4)) ) 1709 { 1710 const char *c = mem_find (p + 4, (size_t) (end - p - 4), "-->", 3); 1711 1712 p = (NULL == c) ? end : c + 3; 1713 continue; 1714 } 1715 if ( (end - p >= 9) && (0 == memcmp (p, "<![CDATA[", 9)) ) 1716 { 1717 const char *c = mem_find (p + 9, (size_t) (end - p - 9), "]]>", 3); 1718 const char *ce = (NULL == c) ? end : c; 1719 1720 if ('\0' == ms.skip_name[0]) 1721 { 1722 EXTRACTOR_fulltext_append_ (ft, p + 9, (size_t) (ce - p - 9)); 1723 ms.after_br = 0; 1724 } 1725 p = (NULL == c) ? end : c + 3; 1726 continue; 1727 } 1728 if ( (end - p >= 2) && ( ('!' == p[1]) || ('?' == p[1]) ) ) 1729 { 1730 gt = memchr (p, '>', (size_t) (end - p)); 1731 p = (NULL == gt) ? end : gt + 1; 1732 continue; 1733 } 1734 closing = ( (end - p >= 2) && ('/' == p[1]) ); 1735 name = p + (closing ? 2 : 1); 1736 nlen = 0; 1737 while ( (name + nlen < end) && 1738 is_name_char (name[nlen]) ) 1739 nlen++; 1740 if ( (0 == nlen) || 1741 (! isalpha ((unsigned char) name[0])) ) 1742 { 1743 /* a stray '<' in the text */ 1744 if ('\0' == ms.skip_name[0]) 1745 markup_chars (&ms, "<", 1); 1746 p++; 1747 continue; 1748 } 1749 gt = tag_end (name + nlen, end); 1750 self_closing = ( (! closing) && (gt > name + nlen) && ('/' == gt[-1]) ); 1751 { 1752 /* the local name, lower case, without namespace prefix */ 1753 const char *l = name; 1754 size_t ll; 1755 1756 for (size_t k = 0; k < nlen; k++) 1757 if (':' == name[k]) 1758 l = &name[k + 1]; 1759 ll = (size_t) (name + nlen - l); 1760 if (ll >= sizeof (local)) 1761 ll = sizeof (local) - 1; 1762 for (size_t k = 0; k < ll; k++) 1763 local[k] = (char) tolower ((unsigned char) l[k]); 1764 local[ll] = '\0'; 1765 } 1766 p = (gt < end) ? gt + 1 : end; 1767 1768 if ( (! closing) && (! self_closing) && 1769 name_in (local, raw_text_elements) ) 1770 { 1771 /* no markup inside; the content is never text, except for the 1772 obsolete <xmp>, which is rare enough to skip too */ 1773 const char *et = find_end_tag (p, end, local); 1774 1775 if (et < end) 1776 et = tag_end (et, end); 1777 p = (et < end) ? et + 1 : end; 1778 if ('\0' == ms.skip_name[0]) 1779 markup_par (&ms); 1780 continue; 1781 } 1782 if ('\0' != ms.skip_name[0]) 1783 { 1784 if (0 != strcmp (local, ms.skip_name)) 1785 continue; 1786 if (closing) 1787 { 1788 if (0 == --ms.skip_nest) 1789 { 1790 ms.skip_name[0] = '\0'; 1791 if (name_in (local, block_elements)) 1792 markup_par (&ms); 1793 } 1794 } 1795 else if (! self_closing) 1796 { 1797 ms.skip_nest++; 1798 } 1799 continue; 1800 } 1801 if (closing) 1802 { 1803 if (name_in (local, sectioning_elements) && (ms.sections > 0)) 1804 ms.sections--; 1805 if (name_in (local, block_elements)) 1806 markup_par (&ms); 1807 continue; 1808 } 1809 if (name_in (local, skip_elements) || 1810 ( ( (0 == strcmp (local, "header")) || 1811 (0 == strcmp (local, "footer")) ) && 1812 (0 == ms.sections) ) || 1813 attrs_skip (name + nlen, (size_t) (gt - name - nlen)) ) 1814 { 1815 if (name_in (local, block_elements)) 1816 markup_par (&ms); 1817 if ( (! self_closing) && 1818 (! name_in (local, void_elements)) ) 1819 { 1820 strcpy (ms.skip_name, local); 1821 ms.skip_nest = 1; 1822 } 1823 continue; 1824 } 1825 if (0 == strcmp (local, "br")) 1826 { 1827 markup_br (&ms); 1828 continue; 1829 } 1830 if ( (0 == strcmp (local, "pagebreak")) || 1831 name_in (local, block_elements) ) 1832 markup_par (&ms); 1833 if ( (! self_closing) && 1834 name_in (local, sectioning_elements) ) 1835 ms.sections++; 1836 } 1837 } 1838 1839 1840 /* ------------------------------------------------------------------ */ 1841 /* Full text: EPUB */ 1842 /* ------------------------------------------------------------------ */ 1843 1844 /** 1845 * One entry of the OPF manifest. 1846 */ 1847 struct ManifestItem 1848 { 1849 /** 1850 * The item's id. 1851 */ 1852 char *id; 1853 1854 /** 1855 * The item's href, relative to the OPF. 1856 */ 1857 char *href; 1858 }; 1859 1860 1861 /** 1862 * Find the next start tag named @a name (not a longer name that begins 1863 * with it) in @a data from offset @a *pos. 1864 * 1865 * @param data buffer to search 1866 * @param len number of bytes in @a data 1867 * @param[in,out] pos where to start; set to just past the tag 1868 * @param name element name including the '<', e.g. "<item" 1869 * @param[out] body start of the attributes 1870 * @param[out] body_len number of bytes of attributes 1871 * @return 1 if found 1872 */ 1873 static int 1874 next_tag (const char *data, 1875 size_t len, 1876 size_t *pos, 1877 const char *name, 1878 const char **body, 1879 size_t *body_len) 1880 { 1881 size_t nlen = strlen (name); 1882 1883 while (*pos < len) 1884 { 1885 const char *t = mem_find (&data[*pos], len - *pos, name, nlen); 1886 const char *gt; 1887 size_t off; 1888 1889 if (NULL == t) 1890 { 1891 *pos = len; 1892 return 0; 1893 } 1894 off = (size_t) (t - data) + nlen; 1895 if ( (off < len) && 1896 is_name_char (data[off]) ) 1897 { 1898 *pos = off; 1899 continue; 1900 } 1901 gt = tag_end (&data[off], data + len); 1902 *body = &data[off]; 1903 *body_len = (size_t) (gt - &data[off]); 1904 *pos = (size_t) (gt - data) + ( (gt < data + len) ? 1 : 0); 1905 return 1; 1906 } 1907 return 0; 1908 } 1909 1910 1911 /** 1912 * Value of a hex digit. 1913 * 1914 * @param c character 1915 * @return value, or -1 if @a c is not a hex digit 1916 */ 1917 static int 1918 hex_val (char c) 1919 { 1920 if ( ('0' <= c) && ('9' >= c) ) 1921 return c - '0'; 1922 if ( ('a' <= c) && ('f' >= c) ) 1923 return c - 'a' + 10; 1924 if ( ('A' <= c) && ('F' >= c) ) 1925 return c - 'A' + 10; 1926 return -1; 1927 } 1928 1929 1930 /** 1931 * Resolve @a href relative to the directory of @a base (a path inside 1932 * the container): drop the fragment, undo percent-encoding and 1933 * normalise `.' and `..' segments. 1934 * 1935 * @param base path of the document @a href appears in 1936 * @param href the reference 1937 * @param[out] out where to write the member name 1938 * @param outsz number of bytes available in @a out 1939 * @return 1 on success, 0 if @a href is not a local path we can use 1940 */ 1941 static int 1942 resolve_href (const char *base, 1943 const char *href, 1944 char *out, 1945 size_t outsz) 1946 { 1947 char tmp[1024]; 1948 size_t t = 0; 1949 size_t o = 0; 1950 const char *slash; 1951 const char *seg; 1952 1953 if ('\0' == href[0]) 1954 return 0; 1955 /* a scheme (http:, data:, ...) means the resource is not in the 1956 container */ 1957 for (size_t i = 0; '\0' != href[i]; i++) 1958 { 1959 if (':' == href[i]) 1960 return 0; 1961 if ( ('/' == href[i]) || ('#' == href[i]) || ('?' == href[i]) ) 1962 break; 1963 } 1964 if ('/' != href[0]) 1965 { 1966 slash = strrchr (base, '/'); 1967 if (NULL != slash) 1968 { 1969 size_t bl = (size_t) (slash - base) + 1; 1970 1971 if (bl >= sizeof (tmp)) 1972 return 0; 1973 memcpy (tmp, base, bl); 1974 t = bl; 1975 } 1976 } 1977 for (size_t i = 0; ('\0' != href[i]) && ('#' != href[i]) && 1978 ('?' != href[i]); i++) 1979 { 1980 char c = href[i]; 1981 1982 if ( ('%' == c) && 1983 (hex_val (href[i + 1]) >= 0) && 1984 (hex_val (href[i + 2]) >= 0) ) 1985 { 1986 c = (char) (hex_val (href[i + 1]) * 16 + hex_val (href[i + 2])); 1987 i += 2; 1988 } 1989 if ('\0' == c) 1990 return 0; 1991 if (t + 1 >= sizeof (tmp)) 1992 return 0; 1993 tmp[t++] = c; 1994 } 1995 tmp[t] = '\0'; 1996 /* normalise the segments into out */ 1997 seg = tmp; 1998 while ('\0' != *seg) 1999 { 2000 size_t sl = strcspn (seg, "/"); 2001 2002 if ( (0 == sl) || 2003 ( (1 == sl) && ('.' == seg[0]) ) ) 2004 { 2005 /* empty or `.' */ 2006 } 2007 else if ( (2 == sl) && ('.' == seg[0]) && ('.' == seg[1]) ) 2008 { 2009 /* `..': drop the last segment of out */ 2010 while ( (o > 0) && ('/' != out[o - 1]) ) 2011 o--; 2012 if (o > 0) 2013 o--; 2014 while ( (o > 0) && ('/' != out[o - 1]) ) 2015 o--; 2016 } 2017 else 2018 { 2019 if (o + sl + 2 > outsz) 2020 return 0; 2021 memcpy (&out[o], seg, sl); 2022 o += sl; 2023 out[o++] = '/'; 2024 } 2025 seg += sl; 2026 if ('/' == *seg) 2027 seg++; 2028 } 2029 if (0 == o) 2030 return 0; 2031 out[o - 1] = '\0'; /* replace the trailing '/' */ 2032 return 1; 2033 } 2034 2035 2036 /** 2037 * Release a manifest. 2038 * 2039 * @param items the manifest 2040 * @param n number of entries in @a items 2041 */ 2042 static void 2043 free_manifest (struct ManifestItem *items, 2044 unsigned int n) 2045 { 2046 for (unsigned int i = 0; i < n; i++) 2047 { 2048 free (items[i].id); 2049 free (items[i].href); 2050 } 2051 free (items); 2052 } 2053 2054 2055 /** 2056 * One content document in the spine. 2057 */ 2058 struct SpineDoc 2059 { 2060 /** 2061 * Member name in the archive. 2062 */ 2063 char *member; 2064 2065 /** 2066 * The document's text (normalized as by #EXTRACTOR_FullText), NULL 2067 * if not (yet) read. Shared between entries for the same member. 2068 */ 2069 char *text; 2070 2071 /** 2072 * Number of bytes in @e text. 2073 */ 2074 size_t text_len; 2075 2076 /** 2077 * 1 if this entry owns @e text. 2078 */ 2079 int owner; 2080 }; 2081 2082 2083 /** 2084 * Index entry for looking up spine documents by member name. 2085 */ 2086 struct SpineKey 2087 { 2088 /** 2089 * Member name (owned by the `struct SpineDoc`). 2090 */ 2091 const char *member; 2092 2093 /** 2094 * Position in the spine. 2095 */ 2096 unsigned int idx; 2097 }; 2098 2099 2100 /** 2101 * Order spine keys by member name (ignoring case, as the archive 2102 * lookup does), then by spine position. 2103 * 2104 * @param a a `struct SpineKey` 2105 * @param b a `struct SpineKey` 2106 * @return comparison result 2107 */ 2108 static int 2109 cmp_spine_key (const void *a, 2110 const void *b) 2111 { 2112 const struct SpineKey *ka = a; 2113 const struct SpineKey *kb = b; 2114 int r = strcasecmp (ka->member, 2115 kb->member); 2116 2117 if (0 != r) 2118 return r; 2119 return (ka->idx < kb->idx) ? -1 : (ka->idx > kb->idx); 2120 } 2121 2122 2123 /** 2124 * Parse the manifest and spine of the OPF: the content documents in 2125 * reading order. 2126 * 2127 * @param opf the package document 2128 * @param opf_len number of bytes in @a opf 2129 * @param opf_path member name of the package document 2130 * @param enc contents of META-INF/encryption.xml, or NULL 2131 * @param enc_len number of bytes in @a enc 2132 * @param[out] docs the spine, caller frees (also the member names) 2133 * @return number of entries in @a docs 2134 */ 2135 static unsigned int 2136 parse_spine (const char *opf, 2137 size_t opf_len, 2138 const char *opf_path, 2139 const char *enc, 2140 size_t enc_len, 2141 struct SpineDoc **docs) 2142 { 2143 struct ManifestItem *items; 2144 struct SpineDoc *d; 2145 unsigned int n_items = 0; 2146 unsigned int n_docs = 0; 2147 unsigned int n_refs = 0; 2148 size_t pos; 2149 const char *body; 2150 size_t body_len; 2151 const char *spine; 2152 const char *spine_end; 2153 2154 *docs = NULL; 2155 spine = mem_find (opf, opf_len, "<spine", strlen ("<spine")); 2156 if (NULL == spine) 2157 return 0; 2158 spine_end = mem_find (spine, 2159 opf_len - (size_t) (spine - opf), 2160 "</spine", 2161 strlen ("</spine")); 2162 if (NULL == spine_end) 2163 spine_end = opf + opf_len; 2164 items = calloc (MAX_MANIFEST, sizeof (struct ManifestItem)); 2165 d = calloc (MAX_SPINE, sizeof (struct SpineDoc)); 2166 if ( (NULL == items) || 2167 (NULL == d) ) 2168 { 2169 free (items); 2170 free (d); 2171 return 0; 2172 } 2173 pos = 0; 2174 while ( (n_items < MAX_MANIFEST) && 2175 next_tag (opf, opf_len, &pos, "<item", &body, &body_len) ) 2176 { 2177 char id[256]; 2178 char href[1024]; 2179 char mt[128]; 2180 2181 if ( (! xml_attr (body, body_len, "id", id, sizeof (id))) || 2182 (! xml_attr (body, body_len, "href", href, sizeof (href))) ) 2183 continue; 2184 if (! xml_attr (body, body_len, "media-type", mt, sizeof (mt))) 2185 mt[0] = '\0'; 2186 if ( (NULL == strstr (mt, "html")) && 2187 (NULL == strstr (mt, "oeb1-document")) && 2188 (NULL == strstr (mt, "dtbook")) ) 2189 continue; /* images, style sheets, fonts, the NCX */ 2190 items[n_items].id = strdup (id); 2191 items[n_items].href = strdup (href); 2192 if ( (NULL == items[n_items].id) || 2193 (NULL == items[n_items].href) ) 2194 { 2195 free (items[n_items].id); 2196 free (items[n_items].href); 2197 break; 2198 } 2199 n_items++; 2200 } 2201 pos = 0; 2202 while ( (n_refs++ < MAX_SPINE) && 2203 next_tag (spine, 2204 (size_t) (spine_end - spine), 2205 &pos, 2206 "<itemref", 2207 &body, 2208 &body_len) ) 2209 { 2210 char idref[256]; 2211 char linear[16]; 2212 char member[1024]; 2213 unsigned int i; 2214 2215 if (! xml_attr (body, body_len, "idref", idref, sizeof (idref))) 2216 continue; 2217 /* linear="no" marks auxiliary content (pop-up notes, answer keys) 2218 that is outside the reading order */ 2219 if ( (xml_attr (body, body_len, "linear", linear, sizeof (linear))) && 2220 (0 == strcmp (linear, "no")) ) 2221 continue; 2222 for (i = 0; i < n_items; i++) 2223 if (0 == strcmp (items[i].id, idref)) 2224 break; 2225 if (i == n_items) 2226 continue; 2227 if (! resolve_href (opf_path, 2228 items[i].href, 2229 member, 2230 sizeof (member))) 2231 continue; 2232 /* content documents that are listed in encryption.xml are DRM 2233 protected; font obfuscation only lists fonts */ 2234 if ( (NULL != enc) && 2235 (NULL != mem_find (enc, enc_len, member, strlen (member))) ) 2236 continue; 2237 if (NULL == (d[n_docs].member = strdup (member))) 2238 break; 2239 n_docs++; 2240 } 2241 free_manifest (items, n_items); 2242 *docs = d; 2243 return n_docs; 2244 } 2245 2246 2247 /** 2248 * Append @a text, which is normalized as by #EXTRACTOR_FullText 2249 * (paragraphs separated by "\n\n"), to @a ft. 2250 * 2251 * @param ft where to append 2252 * @param text the text 2253 * @param len number of bytes in @a text 2254 */ 2255 static void 2256 append_normalized (struct EXTRACTOR_FullText *ft, 2257 const char *text, 2258 size_t len) 2259 { 2260 size_t i = 0; 2261 2262 while ( (i < len) && 2263 (! ft->full) ) 2264 { 2265 const char *nl = memchr (&text[i], '\n', len - i); 2266 size_t pl = (NULL == nl) ? len - i : (size_t) (nl - &text[i]); 2267 2268 EXTRACTOR_fulltext_append_ (ft, &text[i], pl); 2269 EXTRACTOR_fulltext_paragraph_ (ft); 2270 i += pl; 2271 while ( (i < len) && ('\n' == text[i]) ) 2272 i++; 2273 } 2274 } 2275 2276 2277 /** 2278 * Collect the text of an EPUB in reading order (the linear entries of 2279 * the OPF spine) and emit it. 2280 * 2281 * Looking a member up by name walks the central directory, so doing 2282 * that once per spine entry would be quadratic in a hostile archive. 2283 * Instead, the archive is walked once, each content document found in 2284 * the spine is converted to text as it comes along, and the texts are 2285 * put in spine order at the end. 2286 * 2287 * @param ec extraction context 2288 * @param uf the archive 2289 * @param opf the package document 2290 * @param opf_len number of bytes in @a opf 2291 * @param opf_path member name of the package document 2292 * @return 1 if the caller should stop extracting, 0 to continue 2293 */ 2294 static int 2295 epub_fulltext (struct EXTRACTOR_ExtractContext *ec, 2296 struct EXTRACTOR_UnzipFile *uf, 2297 const char *opf, 2298 size_t opf_len, 2299 const char *opf_path) 2300 { 2301 struct EXTRACTOR_FullText ft; 2302 struct SpineDoc *docs; 2303 struct SpineKey *order; 2304 unsigned int n_docs; 2305 size_t budget = MAX_BOOK_MARKUP; 2306 size_t text_total = 0; 2307 size_t enc_len = 0; 2308 char *enc; 2309 int ret; 2310 2311 enc = read_member (uf, 2312 "META-INF/encryption.xml", 2313 MAX_OPF, 2314 &enc_len); 2315 n_docs = parse_spine (opf, 2316 opf_len, 2317 opf_path, 2318 enc, 2319 enc_len, 2320 &docs); 2321 free (enc); 2322 if (0 == n_docs) 2323 { 2324 free (docs); 2325 return 0; 2326 } 2327 if (NULL == (order = malloc (n_docs * sizeof (struct SpineKey)))) 2328 { 2329 for (unsigned int i = 0; i < n_docs; i++) 2330 free (docs[i].member); 2331 free (docs); 2332 return 0; 2333 } 2334 for (unsigned int i = 0; i < n_docs; i++) 2335 { 2336 order[i].member = docs[i].member; 2337 order[i].idx = i; 2338 } 2339 qsort (order, n_docs, sizeof (struct SpineKey), &cmp_spine_key); 2340 2341 if (EXTRACTOR_UNZIP_OK == 2342 EXTRACTOR_common_unzip_go_to_first_file (uf)) 2343 { 2344 do 2345 { 2346 struct EXTRACTOR_UnzipFileInfo fi; 2347 struct EXTRACTOR_FullText cft; 2348 char name[1024]; 2349 unsigned int lo = 0; 2350 unsigned int hi = n_docs; 2351 unsigned int first; 2352 char *doc; 2353 char *utf8; 2354 size_t doc_len; 2355 size_t utf8_len; 2356 2357 if (EXTRACTOR_UNZIP_OK != 2358 EXTRACTOR_common_unzip_get_current_file_info (uf, 2359 &fi, 2360 name, 2361 sizeof (name), 2362 NULL, 0, 2363 NULL, 0)) 2364 break; 2365 name[sizeof (name) - 1] = '\0'; 2366 /* first spine entry for this member (binary search) */ 2367 while (lo < hi) 2368 { 2369 unsigned int mid = lo + (hi - lo) / 2; 2370 2371 if (strcasecmp (order[mid].member, name) < 0) 2372 lo = mid + 1; 2373 else 2374 hi = mid; 2375 } 2376 if ( (lo >= n_docs) || 2377 (0 != strcasecmp (order[lo].member, name)) ) 2378 continue; 2379 first = order[lo].idx; 2380 if (NULL != docs[first].text) 2381 continue; /* the same name twice in the archive */ 2382 doc = read_current (uf, 2383 (budget < MAX_CHAPTER) ? budget : MAX_CHAPTER, 2384 &doc_len); 2385 if (NULL == doc) 2386 continue; 2387 budget -= doc_len; 2388 utf8 = xhtml_to_utf8 (doc, doc_len, &utf8_len); 2389 free (doc); 2390 if (NULL == utf8) 2391 continue; 2392 EXTRACTOR_fulltext_init_ (&cft); 2393 EXTRACTOR_fulltext_set_context_ (&cft, ec); 2394 markup_to_text (&cft, utf8, utf8_len); 2395 free (utf8); 2396 if (0 == cft.len) 2397 { 2398 EXTRACTOR_fulltext_clear_ (&cft); 2399 continue; 2400 } 2401 text_total += cft.len; 2402 /* every spine entry for this member shares the text */ 2403 for (unsigned int k = lo; 2404 (k < n_docs) && 2405 (0 == strcasecmp (order[k].member, name)); 2406 k++) 2407 { 2408 docs[order[k].idx].text = cft.buf; 2409 docs[order[k].idx].text_len = cft.len; 2410 } 2411 docs[first].owner = 1; 2412 } 2413 while ( (budget > 0) && 2414 (text_total < EXTRACTOR_FULLTEXT_MAX) && 2415 (EXTRACTOR_UNZIP_OK == 2416 EXTRACTOR_common_unzip_go_to_next_file (uf)) ); 2417 } 2418 free (order); 2419 2420 EXTRACTOR_fulltext_init_ (&ft); 2421 EXTRACTOR_fulltext_set_context_ (&ft, ec); 2422 for (unsigned int i = 0; (i < n_docs) && (! ft.full); i++) 2423 { 2424 /* each spine entry is a chapter, which always ends a paragraph */ 2425 if (NULL != docs[i].text) 2426 append_normalized (&ft, docs[i].text, docs[i].text_len); 2427 EXTRACTOR_fulltext_paragraph_ (&ft); 2428 } 2429 for (unsigned int i = 0; i < n_docs; i++) 2430 { 2431 if (docs[i].owner) 2432 free (docs[i].text); 2433 free (docs[i].member); 2434 } 2435 free (docs); 2436 ret = EXTRACTOR_fulltext_emit_ (&ft, ec, PLUGIN_NAME); 2437 EXTRACTOR_fulltext_clear_ (&ft); 2438 return (0 != ret) ? 1 : 0; 2439 } 2440 2441 2442 /** 2443 * Handle an EPUB: read `META-INF/container.xml' to find the OPF package 2444 * document, then report what the package document holds. 2445 * 2446 * @param ec extraction context 2447 * @param uf the opened archive 2448 */ 2449 static void 2450 extract_epub (struct EXTRACTOR_ExtractContext *ec, 2451 struct EXTRACTOR_UnzipFile *uf) 2452 { 2453 char path[512]; 2454 char opf_path[512]; 2455 char attr[64]; 2456 char *container; 2457 char *opf = NULL; 2458 size_t container_len; 2459 size_t opf_len = 0; 2460 const char *p; 2461 const char *q; 2462 unsigned int items = 0; 2463 2464 if (0 != 2465 ec->proc (ec->cls, 2466 PLUGIN_NAME, 2467 EXTRACTOR_METATYPE_MIMETYPE, 2468 EXTRACTOR_METAFORMAT_UTF8, 2469 "text/plain", 2470 "application/epub+zip", 2471 strlen ("application/epub+zip") + 1)) 2472 return; 2473 container = read_member (uf, 2474 "META-INF/container.xml", 2475 MAX_CONTAINER, 2476 &container_len); 2477 if (NULL != container) 2478 { 2479 p = mem_find (container, 2480 container_len, 2481 "full-path=", 2482 strlen ("full-path=")); 2483 if (NULL != p) 2484 { 2485 p += strlen ("full-path="); 2486 if ( ('"' == *p) || ('\'' == *p) ) 2487 { 2488 char quote = *p; 2489 2490 p++; 2491 q = memchr (p, 2492 quote, 2493 container_len - (size_t) (p - container)); 2494 if (NULL != q) 2495 (void) xml_text (p, 2496 (size_t) (q - p), 2497 path, 2498 sizeof (path)); 2499 else 2500 path[0] = '\0'; 2501 } 2502 else 2503 { 2504 path[0] = '\0'; 2505 } 2506 if ('\0' != path[0]) 2507 { 2508 opf = read_member (uf, 2509 path, 2510 MAX_OPF, 2511 &opf_len); 2512 if (NULL != opf) 2513 memcpy (opf_path, path, sizeof (opf_path)); 2514 } 2515 } 2516 free (container); 2517 } 2518 if (NULL == opf) 2519 { 2520 /* no usable container.xml; the conventional locations are worth a 2521 try before giving up */ 2522 static const char *guesses[] = { 2523 "OEBPS/content.opf", 2524 "content.opf", 2525 "OPS/content.opf", 2526 NULL 2527 }; 2528 2529 for (unsigned int i = 0; NULL != guesses[i]; i++) 2530 { 2531 opf = read_member (uf, 2532 guesses[i], 2533 MAX_OPF, 2534 &opf_len); 2535 if (NULL != opf) 2536 { 2537 snprintf (opf_path, sizeof (opf_path), "%s", guesses[i]); 2538 break; 2539 } 2540 } 2541 } 2542 if (NULL != opf) 2543 { 2544 /* the <package version="..."> attribute distinguishes EPUB 2 from 2545 EPUB 3, which changes what else we may expect to find */ 2546 p = mem_find (opf, 2547 opf_len, 2548 "<package", 2549 strlen ("<package")); 2550 if (NULL != p) 2551 { 2552 size_t tail = opf_len - (size_t) (p - opf); 2553 const char *gt; 2554 2555 if (tail > 1024) 2556 tail = 1024; 2557 /* stop at the end of the start tag, so that a `version=' further 2558 down the document cannot be mistaken for this one */ 2559 gt = memchr (p, 2560 '>', 2561 tail); 2562 if (NULL != gt) 2563 tail = (size_t) (gt - p); 2564 if (tail <= strlen ("<package")) 2565 tail = strlen ("<package"); 2566 if (xml_attr (p + strlen ("<package"), 2567 tail - strlen ("<package"), 2568 "version", 2569 attr, 2570 sizeof (attr))) 2571 { 2572 if (0 != 2573 EXTRACTOR_forensic_emit_text_ (ec, 2574 PLUGIN_NAME, 2575 EXTRACTOR_METATYPE_FORMAT_VERSION, 2576 attr, 2577 strlen (attr))) 2578 goto CLEANUP; 2579 } 2580 } 2581 if (0 != 2582 parse_opf_metadata (ec, 2583 opf, 2584 opf_len)) 2585 goto CLEANUP; 2586 /* count the manifest entries; `<itemref>' does not match because 2587 the character after `<item' has to be a name delimiter */ 2588 for (size_t i = 0; i + strlen ("<item") < opf_len; i++) 2589 if ( (0 == memcmp (&opf[i], 2590 "<item", 2591 strlen ("<item"))) && 2592 (! is_name_char (opf[i + strlen ("<item")])) ) 2593 items++; 2594 if (0 != items) 2595 { 2596 if (0 != 2597 EXTRACTOR_forensic_emit_ (ec, 2598 PLUGIN_NAME, 2599 EXTRACTOR_METATYPE_ENTRY_COUNT, 2600 "%u", 2601 items)) 2602 goto CLEANUP; 2603 } 2604 } 2605 if (has_member (uf, 2606 "META-INF/encryption.xml")) 2607 { 2608 if (0 != 2609 EXTRACTOR_forensic_emit_ ( 2610 ec, 2611 PLUGIN_NAME, 2612 EXTRACTOR_METATYPE_ENCRYPTION, 2613 "META-INF/encryption.xml present" 2614 " (DRM or font obfuscation)")) 2615 goto CLEANUP; 2616 } 2617 if (has_member (uf, 2618 "META-INF/rights.xml")) 2619 { 2620 if (0 != 2621 EXTRACTOR_forensic_emit_ (ec, 2622 PLUGIN_NAME, 2623 EXTRACTOR_METATYPE_ENCRYPTION, 2624 "META-INF/rights.xml present" 2625 " (Adobe Content Server DRM)")) 2626 goto CLEANUP; 2627 } 2628 if ( (NULL != opf) && 2629 (EXTRACTOR_fulltext_requested_ (ec->config)) ) 2630 (void) epub_fulltext (ec, 2631 uf, 2632 opf, 2633 opf_len, 2634 opf_path); 2635 CLEANUP: 2636 free (opf); 2637 } 2638 2639 2640 /* ------------------------------------------------------------------ */ 2641 /* Full text: MOBI */ 2642 /* ------------------------------------------------------------------ */ 2643 2644 /** 2645 * Size of the trailing entries at the end of a MOBI text record, as 2646 * announced by the "extra data flags" of the MOBI header: bit 0 is the 2647 * multibyte-character overlap (its size is in the low two bits of the 2648 * last byte before the other entries), every other bit is an entry 2649 * whose size is a backward-encoded variable-length integer at its end. 2650 * 2651 * @param d the record 2652 * @param size number of bytes in @a d 2653 * @param flags extra data flags 2654 * @return number of bytes to strip from the end of the record 2655 */ 2656 static size_t 2657 trailing_size (const unsigned char *d, 2658 size_t size, 2659 uint16_t flags) 2660 { 2661 size_t num = 0; 2662 2663 for (unsigned int bit = 1; bit < 16; bit++) 2664 { 2665 size_t end; 2666 uint32_t v = 0; 2667 2668 if (0 == (flags & (1u << bit))) 2669 continue; 2670 end = size - num; 2671 if (0 == end) 2672 return size; 2673 for (size_t k = (end >= 4) ? end - 4 : 0; k < end; k++) 2674 { 2675 if (0 != (d[k] & 0x80)) 2676 v = 0; 2677 v = (v << 7) | (d[k] & 0x7F); 2678 } 2679 if (v > end) 2680 return size; 2681 num += v; 2682 } 2683 if (0 != (flags & 1)) 2684 { 2685 if (num >= size) 2686 return size; 2687 num += (d[size - num - 1] & 0x3) + 1; 2688 } 2689 return (num > size) ? size : num; 2690 } 2691 2692 2693 /** 2694 * Decompress one PalmDOC (LZ77 variant) compressed record. 2695 * 2696 * @param in compressed data 2697 * @param len number of bytes in @a in 2698 * @param[out] out where to write the text 2699 * @param outsz number of bytes available in @a out 2700 * @return number of bytes written 2701 */ 2702 static size_t 2703 palmdoc_decompress (const unsigned char *in, 2704 size_t len, 2705 unsigned char *out, 2706 size_t outsz) 2707 { 2708 size_t i = 0; 2709 size_t o = 0; 2710 2711 while ( (i < len) && (o < outsz) ) 2712 { 2713 unsigned int c = in[i++]; 2714 2715 if ( (0 == c) || 2716 ( (c >= 0x09) && (c <= 0x7F) ) ) 2717 { 2718 out[o++] = (unsigned char) c; 2719 } 2720 else if (c <= 0x08) 2721 { 2722 /* c literal bytes follow */ 2723 for (unsigned int k = 0; (k < c) && (i < len) && (o < outsz); k++) 2724 out[o++] = in[i++]; 2725 } 2726 else if (c <= 0xBF) 2727 { 2728 unsigned int pair; 2729 size_t dist; 2730 unsigned int n; 2731 2732 if (i >= len) 2733 break; 2734 pair = ((c << 8) | in[i++]) & 0x3FFF; 2735 dist = pair >> 3; 2736 n = (pair & 7) + 3; 2737 if ( (0 == dist) || 2738 (dist > o) ) 2739 break; /* corrupt: points before the start */ 2740 for (unsigned int k = 0; (k < n) && (o < outsz); k++, o++) 2741 out[o] = out[o - dist]; 2742 } 2743 else 2744 { 2745 /* a space followed by the character c ^ 0x80 */ 2746 out[o++] = ' '; 2747 if (o < outsz) 2748 out[o++] = (unsigned char) (c ^ 0x80); 2749 } 2750 } 2751 return o; 2752 } 2753 2754 2755 /** 2756 * Collect the text of a MOBI book and emit it. Handles uncompressed 2757 * and PalmDOC-compressed books; HUFF/CDIC compression and encrypted 2758 * books are skipped. 2759 * 2760 * @param ec extraction context 2761 * @param rec record 0 2762 * @param rec_len number of bytes in @a rec (at least 16) 2763 * @param hdr_len length of the MOBI header, 0 if there is none 2764 * @param num_records number of records in the database 2765 * @param size size of the file 2766 */ 2767 static void 2768 mobi_fulltext (struct EXTRACTOR_ExtractContext *ec, 2769 const unsigned char *rec, 2770 size_t rec_len, 2771 uint32_t hdr_len, 2772 uint16_t num_records, 2773 uint64_t size) 2774 { 2775 struct EXTRACTOR_FullText ft; 2776 uint16_t compression = EXTRACTOR_forensic_be16_ (&rec[0]); 2777 uint16_t text_records = EXTRACTOR_forensic_be16_ (&rec[8]); 2778 uint16_t extra_flags = 0; 2779 const char *cs = "windows-1252"; 2780 unsigned char *info = NULL; 2781 unsigned char *in = NULL; 2782 unsigned char *text = NULL; 2783 size_t text_len = 0; 2784 size_t text_cap = 0; 2785 unsigned int n_info; 2786 char *utf8; 2787 size_t utf8_len; 2788 2789 if (0 != EXTRACTOR_forensic_be16_ (&rec[12])) 2790 return; /* encrypted */ 2791 if ( (1 != compression) && 2792 (2 != compression) ) 2793 return; /* HUFF/CDIC (17480) or unknown */ 2794 if (hdr_len >= 28) 2795 { 2796 if (65001 == EXTRACTOR_forensic_be32_ (&rec[16 + 12])) 2797 cs = "utf-8"; 2798 } 2799 /* the extra data flags exist from MOBI header length 0xE4 on */ 2800 if ( (hdr_len >= 0xE4) && 2801 (rec_len >= 16 + 0xE4) ) 2802 extra_flags = EXTRACTOR_forensic_be16_ (&rec[16 + 0xE2]); 2803 if (text_records >= num_records) 2804 text_records = num_records - 1; 2805 if (0 == text_records) 2806 return; 2807 /* offsets of records 0 .. text_records + 1 */ 2808 n_info = (unsigned int) text_records + 2; 2809 if (n_info > num_records) 2810 n_info = num_records; 2811 if (NULL == (info = malloc (8 * (size_t) n_info))) 2812 return; 2813 if (! EXTRACTOR_forensic_read_ (ec, 78, info, 8 * (size_t) n_info)) 2814 goto CLEANUP; 2815 if ( (NULL == (in = malloc (MAX_TEXT_RECORD))) ) 2816 goto CLEANUP; 2817 for (unsigned int r = 1; r <= text_records; r++) 2818 { 2819 uint64_t start = EXTRACTOR_forensic_be32_ (&info[8 * r]); 2820 uint64_t stop = (r + 1 < n_info) 2821 ? EXTRACTOR_forensic_be32_ (&info[8 * (r + 1)]) 2822 : size; 2823 size_t len; 2824 size_t strip; 2825 2826 if ( (stop <= start) || 2827 (stop > size) || 2828 (stop - start > MAX_TEXT_RECORD) ) 2829 continue; 2830 len = (size_t) (stop - start); 2831 if (! EXTRACTOR_forensic_read_ (ec, (int64_t) start, in, len)) 2832 continue; 2833 strip = trailing_size (in, len, extra_flags); 2834 len -= strip; 2835 if (text_len + MAX_TEXT_RECORD_OUT > text_cap) 2836 { 2837 size_t ncap = (0 == text_cap) ? 256 * 1024 : 2 * text_cap; 2838 unsigned char *nt; 2839 2840 if (ncap > MAX_BOOK_MARKUP) 2841 break; 2842 if (NULL == (nt = realloc (text, ncap))) 2843 break; 2844 text = nt; 2845 text_cap = ncap; 2846 } 2847 if (1 == compression) 2848 { 2849 if (len > MAX_TEXT_RECORD_OUT) 2850 len = MAX_TEXT_RECORD_OUT; 2851 memcpy (&text[text_len], in, len); 2852 text_len += len; 2853 } 2854 else 2855 { 2856 text_len += palmdoc_decompress (in, 2857 len, 2858 &text[text_len], 2859 MAX_TEXT_RECORD_OUT); 2860 } 2861 } 2862 if (0 == text_len) 2863 goto CLEANUP; 2864 utf8 = convert_to_utf8 ((const char *) text, text_len, cs, &utf8_len); 2865 if (NULL == utf8) 2866 goto CLEANUP; 2867 free (text); 2868 text = NULL; 2869 EXTRACTOR_fulltext_init_ (&ft); 2870 EXTRACTOR_fulltext_set_context_ (&ft, ec); 2871 markup_to_text (&ft, utf8, utf8_len); 2872 free (utf8); 2873 (void) EXTRACTOR_fulltext_emit_ (&ft, ec, PLUGIN_NAME); 2874 EXTRACTOR_fulltext_clear_ (&ft); 2875 CLEANUP: 2876 free (text); 2877 free (in); 2878 free (info); 2879 } 2880 2881 2882 /** 2883 * Convert a Palm database time stamp. 2884 * 2885 * The format says seconds since 1904, but writers disagree and some use 2886 * the Unix epoch. The two ranges do not overlap for any date between 2887 * 1970 and 2036, so the value itself says which convention was used. 2888 * 2889 * @param raw the 32 bit field 2890 * @return seconds since 1970, 0 if the field was never filled in 2891 */ 2892 static int64_t 2893 palm_time (uint32_t raw) 2894 { 2895 if (0 == raw) 2896 return 0; 2897 if (((int64_t) raw) >= PALM_EPOCH_OFFSET) 2898 return ((int64_t) raw) - PALM_EPOCH_OFFSET; 2899 return (int64_t) raw; 2900 } 2901 2902 2903 /** 2904 * Report one EXTH record. 2905 * 2906 * @param ec extraction context 2907 * @param type EXTH record type 2908 * @param data the record's payload 2909 * @param len number of bytes in @a data 2910 * @param[in,out] have_title set to 1 once a title has been reported 2911 * @return 1 if the caller should stop extracting, 0 to continue 2912 */ 2913 static int 2914 emit_exth (struct EXTRACTOR_ExtractContext *ec, 2915 uint32_t type, 2916 const unsigned char *data, 2917 size_t len, 2918 int *have_title) 2919 { 2920 enum EXTRACTOR_MetaType mt; 2921 2922 switch (type) 2923 { 2924 case 100: mt = EXTRACTOR_METATYPE_AUTHOR_NAME; break; 2925 case 101: mt = EXTRACTOR_METATYPE_PUBLISHER; break; 2926 case 103: mt = EXTRACTOR_METATYPE_DESCRIPTION; break; 2927 case 104: mt = EXTRACTOR_METATYPE_ISBN; break; 2928 case 105: mt = EXTRACTOR_METATYPE_KEYWORDS; break; 2929 case 106: mt = EXTRACTOR_METATYPE_PUBLICATION_DATE; break; 2930 case 108: 2931 /* the tool that produced the file stamps itself here; calibre and 2932 kindlegen both do */ 2933 mt = EXTRACTOR_METATYPE_CREATED_BY_SOFTWARE; 2934 break; 2935 case 109: mt = EXTRACTOR_METATYPE_RIGHTS; break; 2936 case 112: mt = EXTRACTOR_METATYPE_URI; break; 2937 case 113: mt = EXTRACTOR_METATYPE_SERIAL; break; /* ASIN */ 2938 case 208: 2939 /* per-copy marking on files that came from a store */ 2940 mt = EXTRACTOR_METATYPE_WATERMARK; 2941 break; 2942 case 502: mt = EXTRACTOR_METATYPE_MODIFICATION_DATE; break; 2943 case 503: 2944 mt = EXTRACTOR_METATYPE_TITLE; 2945 *have_title = 1; 2946 break; 2947 case 524: mt = EXTRACTOR_METATYPE_LANGUAGE; break; 2948 case 501: 2949 /* `EBOK' for a store book, `PDOC' for a personal document that was 2950 side-loaded or mailed to a device */ 2951 return EXTRACTOR_forensic_emit_ (ec, 2952 PLUGIN_NAME, 2953 EXTRACTOR_METATYPE_ATTRIBUTES, 2954 "cdetype: %.*s", 2955 (int) ((len > 16) ? 16 : len), 2956 (const char *) data); 2957 case 209: 2958 /* "tamper proof keys": binary, and specific to the copy rather than 2959 to the book, so worth reporting even though we cannot decode it */ 2960 return EXTRACTOR_forensic_emit_hex_ (ec, 2961 PLUGIN_NAME, 2962 EXTRACTOR_METATYPE_WATERMARK, 2963 data, 2964 (len > 32) ? 32 : len); 2965 default: 2966 return 0; 2967 } 2968 return EXTRACTOR_forensic_emit_text_ (ec, 2969 PLUGIN_NAME, 2970 mt, 2971 (const char *) data, 2972 len); 2973 } 2974 2975 2976 /** 2977 * Walk the EXTH header of a MOBI file. 2978 * 2979 * @param ec extraction context 2980 * @param rec record 0 2981 * @param rec_len number of bytes in @a rec 2982 * @param off offset of the EXTH header within @a rec 2983 * @param[in,out] have_title set to 1 once a title has been reported 2984 * @return 1 if the caller should stop extracting, 0 to continue 2985 */ 2986 static int 2987 parse_exth (struct EXTRACTOR_ExtractContext *ec, 2988 const unsigned char *rec, 2989 size_t rec_len, 2990 size_t off, 2991 int *have_title) 2992 { 2993 uint32_t count; 2994 uint32_t exth_len; 2995 size_t end; 2996 size_t p; 2997 2998 if ( (off > rec_len) || 2999 (rec_len - off < 12) ) 3000 return 0; 3001 if (0 != memcmp (&rec[off], 3002 "EXTH", 3003 4)) 3004 return 0; 3005 exth_len = EXTRACTOR_forensic_be32_ (&rec[off + 4]); 3006 count = EXTRACTOR_forensic_be32_ (&rec[off + 8]); 3007 end = rec_len; 3008 if ( (exth_len >= 12) && 3009 (exth_len <= rec_len - off) ) 3010 end = off + exth_len; 3011 if (count > MAX_EXTH) 3012 count = MAX_EXTH; 3013 p = off + 12; 3014 for (uint32_t i = 0; i < count; i++) 3015 { 3016 uint32_t type; 3017 uint32_t rlen; 3018 3019 if ( (p + 8 > end) || 3020 (p + 8 < p) ) 3021 break; 3022 type = EXTRACTOR_forensic_be32_ (&rec[p]); 3023 rlen = EXTRACTOR_forensic_be32_ (&rec[p + 4]); 3024 if (rlen < 8) 3025 break; /* no forward progress; a malformed file must not spin */ 3026 if (((size_t) rlen) > end - p) 3027 break; 3028 if (0 != 3029 emit_exth (ec, 3030 type, 3031 &rec[p + 8], 3032 (size_t) rlen - 8, 3033 have_title)) 3034 return 1; 3035 p += rlen; 3036 } 3037 return 0; 3038 } 3039 3040 3041 /** 3042 * Handle a Palm database that identifies itself as a MOBI book. 3043 * 3044 * @param ec extraction context 3045 * @param hdr the 78 byte PDB header 3046 */ 3047 static void 3048 extract_mobi (struct EXTRACTOR_ExtractContext *ec, 3049 const unsigned char *hdr) 3050 { 3051 unsigned char info[16]; 3052 unsigned char *rec = NULL; 3053 uint64_t size; 3054 uint32_t rec0_off; 3055 uint32_t rec1_off; 3056 uint32_t hdr_len = 0; 3057 uint16_t num_records; 3058 size_t rec_len; 3059 int have_title = 0; 3060 int rec_ok = 0; 3061 3062 if (0 != 3063 ec->proc (ec->cls, 3064 PLUGIN_NAME, 3065 EXTRACTOR_METATYPE_MIMETYPE, 3066 EXTRACTOR_METAFORMAT_UTF8, 3067 "text/plain", 3068 "application/x-mobipocket-ebook", 3069 strlen ("application/x-mobipocket-ebook") + 1)) 3070 return; 3071 if (0 != 3072 EXTRACTOR_forensic_emit_unix_time_ (ec, 3073 PLUGIN_NAME, 3074 EXTRACTOR_METATYPE_CREATION_DATE, 3075 palm_time ( 3076 EXTRACTOR_forensic_be32_ ( 3077 &hdr[36])))) 3078 return; 3079 if (0 != 3080 EXTRACTOR_forensic_emit_unix_time_ ( 3081 ec, 3082 PLUGIN_NAME, 3083 EXTRACTOR_METATYPE_MODIFICATION_DATE, 3084 palm_time (EXTRACTOR_forensic_be32_ (&hdr[40])))) 3085 return; 3086 num_records = EXTRACTOR_forensic_be16_ (&hdr[76]); 3087 if (0 != 3088 EXTRACTOR_forensic_emit_ (ec, 3089 PLUGIN_NAME, 3090 EXTRACTOR_METATYPE_ENTRY_COUNT, 3091 "%u", 3092 (unsigned int) num_records)) 3093 return; 3094 size = ec->get_size (ec->cls); 3095 if ( (2 > num_records) || 3096 (UINT64_MAX == size) ) 3097 goto NAME_ONLY; 3098 /* the record info list follows the header; entry i gives the offset 3099 of record i, so record 0 runs up to the start of record 1 */ 3100 if (! EXTRACTOR_forensic_read_ (ec, 3101 78, 3102 info, 3103 16)) 3104 goto NAME_ONLY; 3105 rec0_off = EXTRACTOR_forensic_be32_ (&info[0]); 3106 rec1_off = EXTRACTOR_forensic_be32_ (&info[8]); 3107 if ( (rec1_off <= rec0_off) || 3108 (((uint64_t) rec1_off) > size) ) 3109 goto NAME_ONLY; 3110 rec_len = (size_t) (rec1_off - rec0_off); 3111 if (rec_len > MAX_RECORD0) 3112 rec_len = MAX_RECORD0; 3113 if (rec_len < 16) 3114 goto NAME_ONLY; 3115 if (NULL == (rec = malloc (rec_len))) 3116 goto NAME_ONLY; 3117 if (! EXTRACTOR_forensic_read_ (ec, 3118 (int64_t) rec0_off, 3119 rec, 3120 rec_len)) 3121 goto NAME_ONLY; 3122 rec_ok = 1; 3123 /* PalmDOC header: encryption type at offset 12 */ 3124 switch (EXTRACTOR_forensic_be16_ (&rec[12])) 3125 { 3126 case 0: 3127 break; /* no DRM; saying so would be noise */ 3128 case 1: 3129 if (0 != 3130 EXTRACTOR_forensic_emit_ (ec, 3131 PLUGIN_NAME, 3132 EXTRACTOR_METATYPE_ENCRYPTION, 3133 "old Mobipocket encryption")) 3134 goto CLEANUP; 3135 break; 3136 case 2: 3137 if (0 != 3138 EXTRACTOR_forensic_emit_ (ec, 3139 PLUGIN_NAME, 3140 EXTRACTOR_METATYPE_ENCRYPTION, 3141 "Mobipocket DRM")) 3142 goto CLEANUP; 3143 break; 3144 default: 3145 if (0 != 3146 EXTRACTOR_forensic_emit_ (ec, 3147 PLUGIN_NAME, 3148 EXTRACTOR_METATYPE_ENCRYPTION, 3149 "unknown encryption type %u", 3150 (unsigned int) 3151 EXTRACTOR_forensic_be16_ (&rec[12]))) 3152 goto CLEANUP; 3153 break; 3154 } 3155 if ( (rec_len < 32) || 3156 (0 != memcmp (&rec[16], 3157 "MOBI", 3158 4)) ) 3159 goto NAME_ONLY; /* PalmDOC without a MOBI header */ 3160 hdr_len = EXTRACTOR_forensic_be32_ (&rec[20]); 3161 if ( (hdr_len < 24) || 3162 (((uint64_t) hdr_len) + 16 > rec_len) ) 3163 { 3164 hdr_len = 0; 3165 goto NAME_ONLY; 3166 } 3167 if (hdr_len >= 28) 3168 { 3169 const char *cs = NULL; 3170 3171 switch (EXTRACTOR_forensic_be32_ (&rec[16 + 12])) 3172 { 3173 case 1252: cs = "windows-1252"; break; 3174 case 65001: cs = "UTF-8"; break; 3175 default: break; 3176 } 3177 if ( (NULL != cs) && 3178 (0 != 3179 EXTRACTOR_forensic_emit_ (ec, 3180 PLUGIN_NAME, 3181 EXTRACTOR_METATYPE_CHARACTER_SET, 3182 "%s", 3183 cs)) ) 3184 goto CLEANUP; 3185 } 3186 /* EXTH flags live at MOBI header offset 112; bit 0x40 says that an 3187 EXTH header follows the MOBI header */ 3188 if ( (hdr_len >= 116) && 3189 (0 != (EXTRACTOR_forensic_be32_ (&rec[16 + 112]) & 0x40)) ) 3190 { 3191 if (0 != 3192 parse_exth (ec, 3193 rec, 3194 rec_len, 3195 (size_t) hdr_len + 16, 3196 &have_title)) 3197 goto CLEANUP; 3198 } 3199 if ( (! have_title) && 3200 (hdr_len >= 76) ) 3201 { 3202 uint32_t fn_off = EXTRACTOR_forensic_be32_ (&rec[16 + 68]); 3203 uint32_t fn_len = EXTRACTOR_forensic_be32_ (&rec[16 + 72]); 3204 3205 if ( (fn_len > 0) && 3206 (fn_len <= EXTRACTOR_FORENSIC_MAX_STRING) && 3207 (((uint64_t) fn_off) + fn_len <= rec_len) ) 3208 { 3209 if (0 != 3210 EXTRACTOR_forensic_emit_text_ (ec, 3211 PLUGIN_NAME, 3212 EXTRACTOR_METATYPE_TITLE, 3213 (const char *) &rec[fn_off], 3214 fn_len)) 3215 goto CLEANUP; 3216 have_title = 1; 3217 } 3218 } 3219 NAME_ONLY: 3220 if ( (! have_title) && 3221 (0 != EXTRACTOR_forensic_emit_text_ (ec, 3222 PLUGIN_NAME, 3223 EXTRACTOR_METATYPE_TITLE, 3224 (const char *) hdr, 3225 32)) ) 3226 goto CLEANUP; 3227 if (rec_ok && 3228 (EXTRACTOR_fulltext_requested_ (ec->config)) ) 3229 mobi_fulltext (ec, 3230 rec, 3231 rec_len, 3232 hdr_len, 3233 num_records, 3234 size); 3235 CLEANUP: 3236 free (rec); 3237 } 3238 3239 3240 /** 3241 * Main entry method for the ebook extraction plugin. 3242 * 3243 * @param ec extraction context provided to the plugin 3244 */ 3245 void 3246 EXTRACTOR_ebook_extract_method (struct EXTRACTOR_ExtractContext *ec); 3247 3248 void 3249 EXTRACTOR_ebook_extract_method (struct EXTRACTOR_ExtractContext *ec) 3250 { 3251 unsigned char hdr[78]; 3252 struct EXTRACTOR_UnzipFile *uf; 3253 3254 if (! EXTRACTOR_forensic_read_ (ec, 3255 0, 3256 hdr, 3257 4)) 3258 return; 3259 if (0 != memcmp (hdr, 3260 "PK\x03\x04", 3261 4)) 3262 { 3263 /* not a ZIP; the only other thing we handle is a Palm database, 3264 whose type/creator pair sits at offset 60 */ 3265 if (! EXTRACTOR_forensic_read_ (ec, 3266 0, 3267 hdr, 3268 sizeof (hdr))) 3269 return; 3270 if (0 == memcmp (&hdr[60], 3271 "BOOKMOBI", 3272 8)) 3273 extract_mobi (ec, 3274 hdr); 3275 return; 3276 } 3277 if (NULL == (uf = EXTRACTOR_common_unzip_open (ec))) 3278 return; 3279 /* a plain ZIP, a docx and an ODF file all start `PK\003\004', so 3280 claim the file only if it carries the OCF structure */ 3281 if (has_member (uf, 3282 "META-INF/container.xml")) 3283 { 3284 extract_epub (ec, 3285 uf); 3286 } 3287 else 3288 { 3289 size_t len; 3290 char *mt = read_member (uf, 3291 "mimetype", 3292 64, 3293 &len); 3294 3295 if ( (NULL != mt) && 3296 (len >= strlen ("application/epub+zip")) && 3297 (0 == memcmp (mt, 3298 "application/epub+zip", 3299 strlen ("application/epub+zip"))) ) 3300 { 3301 free (mt); 3302 extract_epub (ec, 3303 uf); 3304 } 3305 else 3306 { 3307 free (mt); 3308 } 3309 } 3310 (void) EXTRACTOR_common_unzip_close (uf); 3311 } 3312 3313 3314 /* end of ebook_extractor.c */