feat(bookdrop): ComicInfo.xml exact provider-ID extraction (bookshelf-qgd90.5) #1240
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "bd-bookshelf-qgd90.5"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
metadata.MatchQuerywithExternalID+ExternalProviderIDso providers can skip fuzzy search and fetch a precise issue directly by IDinternal/bookdrop/comicinfo_provider_id.goparses ComicVine/Metron issue IDs from<Web>URLs and<Notes>annotations inComicInfo.xmlsidecarsNewvia newfetchExactByComicVineIDhelper (mapsErrIssueNotFound→ErrNoMatchfor graceful fallback)doSearchbefore the fuzzyresolveSearchFieldspathImportMetadataForProposalsgains areadComicArchivedep (nil = disabled, graceful fallback) and threads the extracted IDs into the per-proposal querywire.go,build_extended_deps.go) passReadComicArchiveFromPathTest plan
make testpassesmake coveragepasses (100% on all three domains: bookdrop, comicvine, metron)make lintno new issues from our worktreeExtractProviderIDFromComicInfo: WebLink ComicVine, WebLink Metron, Notes annotation, empty/unrecognised, zero-ID guard all testedReadComicArchiveFromPath: CBZ with ComicInfo.xml, CBR delegation, unsupported extension all testedImportMetadataForProposalswith stubreadComicArchivereturning ComicVine/Metron IDs — provider receivesExternalID > 0; error and nil cases fall back to fuzzy searchCloses bead bookshelf-qgd90.5 on merge.
When a comic archive's ComicInfo.xml contains a ComicVine or Metron issue URL in <Web>, or a [ComicVine:4000-{id}] annotation in <Notes>, the bookdrop metadata fetch now looks up the EXACT issue by ID via the provider's detail endpoint instead of doing a fuzzy Series+Issue+Year search. - Add ExternalID/ExternalProviderID to metadata.MatchQuery - New comicinfo_provider_id.go: extractProviderIDFromComicInfo + ReadComicArchiveFromPath - ComicVine: exact-by-ID early exit in New via fetchExactByComicVineID helper - Metron: exact-by-ID early exit in doSearch via fetchDetail - readComicArchive dep threaded into ImportMetadataForProposals (nil = fallback) - Wire sites (wire.go, build_extended_deps.go) updated - 100% coverage maintained across all three domains Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>Security Review — PR #1240 (ComicInfo.xml → exact-by-ID provider fetch)
Adversarial focus: ComicInfo.xml is attacker-controlled (inside an untrusted .cbz/.cbr).
SSRF (untrusted
<Web>/<Notes>steering outbound host) — CLEAN. The rawURL is never used as a request target.
extractProviderIDFromComicInfo(
internal/bookdrop/comicinfo_provider_id.go) only regex-captures a numeric issueID (
4000-(\d+),metron\.cloud/issue/(\d+),\[ComicVine:4000-(\d+)\]). Bothproviders build the request from the FIXED provider base URL:
fmt.Sprintf("%s/issue/4000-%d/?%s", baseURL, issueID, ...)(internal/metadata/comicvine/search.go:778),baseURL=defaultBaseURL/test server.fmt.Sprintf("%s/issue/%d/", d.baseURL, issueID)(internal/metadata/metron/provider.go:425).The persisted
WebLinkalso passes throughurlutil.SafeURL(internal/comic/persist.go:163).Injection / request-splitting — CLEAN. The ID is parsed with
strconv.ParseInt(match[1], 10, 64)and rejected unless> 0; it is emitted as%d. No non-numeric content can reach the path/query. Provider tag is a fixedliteral (
"comicvine"/"metron"), never concatenated into a URL.XXE / XML bomb — CLEAN. Parsing uses Go
encoding/xml(decodeComicInfoReader,internal/comic/comicinfo.go:258) with noCharsetReaderand no customEntitymap — external/DTD entities are not resolved by default. Decompressed size is
double-bounded: the zip/rar header uncompressed-size guard rejects
> maxComicInfoBytes(1 MB) before open, and anio.LimitReader(rc, cap)caps thedecode. Archive read is
io.LimitReader(f, 512MB). Zip entry is read, notextracted to disk (no zip-slip).
API keys — CLEAN. New log lines emit only
issue_id/volume_id/err(
internal/metadata/comicvine/provider.go); noapi_keyin the new code.Permanent vs transient — CORRECT. A malformed/missing/unsupported archive →
readComicArchiveerror/ErrNoComicInfois swallowed and falls back to fuzzysearch (
fetchMergedCandidateForProposal). A provider 404 for anattacker-forged ID maps to
metadata.ErrNoMatch(fall through to otherproviders), not a hard error. Exact-by-ID fetch still runs under the existing
rate limiter and only for already-enabled/active providers.
[MINOR] internal/bookdrop/import_metadata_service.go:15 — import out of goimports order
internal/comicis placed afterinternal/db/sqlc(comic < db/sqlc). Ifgolangci-lint/gci is not flagging it, harmless, but reorder for tidiness. No
security impact.
REVIEW VERDICT: 0 blocker, 0 major, 1 minor
Code Review: AttachFiles Data-Loss Fix (bd-bookshelf-du8hq)
1. Retain-vs-Delete Correctness ✓
The logic correctly implements selective retention:
total == reassigned(all files passed)All five guard paths (null byte, source-confinement, destination-confinement, destination-exists, move-failure) correctly skip the counter increment with
continue. A book with even one skipped file will havetotal > reassigned, preventing its deletion.Atomic transaction: Both reassign and delete execute in a single
runInTxblock. If delete fails, reassign rolls back. ✓2. Over-Deletion Regression Prevention ✓
Books with all files passing guards WILL satisfy
total == reassigned, so they ARE still deleted. The happy path is preserved. ✓3. Coverage Exclusion: Valid Repath, Not New Exclusion ✓
books/attach_service.go:137.28,143.14books/attach_service.go:146.28,152.144. Test Structure & Conventions ✓
package books_test✓Itblocks follow this pattern ✓total > reassigned✓5. Result Struct Assignment ✓
FilesReassigned and SourceBooksRemoved counts reflect actual guarded/deleted behavior. Tests validate both counts. ✓
6. Edge Cases ✓
REVIEW VERDICT: 0 blockers, 0 majors, 0 minors
This is a correct, focused fix with rigorous test coverage and no regressions. Ready for merge.
zombor referenced this pull request2026-07-25 11:35:29 +00:00
6655f8a8897119c94d37zombor referenced this pull request2026-07-26 19:35:19 +00:00
zombor referenced this pull request2026-07-26 19:35:51 +00:00
zombor referenced this pull request2026-07-26 21:58:20 +00:00
zombor referenced this pull request2026-07-26 21:58:42 +00:00