Skip to content

Share the Newznab and Torznab transport and response loop #743

Description

@forkwright

Finding

NewznabClient and TorznabClient independently own the same HTTP/XML session machinery even though their real protocol differences begin only when mapping a feed item into a SearchResult and when downloading NZB versus torrent content.

The two clients have field-for-field state, byte-equivalent constructors, byte-equivalent fetch_xml implementations, the same caps implementation, the same health-test implementation, the same query dispatch shape, and duplicated negative tests for cancellation, auth failure, rate limiting, oversized bodies, and credential redaction.

Verified against main 65644113e47814c87eaba14a6f322820974b8780.

Evidence

  • crates/eksetasis/src/client/newznab.rs:21-88 and torznab.rs:21-88 each define { config, http, cf_proxy, timeout, max_body_bytes }, the same constructor, and the same fetch_xml body.
  • Both fetch_xml copies independently own Cloudflare bypass, request timeout, cancellation selection, URL-secret redaction, 401/403 → AuthFailed, 429/Retry-AfterRateLimited, and bounded response-body reading.
  • newznab.rs:161-185 and torznab.rs:170-194 duplicate caps() and test() exactly.
  • The test modules repeat the same transport-policy cases. For example, the cancellation, 403, 429-without-header, 500-fallthrough, oversized-stream, and error-redaction tests are structurally identical on both clients.
  • Their legitimate divergence is visible immediately after the shared fetch/parse boundary: Newznab maps results to ReleaseProtocol::Nzb without torrent peer/hash fields, while Torznab maps seeders, leechers, infohash, and ReleaseProtocol::Torrent. Download behavior also differs by protocol.

No open issue owns this shared transport/session layer; #513 concerns Cardigann definition coverage rather than these two native clients.

Why this matters

Authentication, cancellation, response-size bounds, Cloudflare routing, secret redaction, retry metadata, and health semantics are cross-cutting acquisition policy. Maintaining them twice means a security or resilience repair can land for Torznab while Newznab retains the old behavior, or vice versa.

The repeated negative tests do not prevent that structural fork; they themselves are copies that need to be remembered whenever the policy changes. The protocol-specific result mapping does not require duplicating the HTTP transaction that precedes it.

Desired correction

Introduce one narrow XML-indexer transport/session core that owns the common client state and request lifecycle. Newznab and Torznab should remain separate IndexerClient implementations for query/result/download semantics, but delegate fetch, caps retrieval, status mapping, cancellation, body bounds, auth/rate-limit handling, and URL-redaction policy to the shared owner.

Prefer shared behavioral fixtures for the common transport contract and protocol-specific fixtures only for the mapping differences.

Done when:

  • one production function owns the XML HTTP request lifecycle for both clients;
  • caps/test behavior is derived from that common path;
  • auth, cancellation, rate-limit, bounded-body, and secret-redaction tests are written once against the shared contract;
  • Newznab/Torznab retain only their genuine result/download differences; and
  • changing common transport policy requires one implementation edit.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    refactorCode quality and architecture improvementstech-debtTechnical debt and cleanup

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions