github ePubLift/epublift cli-v2.0.0

2 hours ago

A major release because check's machine output and exit code changed (both
marked BREAKING below). Everything else is fixes, security and upkeep.

Changed

  • Upgraded the validator engine to epubveri 0.17.5 (from 0.4.4) — on both the
    CLI and the browser.
    epublift check and the web app's client-side Validate
    mode were re-pointed together, and on the 474-book test shelf they report
    identical findings — status, counts, and every finding's ID, severity, file,
    line, column and message — for every book. Against epubcheck's own test suite
    epubveri now catches 99.7% of the cases that should be flagged with the same
    message ID and raises no false alarm on the valid ones. On the shelf, 115
    books change verdict compared with 0.5.9, and epubcheck 5.3.0 was run on each
    of them as referee: 86 books that wrongly failed now pass (85 confirmed by
    epubcheck), 29 that wrongly passed now fail (all 29 confirmed), and the one
    disagreement is a documented, deliberate epubveri departure — a stray ; in a
    CSS declaration list, which the CSS specification allows and epubcheck's
    older parser rejects. Among the fixes, many from MobileRead forum reports,
    mostly EPUB 2 false positives:
    •  , é, © and the other standard HTML named entities no
      longer raise a spurious fatal RSC-016 in EPUB 2 content. (The most
      painful of the set:   is everywhere, especially in French books.)
    • An EPUB 2 package with several dc:date elements — the creation/modification
      pair Sigil and Calibre write — is no longer flagged RSC-005, and a legacy
      OpenType font no longer draws OPF-090. Both are EPUB 3-only concepts.
    • A valid <meta http-equiv="Content-Type"> in EPUB 2 content is no longer
      flagged: the exact-value rule is HTML5's, so it applies to EPUB 3 only.
    • Ordinary META-INF/ metadata (Apple's com.apple.ibooks.display-options.xml,
      calibre's bookmark files) no longer draws PKG-025.
    • A nav index landmark no longer draws a false RSC-005.
    • Genuinely malformed content documents (e.g. an unclosed <p>) are no longer
      silently accepted — they now fail with RSC-016 at the exact spot.
    • RSC-005 now names what is wrong ("element "p" is not allowed here")
      instead of a blanket schema message, reports every offending node rather than
      only the first, and RSC-011 points at the source <a> rather than the OPF
      root.
    • The engine also stops pulling second copies of zip and roxmltree into the
      build: it now shares the versions epublift already uses.
    • Findings now include epubcheck's info and usage levels, and the JSON
      summary always carries all five counters, zeros included.
  • BREAKING: check --json now emits the shared veripublica machine envelope
    (FORMATS.md)
    instead of a bespoke array, so epublift's validation output can be consumed by
    anything that already reads epubveri's or epubsana's — no per-tool parsing. One
    object per run: {tool, tool_version, convention, status, inputs[]}. Findings
    gain rule (a stable semantic sub-code such as opf.spine.duplicate_itemref,
    distinguishing the many unrelated violations one ID like RSC-005 can mean),
    data.params, and data.element_path — an XPath-style path to the offending
    node, resolvable with the accompanying namespaces map. See
    docs/validate.md for the shape and a migration note.
  • BREAKING: an input that cannot be read now exits 2, not 1. Exit 1
    means every input was graded and at least one book is invalid; exit 2 means
    at least one input could not be processed at all (a missing file, an unreadable
    one) — so a typo in a path is no longer indistinguishable from a bad book. Both
    are non-zero, so an ordinary check … || exit 1 gate is unaffected. Every
    input is still processed and reported even when an earlier one fails.

Fixed

  • A fatally broken book no longer reports (0 errors, 0 warnings). epubveri
    counts fatal-severity findings apart from errors, so a book stopped dead by a
    corrupt container — the whole reason it failed — summarised as a failure that
    named nothing. Both surfaces now name them only when there are any: the CLI
    reads FAIL book.epub (1 fatal, 0 errors, 0 warnings), and the web report's
    count line matches.
  • PDF import: text now comes from whichever extractor reads the page better.
    Newer lopdf reads pages its extract_text used to give up on, but on some
    fonts it runs the words together ("Atyearend2022,Berkshirewas…") and on an
    unreadable composite font it returns the raw glyph codes as letters
    ("DXWKRUV" for "authors"). Our own extractor, which places spaces from the
    real glyph widths, used to take over only when lopdf found nothing. Now, on a
    born-digital page, both run and the text with fewer run-together words wins
    (lopdf's on a tie, as before); on a scanned page with an OCR text layer lopdf
    still reads first. Measured on 126 real PDFs against the previous release: 13
    more documents import (74 vs 61), 27% more text comes through, and
    run-together words drop from 0.27% to 0.21%; the reference books in tests/
    import byte-identically.

Security

  • One crafted upload could take the whole web server down; now it is declined.
    epublift parses a book's container.xml, package document and NCX with
    roxmltree, which recurses per nesting level: an EPUB whose package document
    nested 20,000 elements (1.2 MB) overflowed the stack, and in Rust that aborts
    the process — the epublift-web container exited, taking every conversion in
    flight with it. Found by epubveri, reproduced against the server. Every such
    parse (eight sites, in the CLI and the web service alike) now runs epubveri's
    xmlguard::check first, which declines nesting deeper than 256 levels, more
    than 256 attributes on one element (quadratic to parse), more than a million
    elements, or runaway entity expansion — with an ordinary error. No real book
    comes near: the deepest document on the 474-book test shelf nests 24 levels,
    and convert, kepub, meta show and repair produce identical output on all 474
    before and after. epubveri is therefore always linked now (the validator
    itself stays behind the validate feature); the binaries did not grow.
  • Dependencies refreshed to their latest compatible releases (cargo update,
    135 lockfile changes). This clears two advisories — rustls 0.23.40
    (RUSTSEC-2026-0285, TLS 1.3 handshake messages accepted across encryption
    levels) and crossbeam-epoch 0.9.18 (RUSTSEC-2026-0204) — and the anyhow
    (unsound) and spin (yanked) warnings. archmage/magetypes/archmage-macros are held at 0.9.26: 0.9.27+ no longer
    compiles jxl-encoder-simd 0.3.0 (the JPEG XL encoder) on ARM64.
  • quick-xml 0.40 → 0.41, clearing two advisories on the parser that reads
    every uploaded book's package, metadata and content: RUSTSEC-2026-0194
    (quadratic time on a start tag with many attributes) and RUSTSEC-2026-0195
    (unbounded namespace-declaration allocation). No code change was needed;
    convert, kepub, meta show and repair produce identical output on all 474
    books of the test shelf. 0.42 is a separate decision: it rewrites the API
    around &str and rejects non-UTF-8 input outright.
  • lopdf 0.34 → 0.45 (PDF import), clearing RUSTSEC-2026-0187 (stack
    overflow on deeply nested PDF objects) and picking up fixes for four crashes
    on crafted PDFs and a bound on object-graph recursion.
  • HTTPS client: the pure-Rust crypto provider now comes from the maintained
    code line.
    rustls-rustcrypto's only release (0.0.2-alpha, 2024) pulls
    rustls-webpki 0.102 with four advisories (RUSTSEC-2026-0049, -0098, -0099,
    -0104); its main branch no longer does, so it is now pinned to a reviewed
    commit until a release is cut. The HTTPS client stays pure Rust — no C
    toolchain — and still runs on every CPU we ship for, Raspberry Pi 4
    included. Handshakes to every host we talk to (Open Library, Google Books,
    Mistral, the OCR model store) measure the same as before. One advisory
    remains accepted, with its reason in .cargo/audit.toml: the rsa Marvin
    side channel concerns RSA private keys, and a TLS client verifying server
    signatures holds none.
  • Dependencies are now audited continuously. A new Audit workflow checks
    Cargo.lock against the RustSec advisory database whenever the dependencies
    change and every Monday; the few accepted advisories are listed, each with
    its reason, in .cargo/audit.toml. Dependabot now proposes weekly updates
    for the Rust crates, the GitHub Actions and the Docker base image.
  • CI now builds and tests on ARM64 as well as x86-64, since the SIMD image
    codecs compile different code per architecture. A SECURITY.md now says how
    to report a vulnerability privately and what counts as one, including for
    the hosted web service.

Full Changelog: cli-v1.13.0...cli-v2.0.0

Don't miss a new epublift release

NewReleases is sending notifications on new releases.