{"record":{"id":"b26e400b0f4a9f3c","repo":"GopeedLab/gopeed","slug":"invalid-blob-url","errorCode":null,"errorMessage":"invalid blob url","messagePattern":"invalid blob url","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/blob/registry.go","lineNumber":30,"sourceCode":"\t\"net/http\"\n\t\"net/url\"\n\t\"path\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\nconst urlPathPrefix = \"/__blob/\"\n\nconst rangeSourceFailureLimit = 2\n\n// unclaimedSourceTTL bounds how long a session-backed source may keep its\n// engine alive without ever being claimed by a download task.\nvar unclaimedSourceTTL = 10 * time.Minute\n\nvar (\n\tErrInvalidURL      = errors.New(\"invalid blob url\")\n\tErrInvalidOptions  = errors.New(\"invalid blob options\")\n\tErrSourceNotFound  = errors.New(\"blob source not found\")\n\tErrSourceRevoked   = errors.New(\"blob source revoked\")\n\tErrSourceClosed    = errors.New(\"blob source closed\")\n\tErrRangeNotAllowed = errors.New(\"blob range not allowed\")\n)\n\ntype SessionRef interface {\n\tRetain()\n\tRelease()\n}\n\ntype OpenRequest struct {\n\tOffset int64\n\tEnd    int64\n}\n\ntype OpenFunc func(ctx context.Context, req OpenRequest) (io.ReadCloser, error)","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/internal/blob/registry.go#L12-L48","documentation":"Returned by the blob Registry (internal/blob/registry.go) when a URL passed to Metadata/Acquire/Release/Revoke/SourceError/IsURL does not identify a source served by this registry. parseURL (registry.go:645-661) accepts only http URLs whose path starts with /__blob/, whose string form starts with the registry's current baseURL (host:port of its loopback server), and whose id is a single non-empty path segment. Anything else — a plain http(s) URL, a blob URL from a previous server generation, or a truncated/mangled URL — fails with this sentinel.","triggerScenarios":"Calling registry.Acquire/Metadata/Release/Revoke with a normal download URL instead of a URL returned by CreateOpener/CreateBlob; persisting a blob URL across a Registry restart (baseURL is regenerated with a new random port each time ensureServerLocked runs, so the stored prefix no longer matches); URL-encoding or path manipulation that makes the id a nested path (path.Base(id) != id).","commonSituations":"Treating blob:///__blob/<id> or the raw id as the URL; saving blob URLs to disk and reusing them next run; concatenating the base URL yourself instead of using the returned src.URL; passing the URL with a trailing slash or query string appended by other code.","solutions":["Use the exact string returned by Registry.CreateOpener/CreateBlob — never construct or edit a blob URL manually","Do not persist blob URLs; they are valid only for the lifetime of the Registry instance that created them (sources die with Registry.Close and the port changes on restart)","If unsure, gate the call with registry.IsURL(raw) (registry.go:106) which returns false instead of erroring","If you stored the URL, re-create the source (CreateOpener) and re-resolve to get a fresh URL"],"exampleFix":"// before\nurl := \"http://127.0.0.1:39821/__blob/abc\" // copied from a previous run\nerr := registry.Acquire(url)\n\n// after\nurl, err := registry.CreateOpener(open, opts)\nif err != nil { return err }\nerr = registry.Acquire(url) // exact string from CreateOpener","handlingStrategy":"validation","validationCode":"// Only operate on URLs this registry issued, in this process:\nif !registry.IsURL(raw) {\n    // not a blob URL: handle as a normal http URL or drop it\n}\nerr := registry.Acquire(raw)","typeGuard":"func isBlobURL(r *blob.Registry, raw string) bool { return r.IsURL(raw) }","tryCatchPattern":"if err := registry.Acquire(raw); err != nil {\n    if errors.Is(err, blob.ErrInvalidURL) {\n        // URL is not from this registry (stale or foreign): re-create source or ignore\n    }\n}","preventionTips":["Never persist or hand-edit blob URLs; regenerate them each run via CreateOpener/CreateBlob","Pass the exact string returned by CreateOpener through every layer (no re-parsing/re-encoding)","Gate every blob call with IsURL when the URL might come from user input or storage"],"tags":["blob","url","validation","registry"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}