{"record":{"id":"be903f30fed333dd","repo":"larksuite/cli","slug":"content-range-is-empty","errorCode":null,"errorMessage":"content-range is empty","messagePattern":"content-range is empty","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"extension/download/download.go","lineNumber":675,"sourceCode":"// contentRange is a parsed `Content-Range: bytes start-end/total` header.\ntype contentRange struct {\n\tstart int64\n\tend   int64\n\ttotal int64\n}\n\nfunc (cr contentRange) String() string {\n\treturn fmt.Sprintf(\"bytes %d-%d/%d\", cr.start, cr.end, cr.total)\n}\n\nfunc (cr contentRange) length() int64 {\n\treturn cr.end - cr.start + 1\n}\n\nfunc parseContentRange(header string) (contentRange, error) {\n\theader = strings.TrimSpace(header)\n\tif header == \"\" {\n\t\treturn contentRange{}, fmt.Errorf(\"content-range is empty\")\n\t}\n\tunit, spec, found := strings.Cut(header, \" \")\n\tif !found || !strings.EqualFold(unit, \"bytes\") {\n\t\treturn contentRange{}, fmt.Errorf(\"unsupported content-range: %q\", header)\n\t}\n\tparts := strings.SplitN(strings.TrimSpace(spec), \"/\", 2)\n\tif len(parts) != 2 || parts[0] == \"\" || parts[1] == \"\" || parts[0] == \"*\" {\n\t\treturn contentRange{}, fmt.Errorf(\"unsupported content-range: %q\", header)\n\t}\n\tif parts[1] == \"*\" {\n\t\treturn contentRange{}, fmt.Errorf(\"unknown total size in content-range: %q\", header)\n\t}\n\tbounds := strings.SplitN(parts[0], \"-\", 2)\n\tif len(bounds) != 2 || bounds[0] == \"\" || bounds[1] == \"\" {\n\t\treturn contentRange{}, fmt.Errorf(\"unsupported content-range: %q\", header)\n\t}\n\tstart, err := strconv.ParseInt(bounds[0], 10, 64)\n\tif err != nil {","sourceCodeStart":657,"sourceCodeEnd":693,"githubUrl":"https://github.com/larksuite/cli/blob/7fd6ef3c07182257ce776cdc5a614e122d5bd4b3/extension/download/download.go#L657-L693","documentation":"parseContentRange in extension/download parses the HTTP `Content-Range` header of a ranged (partial) download. When the header is missing or whitespace-only, it returns \"content-range is empty\". Callers openPartial/openNext rely on this header to compute chunk offsets, so a response without it cannot be assembled as a partial download.","triggerScenarios":"A ranged GET (Range: bytes=...) response that omits Content-Range: server returns 200 instead of 206 Partial Content, a proxy/CDN strips the header, or the endpoint does not support range requests; the header value is empty after trimming.","commonSituations":"Downloading from storage/CDN endpoints that ignore Range and return the full body; misconfigured caches or gateways stripping headers; resuming a download against a different server that no longer supports ranges.","solutions":["Check the response: log status code and headers; a 200 (not 206) means the server ignored the Range request — fall back to a full single-shot download.","Bypass or reconfigure proxies/CDNs that strip Content-Range, or use a direct storage URL.","Retry with backoff — transient middlebox behavior can drop headers; retrying may recover.","If the target regularly lacks range support, disable partial/resumable download and fetch the file whole."],"exampleFix":"// before: assume every ranged response carries Content-Range\nresp, _ := client.Do(rangeReq)\ncr, err := parseContentRange(resp.Header.Get(\"Content-Range\"))\n// after: verify 206 + header presence and fall back to full download\nif resp.StatusCode != http.StatusPartialContent || strings.TrimSpace(resp.Header.Get(\"Content-Range\")) == \"\" {\n\treturn downloadFully(url, dest) // plain GET, no chunking\n}\ncr, err := parseContentRange(resp.Header.Get(\"Content-Range\"))","handlingStrategy":"retry","validationCode":"// Pre-flight: confirm the server supports ranges before chunked download\nreq, _ := http.NewRequest(\"HEAD\", url, nil)\nresp, err := client.Do(req)\nif err != nil { return err }\nif resp.Header.Get(\"Accept-Ranges\") != \"bytes\" {\n\treturn errors.New(\"server does not advertise range support; use full download\")\n}","typeGuard":"func hasContentRange(h http.Header) bool {\n\treturn strings.TrimSpace(h.Get(\"Content-Range\")) != \"\"\n}","tryCatchPattern":"cr, err := parseContentRange(resp.Header.Get(\"Content-Range\"))\nif err != nil {\n\tif strings.Contains(err.Error(), \"content-range is empty\") {\n\t\t// server ignored Range or stripped the header: retry once, then full download\n\t\tif !retryWithBackoff(ctx) {\n\t\t\treturn downloadFully(url, dest)\n\t\t}\n\t}\n\treturn err\n}","preventionTips":["Check for Accept-Ranges: bytes and expect 206 responses before chunked downloads.","Bypass/reconfigure proxies or CDNs that strip Content-Range.","Use direct storage URLs (e.g. presigned) instead of intermediary gateways.","Fall back to single-request downloads when range support is absent."],"tags":["http","download","range-requests","headers"],"backgroundTag":"missing-content-range-header","analyzedSha":"7fd6ef3c07182257ce776cdc5a614e122d5bd4b3","analyzedAt":"2026-09-04T21:17:44.649Z","contentChangedAt":"2026-09-04T21:17:44.649Z","schemaVersion":2},"datasetVersion":"2026-09-12T02:17:10.037Z"}