{"record":{"id":"a159d19bfc7d3d9c","repo":"AlistGo/alist","slug":"range-start-out-of-bound","errorCode":null,"errorMessage":"range start out of bound","messagePattern":"range start out of bound","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"drivers/chunker/util.go","lineNumber":832,"sourceCode":"\nfunc (d *Chunker) buildKeepSet(locations ...objectLocation) map[string]struct{} {\n\tkeep := make(map[string]struct{}, len(locations))\n\tfor _, location := range locations {\n\t\tif location.LogicalPath == \"\" {\n\t\t\tcontinue\n\t\t}\n\t\tkeep[d.keepKey(location)] = struct{}{}\n\t}\n\treturn keep\n}\n\nfunc (d *Chunker) chunkPartBaseName(filePath string, chunkNo int, xactID string) string {\n\treturn path.Base(d.makeChunkName(filePath, chunkNo, xactID))\n}\n\nfunc (d *Chunker) openChunkReader(ctx context.Context, parts []linkedPart, totalSize int64, req http_range.Range) (io.ReadCloser, error) {\n\tif req.Start < 0 || req.Start > totalSize {\n\t\treturn nil, fmt.Errorf(\"range start out of bound\")\n\t}\n\tif req.Length < 0 || req.Start+req.Length > totalSize {\n\t\treq.Length = totalSize - req.Start\n\t}\n\tif req.Length == 0 {\n\t\treturn io.NopCloser(strings.NewReader(\"\")), nil\n\t}\n\n\tvar (\n\t\treaders   []io.Reader\n\t\tclosers   = utils.EmptyClosers()\n\t\toffset    int64\n\t\tremaining = req.Length\n\t)\n\tfor _, part := range parts {\n\t\tpartStart := offset\n\t\tpartEnd := offset + part.part.Size\n\t\toffset = partEnd","sourceCodeStart":814,"sourceCodeEnd":850,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/drivers/chunker/util.go#L814-L850","documentation":"Range-validation error inside Chunker.openChunkReader, which assembles a reader over chunk parts to serve an HTTP Range request on a chunked file. The requested start offset is rejected when it is negative or greater than the file's total size (as recorded in chunk metadata). This is a precondition failure before any chunk is opened — the requested window simply cannot exist within the logical file.","triggerScenarios":"Serving a range request (video seeking, partial download, web seed client) where req.Start > totalSize or req.Start < 0: client asks for bytes beyond EOF; stale chunk metadata claims a smaller totalSize than the real file (or the file shrank); corrupted/truncated metadata after an interrupted chunked upload; unit tests passing an unsanitized Range struct.","commonSituations":"Media players requesting a seek position past the end of a chunked video; chunk metadata out of sync after manual edits to the remote store; a partial chunk upload leaving totalSize=0 while clients still issue range reads.","solutions":["Clamp the requested start to the file's actual size and serve an empty body or 416 when the range is unsatisfiable, instead of propagating the raw error","Verify the chunked object's metadata totalSize matches the sum of chunk part sizes; re-upload or repair metadata if they diverge","Fix callers that construct http_range.Range without validating Start against the object size"],"exampleFix":"// before\nreader, err := d.openChunkReader(ctx, parts, total, req)\n\n// after\nif req.Start < 0 || req.Start > total {\n    req.Start = total\n    req.Length = 0\n}\nreader, err := d.openChunkReader(ctx, parts, total, req)","handlingStrategy":"validation","validationCode":"// sanitize the range before opening a chunked read\nfunc sanitizeRange(req http_range.Range, total int64) (http_range.Range, error) {\n    if req.Start < 0 {\n        req.Start = 0\n    }\n    if req.Start > total {\n        return req, fmt.Errorf(\"unsatisfiable range: start %d > size %d\", req.Start, total)\n    }\n    if req.Length < 0 || req.Start+req.Length > total {\n        req.Length = total - req.Start\n    }\n    return req, nil\n}","typeGuard":"func rangeSatisfiable(req http_range.Range, totalSize int64) bool {\n    return req.Start >= 0 && req.Start <= totalSize\n}","tryCatchPattern":"reader, err := d.openChunkReader(ctx, parts, total, req)\nif err != nil {\n    if strings.Contains(err.Error(), \"range start out of bound\") {\n        // serve 416 or empty body instead of a 500\n        w.Header().Set(\"Content-Range\", fmt.Sprintf(\"bytes */%d\", total))\n        w.WriteHeader(http.StatusRequestedRangeNotSatisfiable)\n        return\n    }\n    return err\n}","preventionTips":["Always clamp Range values against the object's real size before use","Treat out-of-bound ranges as 416 responses, not internal errors","Keep chunk metadata totalSize in sync with actual part sizes; repair after interrupted uploads"],"tags":["alist","chunker","http-range","validation","corrupt-metadata"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}