libextractor

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

README.forensics (23488B)


      1 heif -- heif_test.heic, heif_test.avif
      2   Generated by contrib/gen_heif_testdata.sh (python3 only, deterministic).
      3   Hand-constructed ISO base media file format containers: the box tree,
      4   the item structures and the property associations are exactly what
      5   ISO/IEC 14496-12, ISO/IEC 23008-12 and the AV1 Image File Format
      6   specify, but the 'mdat' payload is a placeholder rather than a real
      7   HEVC or AV1 bitstream, so the files identify but do not decode.  That
      8   is on purpose: the plugin is a container parser and the values it must
      9   report are then known exactly.
     10 
     11   heif_test.heic (916 bytes): major brand 'heic'.  Three items -- the
     12   primary image (id 1), a thumbnail (id 2, tied to the primary by an
     13   'iref' of type 'thmb') and an 'Exif' metadata item (id 3).  The 'ipco'
     14   property container holds the thumbnail's 16x12 'ispe' *first* and the
     15   primary item's 96x64 'ispe' second, so a parser that reports the first
     16   'ispe' it finds gets the wrong answer and only a correct
     17   pitm -> ipma -> ipco walk reports 96x64.  Also carries pixi (10 bit),
     18   irot (90 degrees), imir, colr nclx with transfer 16 (PQ, i.e. HDR) and
     19   an hvcC shell.
     20 
     21   heif_test.avif (631 bytes): major brand 'mif1' with 'avif' only among
     22   the compatible brands, which is what several AVIF writers emit; both
     23   the MIME type and the codec therefore have to come from that list.
     24   One image item, 128x72, no 'pixi', so the bit depth has to be taken
     25   from the 'av1C' configuration record.
     26 
     27   Licence: CC0.  No third party content.
     28 
     29 webp -- webp_test.webp
     30   Generated by contrib/gen_webp_testdata.sh (needs ffmpeg with libwebp,
     31   Debian package "ffmpeg", plus python3; deterministic).
     32 
     33   webp_test.webp (752 bytes): an extended (VP8X) animated WebP carrying
     34   every optional chunk the container defines -- ICCP, ANIM, three ANMF
     35   frames, EXIF and XMP -- with the alpha, animation, ICC, EXIF and XMP
     36   feature flags set.  Canvas 40x24.  The three frames hold a genuine
     37   lossless VP8L bitstream encoded by libwebp from a solid colour
     38   generated by lavfi (nothing is read from disk); libwebp reads the
     39   result back as a valid 3 frame RGBA animation.  Frame durations are
     40   100, 150 and 250 ms, summing to 500 ms.  The ICC (136 bytes), EXIF and
     41   XMP payloads are placeholders: the plugin only reports that they are
     42   present and parsing them is the exiv2 plugin's job.
     43 
     44   Licence: CC0.  No third party content.
     45 
     46 sqlite -- sqlite_test.db
     47   SQLite 3 database, 5120 bytes.  Generated by contrib/gen_sqlite_testdata.sh
     48   with the sqlite3 CLI: 1 KiB pages, UTF-8, incremental auto-vacuum, WAL
     49   journal mode, application_id 1279613012 (= "LEXT"), user_version 4242, two
     50   tables and an index, with rows deleted and an index dropped so that a
     51   freelist page survives.  The generator then rewrites the SQLite version at
     52   header offset 96 to 3045001 ("SQLite 3.45.1") and makes version-valid-for
     53   agree with the change counter, so the file does not depend on the sqlite3
     54   that produced it.  test_sqlite asserts on the page size, journal mode,
     55   change counter, page count, free space, schema cookie, user_version,
     56   vacuum mode, encoding, application id (numeric and as its four-character
     57   tag), writer version and logical size.  Licence: CC0 / public domain.
     58 
     59 tar -- tar_test.tar
     60   POSIX ustar archive, 10240 bytes.  Generated by contrib/gen_tar_testdata.sh
     61   using Python's tarfile with every ownership field set explicitly:
     62   uname "forensic", gname "analysts", uid 1000, gid 100, plus one member
     63   owned by root/root.  Contains a directory, three regular files, a symlink,
     64   a hard link and one member whose 107-character path has to be split across
     65   the ustar prefix field.  Modification times are hard-coded (1700000000 and
     66   1710506096); nothing is read from the machine that runs the generator.
     67   test_tar asserts on the format, member count, summed member size, newest
     68   mtime, both owners and groups, both uid/gid pairs, the five distinct
     69   permission masks, all seven member names and both link targets.
     70   Licence: CC0 / public domain.
     71 
     72 iso9660 -- iso9660_test.iso
     73   ISO 9660 (ECMA-119) image, 43008 bytes = 21 sectors.  Generated by
     74   contrib/gen_iso9660_testdata.sh, which builds the volume descriptor set
     75   by hand with Python struct rather than calling genisoimage or xorriso: a
     76   mastering program stamps its own name and the current time into the
     77   descriptors, which would be neither reproducible nor assertable.  Layout is
     78   16 zero sectors of system area, then the primary volume descriptor, an
     79   El Torito boot record, a Joliet supplementary descriptor (escape "%/E"),
     80   the set terminator and one root directory extent whose "." record carries
     81   SUSP "SP" and Rock Ridge "RR" entries.  This is over the 32 KB guideline
     82   and cannot be smaller: ECMA-119 puts the first descriptor at byte 32768.
     83   test_iso9660 asserts on the system/volume/publisher/preparer/application
     84   identifiers, the copyright, abstract and bibliographic file identifiers,
     85   the block size, volume size, volume set size, all four dec-datetime fields
     86   and the Joliet, Rock Ridge and El Torito detections.
     87   Licence: CC0 / public domain.
     88 
     89 
     90 geotiff -- geotiff_test.tif
     91   Generated by contrib/gen_geotiff_testdata.sh (python3 only,
     92   deterministic; no gdal-bin needed).  Hand-built with struct rather
     93   than produced by gdal_translate: GDAL's own output is several KB and
     94   its numbers are whatever the projection happens to give, whereas a
     95   hand-built file can be 1 KB and can use round numbers for the
     96   tiepoint and the pixel scale so that the bounding box the plugin
     97   derives from them is an exact constant to assert on.
     98 
     99   geotiff_test.tif (1099 bytes): a classic little-endian TIFF 6.0
    100   baseline image, 16x16, 8 bit greyscale, uncompressed, with the
    101   GeoTIFF 1.1 tags for EPSG:32633 (WGS 84 / UTM zone 33N).
    102   ModelPixelScaleTag is 10 m, ModelTiepointTag maps raster (0,0) to
    103   model (500000, 5400000), so the extent is exactly
    104   500000,5399840,500160,5400000 -- a value that can only come out right
    105   if the tiepoint, the scale and the image dimensions were all read and
    106   combined.  GeoKeyDirectoryTag carries eight keys covering all three
    107   TIFFTagLocation cases the specification allows (inline SHORT and
    108   '|'-terminated substrings of GeoAsciiParamsTag), and GDAL's two
    109   private tags (GDAL_METADATA with three <Item> elements, GDAL_NODATA)
    110   are present so the provenance scan is exercised too.  Byte order,
    111   BigTIFF and the ModelTransformationTag path are covered by variants
    112   built during development but not committed, since asserting on them
    113   would only duplicate this file's expectations.
    114 
    115   Licence: CC0.  No third party content.
    116 
    117 diskimage (QCOW2 / VMDK / VHD / VHDX)
    118 -------------------------------
    119 
    120   Generated by contrib/gen_diskimage_testdata.sh (needs qemu-utils and
    121   python3).  qemu-img is the producer for all three; python3 only
    122   overwrites the fields qemu-img randomises per run, so re-running the
    123   script gives byte-identical files.
    124 
    125   diskimage_test.qcow2 (1792 bytes): a QCOW2 version 3 differencing
    126   image, 1 MiB virtual, 512 byte clusters, backed by
    127   "diskimage_base.qcow2" (which the script creates in a temporary
    128   directory and throws away -- the plugin never opens it, only reports
    129   its name).  Created with lazy_refcounts=on and compression_type=zstd
    130   so that the compatible-feature and incompatible-feature paths and the
    131   version 3 compression type byte are all exercised.  Untouched
    132   qemu-img output; the QCOW2 header has no per-run fields.
    133 
    134   diskimage_test.vmdk (16384 bytes): a monolithicSparse VMDK, 1 MiB
    135   virtual, 64 KiB grains, backed by "diskimage_base.vmdk".  Covers both
    136   the binary sparse extent header and the text descriptor embedded in
    137   it at offset 512.  The CID and parentCID qemu-img picks at random are
    138   rewritten to 1f2e3d4c and 5a6b7c8d and the descriptor slot is padded
    139   back to its original length, so no offset in the header changes.
    140   TRUNCATED from the 64 KiB qemu-img writes to 16 KiB: everything past
    141   the descriptor is zeroed grain tables.  The file is therefore no
    142   longer a mountable VMDK, but the header (80 bytes) and the descriptor
    143   (offset 512, 10 KiB) are intact and are all the plugin reads.
    144 
    145   diskimage_test.vhdx (4096 bytes): the file identifier block of a
    146   VHDX, with the UTF-16LE creator string overwritten with a fixed value
    147   so the test does not depend on the local qemu version.  TRUNCATED,
    148   and unavoidably so: [MS-VHDX] fixes the header at 64 KiB and the
    149   region table at 192 KiB, and requires the metadata region to start at
    150   a 1 MiB boundary, so the smallest conformant VHDX is over 2 MiB.  The
    151   test can only assert the signature-level items; the plugin's region
    152   table, metadata table and parent locator walk was checked by hand
    153   against full qemu-img output and against a hand-built conformant
    154   image with a parent locator and a non-zero LogGuid.
    155 
    156   There is no diskimage_test.vhd: the plugin reads the Microsoft VHD
    157   ("conectix") footer as well, but src/plugins/Makefile.am has no
    158   EXTRA_DIST entry for one.  Run the generator with GEN_FULL=1 to get
    159   a deterministic sample plus the untruncated VMDK and VHDX.
    160 
    161   Licence: CC0.  The images contain no data at all, only headers.
    162 
    163 pecoff -- pecoff_test.exe, pecoff_test.dll
    164   Generated by contrib/gen_pecoff_testdata.sh.  The .exe needs the
    165   Debian package "gcc-mingw-w64-x86-64"; the .dll needs only python3.
    166   Both are deterministic: the linker is told not to insert a timestamp
    167   and the script writes a fixed one afterwards.
    168 
    169   pecoff_test.exe (4608 bytes): a real x86-64 PE32+ image built by
    170   mingw-w64 from a three line program, linked without the C runtime
    171   (-nostdlib) so that its whole import table is KERNEL32.dll!ExitProcess
    172   and the import hash is therefore a value the test can spell out --
    173   MD5("kernel32.exitprocess"), which is what pefile computes for the
    174   same file.  Carries a VERSIONINFO resource written by windres, which
    175   is what exercises the resource directory walk and the nested,
    176   32-bit-aligned, UTF-16 StringFileInfo parser.  TimeDateStamp is set to
    177   1700000000 (2023-11-14T22:13:20Z) after linking.
    178 
    179   pecoff_test.dll (3584 bytes): assembled from the PE specification with
    180   python3, because mingw cannot produce the two artifacts that matter
    181   most here.  A Rich header (Microsoft's linker writes those and nothing
    182   else does) masked with the key 0x1A2B3C4D and holding four
    183   (product id, build, use count) triples; a CodeView RSDS record naming
    184   C:\Users\builder\source\repos\pecoff_test\x64\Release\pecoff_test.pdb
    185   with a fixed GUID and age; an export directory with three named
    186   exports; a certificate table; and a .pack section filled with SHA-256
    187   output so that its entropy lands where a packed section's would.  The
    188   certificate table is not a signature and verifies as nothing -- it is
    189   the shortest DER that gives the commonName scan something to find, and
    190   the name it carries is "GNU libextractor test signer".  Verified
    191   against pefile: machine, subsystem, Rich values, CodeView record and
    192   export names all read back as intended.
    193 
    194   Licence: CC0.  No third party content.
    195 
    196 lnk -- lnk_test.lnk
    197   Generated by contrib/gen_lnk_testdata.sh (python3 only, deterministic).
    198 
    199   lnk_test.lnk (706 bytes): a Windows shell link assembled from
    200   [MS-SHLLINK] rather than produced by Windows, so that every value the
    201   test asserts is a constant the generator owns.  It carries a
    202   LinkTargetIDList (two shell items) that the parser has to step over
    203   without looking at, a LinkInfo whose VolumeID has drive type "fixed",
    204   serial 1A2B-3C4D and the unicode volume label EVIDENCE, and whose
    205   target path is split across LocalBasePathUnicode
    206   ("C:\Users\analyst\Desktop\") and CommonPathSuffixUnicode
    207   ("evidence.txt") so that the join is tested; UTF-16 string data for
    208   all five optional strings; and a TrackerDataBlock naming the machine
    209   "lab-ws-07" whose droid file identifier 5f3e2d1c-a4b6-11d2-8ae7-
    210   001b44113ab7 is a version 1 UUID carrying the MAC address
    211   00:1b:44:11:3a:b7.  The three FILETIMEs in the header are the
    212   target's, and all differ: 2021-03-04T05:06:07Z (creation),
    213   2021-06-15T14:30:00Z (write) and 2022-07-08T09:10:11Z (access).
    214 
    215   Cross-checked with liblnk's lnkinfo (20240423), which reads back the
    216   same timestamps, serial, label, paths, strings and tracker GUIDs.
    217 
    218   Licence: CC0.  No third party content.  The machine name, MAC address
    219   and user name are invented.
    220 
    221 plist -- plist_binary.plist, plist_xml.plist
    222   Generated by contrib/gen_plist_testdata.sh (python3 only, using the
    223   standard library's plistlib; deterministic).  Both files hold the same
    224   dictionary written once in each representation, so the two solution
    225   sets in test_plist.c differ only where the formats genuinely do.
    226 
    227   The dictionary mixes the keys of an application bundle Info.plist
    228   (CFBundleIdentifier, CFBundleShortVersionString, DTPlatformName,
    229   DTXcode, BuildMachineOSBuild, ...) with those of an iOS backup
    230   Info.plist (DeviceName, ProductType, SerialNumber, LastBackupDate),
    231   which is the forensic case the plugin exists for, plus one integer
    232   under an unrecognised key so the "key: value" fallback is exercised.
    233 
    234   plist_binary.plist (613 bytes): bplist00, 32 objects, two byte offsets
    235   and one byte object references.  NSHumanReadableCopyright is
    236   "Copyright (C) 2026 Grüße" with a copyright sign and two umlauts, so
    237   plistlib has to store it as a UTF-16BE string object (marker 0x6n) --
    238   the string decoder most likely to be got wrong, and the one the ASCII
    239   path (0x5n) cannot fake.  LastBackupDate is a date object (0x33), an
    240   eight byte big-endian IEEE double counting seconds from 2001-01-01,
    241   and only comes back as 2024-03-14T15:09:26Z if both the epoch shift
    242   and the byte order were handled.
    243 
    244   plist_xml.plist (1126 bytes): the same dictionary as a DTD-bound XML
    245   document, which is what the bounded <key>/value scan reads.
    246 
    247   The parser was also checked against the XML property lists shipped by
    248   Debian's cmake and qt6 packages, and against a binary plist produced
    249   by libplist's plistutil (an implementation independent of plistlib,
    250   and one that interleaves the key and value object indices rather than
    251   writing them in two runs).  Those files are not committed.
    252 
    253   Licence: CC0.  Every identifier, serial number and device name in the
    254   file is invented.
    255 
    256 id3 -- id3_test.mp3
    257   Generated by contrib/gen_id3_testdata.sh.  Debian packages needed:
    258   python3, python3-mutagen and lame.  Deterministic.
    259 
    260   id3_test.mp3 (7546 bytes): one second of a 440 Hz sine at 44100 Hz,
    261   mono, written as a WAV by python and encoded by LAME 3.100 at -V 5
    262   --vbr-new.  LAME is the encoder rather than ffmpeg on purpose: ffmpeg
    263   writes its own Xing/LAME tag stamped "Lavf lame", which does not carry
    264   the lowpass, ReplayGain, preset or VBR method fields that make the
    265   LAME tag the fingerprint it is.  Note that the encoder version string
    266   ("LAME3.100") and the settings summary derived from that tag are
    267   asserted on literally, so regenerating with a different LAME release
    268   means updating those two expectations.
    269 
    270   The tag is ID3v2.4 and exercises all four text encodings in one file:
    271   TIT2 is UTF-8, TPE1 is UTF-16 with a byte order mark, TALB is
    272   ISO-8859-1, and all three carry characters outside ASCII, so a plugin
    273   that passed the bytes through would produce invalid UTF-8 rather than
    274   quietly wrong text.  TCON is stored as "(52)" and has to come back
    275   resolved to "Electronic".  The USLT and APIC frames are both longer
    276   than 127 bytes, which is where the synchsafe frame size of ID3v2.4
    277   first differs from the plain big-endian integer of 2.3 -- everything
    278   after them only parses if that was handled correctly.  The provenance
    279   frames are TENC, TSSE, TOWN, TFLT, TMED, a UFID with a MusicBrainz
    280   style owner, a PRIV owned by com.apple.iTunes, two URL frames and a
    281   TXXX with the description "apID".  The attached picture is a 208 byte
    282   PNG built by the generator with zlib.
    283 
    284   A hand-written ID3v1 tag follows the audio.  Every one of its fields
    285   is deliberately different from its ID3v2 counterpart, so only the two
    286   it alone supplies -- the year 1999 and the comment -- may reach the
    287   caller; anything else showing up means the gap filling is broken.
    288 
    289   The parser was also checked during development against files written
    290   by three other producers, none of them committed: mutagen at ID3v2.3
    291   (plain frame sizes, UTF-16 text), ffmpeg's own ID3v2.3 writer and its
    292   "Lavf"/"Lavc" Xing tag, a hand-built ID3v2.2 tag with three character
    293   frame identifiers, a hand-built unsynchronised ID3v2.3 tag, an
    294   ID3v1-only file, a stereo file (which puts the Xing tag at offset 36
    295   rather than 21) and a CBR file (an "Info" tag rather than "Xing").
    296 
    297   Licence: CC0.  The audio is a generated sine tone; the licensee
    298   address, purchase account, MusicBrainz identifier and every name in
    299   the tag are invented.
    300 
    301 apk -- apk_test.apk, apk_test.jar
    302   Generated by contrib/gen_apk_testdata.sh (python3 only, deterministic).
    303 
    304   apk_test.apk (4723 bytes): a hand-built Android package.  The
    305   AndroidManifest.xml is real binary XML written out from the chunk
    306   layout in AOSP's ResourceTypes.h -- a UTF-16LE string pool (which is
    307   what aapt emits for the manifest), a resource map with the attribute
    308   resource ids read off a real aapt2 build, and START/END_ELEMENT
    309   chunks.  It declares package org.gnu.libextractor.apktest,
    310   versionCode 1719, versionName 1.19.0, compileSdkVersion 33,
    311   minSdkVersion 21, targetSdkVersion 33 and five uses-permission
    312   elements.  The <application> element mixes literal and reference
    313   valued attributes on purpose: android:label is a literal string and
    314   must be reported, android:icon and android:networkSecurityConfig are
    315   resource references and a bare @0x7f... must not be reported as a
    316   title.  The archive also holds three classes*.dex stubs, three
    317   lib/<abi>/ stubs (arm64-v8a, armeabi-v7a, x86_64), a META-INF JAR
    318   manifest (so that the plugin has to prefer the Android manifest), a
    319   v1 signature under the alias ANDROIDD, and an APK Signing Block with
    320   v2 and v3 identifiers spliced in between the last entry and the
    321   central directory.
    322 
    323   apk_test.jar (3108 bytes): a JAR whose META-INF/MANIFEST.MF carries
    324   the JDK identification (Created-By, Build-Jdk, Build-Jdk-Spec), the
    325   Implementation-*/Specification-* triples, the OSGi Bundle-* headers
    326   and a Class-Path.  Implementation-Title is 98 bytes long and is
    327   therefore folded across two lines, so reading it back proves the
    328   continuation lines are unfolded.
    329 
    330   Both archives are signed by the same PKCS#7 block, a throwaway
    331   two-element chain ("libextractor test CA" issuing "libextractor test
    332   signer", serial 0x1719) generated once with openssl and embedded in
    333   the generator as base64; the private keys were discarded, so the
    334   signature protects nothing.  The certificate authority comes first
    335   inside the certificates set, which is what makes the test prove that
    336   the signer is resolved through the SignerInfo's issuerAndSerialNumber
    337   rather than simply taken from the first certificate.
    338 
    339   Licence: CC0.  No third party content; every name, package
    340   identifier and certificate subject is invented.
    341 
    342 ebook -- ebook_test.epub, ebook_test.mobi
    343   Generated by contrib/gen_ebook_testdata.sh (python3 only,
    344   deterministic).  Calibre is deliberately not used.
    345 
    346   ebook_test.epub (2645 bytes): an OCF container with the uncompressed
    347   `mimetype' member first as the specification requires, a
    348   META-INF/container.xml pointing at OEBPS/content.opf, and an EPUB 3
    349   package document with rich Dublin Core: title, creator, publisher,
    350   language, date, two subjects, a description indented across three
    351   lines (so that reading it back proves the white space collapsing), a
    352   rights field containing an &amp; entity, one dc:identifier with
    353   opf:scheme="ISBN" and one that is a urn:uuid, a dc:contributor with
    354   opf:role="bkp" naming the tool, <meta name="calibre:timestamp">,
    355   <meta name="generator"> and <meta property="dcterms:modified">.  The
    356   manifest holds seven <item> entries and the spine three <itemref>
    357   entries, so the entry count also proves the two are told apart.  A
    358   META-INF/encryption.xml (font obfuscation) is present.
    359 
    360   ebook_test.mobi (946 bytes): a hand-built Palm database, type/creator
    361   BOOKMOBI, three records.  The PDB creation and modification times are
    362   written in the 1904 epoch (2026-01-01 and 2026-02-01).  Record 0
    363   holds a PalmDOC header with encryption type 1, a 232 byte MOBI header
    364   declaring UTF-8 text and the EXTH flag, and an EXTH header with
    365   seventeen records covering author, publisher, description, ISBN,
    366   subject, published date, contributor (the tool), rights, ASIN,
    367   creator software triple, watermark, cdetype, last update time,
    368   updated title and language, followed by the full name.
    369 
    370   Licence: CC0.  No third party content; the book, its author, its
    371   ISBN and its watermark are invented.
    372 
    373 gpx / kml / mbox
    374 ----------------
    375 
    376   Generated by contrib/gen_gpx_testdata.sh, contrib/gen_kml_testdata.sh
    377   and contrib/gen_mbox_testdata.sh.  All three are plain text written
    378   out literally by a POSIX shell heredoc -- no tools, no timestamps
    379   taken from the clock, byte-for-byte reproducible.
    380 
    381   gpx_test.gpx (2146 bytes): a GPX 1.1 track with creator="Garmin eTrex
    382   30" (the field that names the recording device, and the one the
    383   plugin also reports as a device model), a full <metadata> block with
    384   name, desc carrying an &amp; entity, author name and a split
    385   <email id= domain=>, copyright with a <license>, two <link href=>,
    386   a <time> of 2024-03-15T07:12:00Z, keywords, and an explicit <bounds>.
    387   Two <wpt>, a <rte> of two <rtept> and one <trkseg> of five <trkpt>
    388   with elevations 408.2-430.7 m and times from 07:12:33 to 07:24:53,
    389   which is where the 0:12:20 duration and the 1215 m haversine sum in
    390   the test come from.  The coordinates are a walk through central
    391   Zurich, invented, not anybody's real track.
    392 
    393   kml_test.kml (2027 bytes): an OGC KML 2.2 Document over the same
    394   coordinates, so that the two plugins can be compared -- note that the
    395   tuples are lon,lat,alt, the reverse of the GPX attribute order, which
    396   is what the bounding box assertion actually tests.  It has a
    397   <description> whose HTML sits in a CDATA section, an atom:author and
    398   atom:link, a <TimeStamp>, a <LookAt> (the viewer's position, which
    399   the plugin reports as a comment rather than as the data's position),
    400   three Placemarks (two Points and a LineString) and a <NetworkLink>
    401   whose <href> points at a remote host.
    402 
    403   mbox_test.mbox (3504 bytes): three messages in mbox (mboxo) form.
    404   The first carries a three-hop Received chain with two IPv4 literals
    405   and one [IPv6:...] literal, an X-Originating-IP, a DKIM-Signature
    406   with d=example.com, an Authentication-Results, X-Mailer, an RFC 2047
    407   From and Subject (base64/UTF-8 and quoted-printable/ISO-8859-1 in one
    408   field), In-Reply-To and References, and a multipart/mixed body with
    409   two attachments -- one with a quoted filename and one that only
    410   exists as RFC 2231 filename*0/filename*1 continuation segments.
    411   Date: Tue, 12 Mar 2024 09:41:07 +0100 is Unix time 1710232867.  The
    412   third message's body holds a `>From ' line, the mboxo escaping, so
    413   the message count staying at three proves the separator scan is not
    414   fooled by it.
    415 
    416   Licence: CC0.  All names, addresses and hosts are from the RFC 2606 /
    417   RFC 5737 / RFC 3849 reserved ranges; the coordinates and the mail are
    418   invented.