{"record":{"id":"007b4a445f35f31e","repo":"AlistGo/alist","slug":"file-size-unknown","errorCode":null,"errorMessage":"file size unknown","messagePattern":"file size unknown","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/net/request.go","lineNumber":502,"sourceCode":"\t\tif resp.ContentLength > 0 {\n\t\t\ttotalBytes = resp.ContentLength\n\t\t}\n\t} else {\n\t\tparts := strings.Split(contentRange, \"/\")\n\n\t\ttotal := int64(-1)\n\n\t\t// Checking for whether a numbered total exists\n\t\t// If one does not exist, we will assume the total to be -1, undefined,\n\t\t// and sequentially download each chunk until hitting a 416 error\n\t\ttotalStr := parts[len(parts)-1]\n\t\tif totalStr != \"*\" {\n\t\t\ttotal, err = strconv.ParseInt(totalStr, 10, 64)\n\t\t\tif err != nil {\n\t\t\t\terr = fmt.Errorf(\"failed extracting file size\")\n\t\t\t}\n\t\t} else {\n\t\t\terr = fmt.Errorf(\"file size unknown\")\n\t\t}\n\n\t\ttotalBytes = total\n\t}\n\tif totalBytes != d.params.Size && err == nil {\n\t\terr = fmt.Errorf(\"expect file size=%d unmatch remote report size=%d, need refresh cache\", d.params.Size, totalBytes)\n\t}\n\tif err != nil {\n\t\t// _ = d.interrupt()\n\t\td.setErr(err)\n\t\td.cancel(err)\n\t}\n\treturn err\n\n}\n\nfunc (d *downloader) incrWritten(n int64) {\n\td.m.Lock()","sourceCodeStart":484,"sourceCodeEnd":520,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/internal/net/request.go#L484-L520","documentation":"In checkTotalBytes, when the Content-Range total segment is '*' the server explicitly declines to state the total size. Because the downloader's chunk planner (d.params.Size) expects a known size, it cancels the transfer with this error. The comment in code notes the intended fallback is sequential chunking until a 416, but the current path treats unknown size as fatal when a size was expected.","triggerScenarios":"Ranged GET response with 'Content-Range: bytes 0-1023/*' — server supports ranges but not total reporting; some object stores and CDNs do this for dynamically generated or streamed content.","commonSituations":"Downloading from a storage driver whose server omits totals; pre-signed URLs that hide object size; streaming endpoints.","solutions":["Use a driver/path that provides the file size (e.g. via metadata/list API) before starting the ranged download.","If you operate the server, include the total length in Content-Range.","For genuinely size-less sources, download sequentially without ranged chunking rather than through this downloader.","Clear stale cached metadata if the size was cached as -1/unknown."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"parts := strings.Split(contentRange, \"/\")\nif parts[len(parts)-1] == \"*\" {\n    // size unknown: choose sequential download path, skip chunk planner\n}","typeGuard":"strings.Contains(err.Error(), \"file size unknown\")","tryCatchPattern":"if err := d.checkTotalBytes(resp); err != nil {\n    if strings.Contains(err.Error(), \"file size unknown\") {\n        // fall back to sequential read-until-416 / single-stream download\n    }\n}","preventionTips":["Provide file size out-of-band (listing/metadata API) before ranged download.","Prefer endpoints that report totals; configure CDNs to include them.","Treat '*' totals as 'must not chunk-plan', not as zero."],"tags":["network","download","http-headers","content-range","go"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}