{"record":{"id":"17803857b0381ad5","repo":"AlistGo/alist","slug":"chunknum-invalid","errorCode":null,"errorMessage":"chunkNum invalid","messagePattern":"chunkNum invalid","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"drivers/115/util.go","lineNumber":525,"sourceCode":"\tif fileSize > 9*utils.GB { // 文件大小大于9GB时分为10000片\n\t\tif chunks, err = SplitFileByPartNum(fileSize, 10000); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\t// 单个分片大小不能小于100KB\n\tif chunks[0].Size < 100*utils.KB {\n\t\tif chunks, err = SplitFileByPartSize(fileSize, 100*utils.KB); err != nil {\n\t\t\treturn\n\t\t}\n\t}\n\treturn\n}\n\n// SplitFileByPartNum splits big file into parts by the num of parts.\n// Split the file with specified parts count, returns the split result when error is nil.\nfunc SplitFileByPartNum(fileSize int64, chunkNum int) ([]oss.FileChunk, error) {\n\tif chunkNum <= 0 || chunkNum > 10000 {\n\t\treturn nil, errors.New(\"chunkNum invalid\")\n\t}\n\n\tif int64(chunkNum) > fileSize {\n\t\treturn nil, errors.New(\"oss: chunkNum invalid\")\n\t}\n\n\tvar chunks []oss.FileChunk\n\tchunk := oss.FileChunk{}\n\tchunkN := (int64)(chunkNum)\n\tfor i := int64(0); i < chunkN; i++ {\n\t\tchunk.Number = int(i + 1)\n\t\tchunk.Offset = i * (fileSize / chunkN)\n\t\tif i == chunkN-1 {\n\t\t\tchunk.Size = fileSize/chunkN + fileSize%chunkN\n\t\t} else {\n\t\t\tchunk.Size = fileSize / chunkN\n\t\t}\n\t\tchunks = append(chunks, chunk)","sourceCodeStart":507,"sourceCodeEnd":543,"githubUrl":"https://github.com/AlistGo/alist/blob/843d9dc8149126976b2625911e45a4d3ffd6f2f5/drivers/115/util.go#L507-L543","documentation":"Error path in alist's ServeHTTP for a multi-range request ('Range: bytes=0-99,200-299'). Before streaming the multipart/byteranges response, the server pre-computes the encoded size via rangesMIMESize (internal/net/util.go:290); that function only fails when multipart.Writer.CreatePart cannot format the part header, i.e. when mime.FormatMediaType rejects the Content-Type (malformed media type parameters). On failure the error is sent with HTTP 416. Note a defect in this branch: unlike the other two paths there is no `return nil` after http.Error (serve.go:158), so execution continues, sets code=206, and later WriteHeader calls become superfluous, corrupting the response.","triggerScenarios":"A client sends multiple ranges in one request AND the effective Content-Type is malformed — e.g. an upstream-mounted file server returns a Content-Type with invalid parameters (unquoted special characters, bad boundary, stray semicolons), which alist copies onto w.Header() before ServeHTTP runs. ParseRange has already succeeded and sizes are valid; the failure is purely in MIME header formatting.","commonSituations":"Exotic download accelerators and some CLI tools (curl 7.x multi-URL, aria2 in segment mode) that batch ranges; proxying/mounting remote servers that emit non-conformal Content-Type headers; custom code that sets Content-Type manually with unquoted parameter values before calling ServeHTTP; rarely triggered because most clients send one range per request.","solutions":["Capture the exact Content-Type being served (curl -I) and sanitize it: strip or quote parameters, or override it to a clean value like 'application/octet-stream' before the response is written.","As a client, split the multi-range request into separate single-range requests — the single-range path (len(ranges)==1) does not call rangesMIMESize and avoids the failure entirely.","If you control the caller of ServeHTTP, normalize w.Header().Get(\"Content-Type\") with mime.ParseMediaType + mime.FormatMediaType (dropping bad params) before invoking it.","If you maintain alist itself, add `return nil` after the http.Error call at serve.go:158 so the handler stops instead of double-writing headers."],"exampleFix":"// before (alist-internal, serve.go): missing return corrupts the response\nsendSize, err = rangesMIMESize(ranges, contentType, size)\nif err != nil {\n    http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)\n}\ncode = http.StatusPartialContent\n\n// after: stop after reporting the error\nsendSize, err = rangesMIMESize(ranges, contentType, size)\nif err != nil {\n    http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)\n    return nil\n}\ncode = http.StatusPartialContent","handlingStrategy":"validation","validationCode":"// Before serving, normalize the Content-Type so mime.FormatMediaType cannot fail:\nfunc sanitizeContentType(ct string) string {\n    mt, params, err := mime.ParseMediaType(ct)\n    if err != nil {\n        return \"application/octet-stream\"\n    }\n    clean, err := mime.FormatMediaType(mt, params)\n    if err != nil {\n        return \"application/octet-stream\"\n    }\n    return clean\n}\n// client-side: avoid multi-range requests entirely\nfunc requestRanges(url string, ranges [][2]int64) ([]*http.Response, error) {\n    // one request per range -> single-range path, rangesMIMESize never runs\n    for _, rg := range ranges {\n        req, _ := http.NewRequest(\"GET\", url, nil)\n        req.Header.Set(\"Range\", fmt.Sprintf(\"bytes=%d-%d\", rg[0], rg[1]))\n        resp, err := http.DefaultClient.Do(req)\n        if err != nil || resp.StatusCode >= 400 {\n            return nil, fmt.Errorf(\"range %v failed: %v\", rg, err)\n        }\n        _ = resp\n    }\n    return nil, nil\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Sanitize upstream Content-Type headers (parse then re-format via mime.ParseMediaType/FormatMediaType) before they reach ServeHTTP.","Prefer one range per request; multi-range responses are poorly supported by clients anyway.","If maintaining alist, add the missing `return nil` after http.Error at serve.go:158 to prevent header corruption.","Alert on 'http: superfluous response.WriteHeader call' in logs — it is a symptom of this branch continuing after the error."],"tags":["go","http","multipart","mime","range-request","alist","upstream-header"],"backgroundTag":null,"analyzedSha":"843d9dc8149126976b2625911e45a4d3ffd6f2f5","analyzedAt":"2026-08-15T12:14:11.722Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}