AlistGo/alist · warning
chunkNum invalid
Error message
chunkNum invalid
What it means
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.
Source
Thrown at drivers/115/util.go:525
if fileSize > 9*utils.GB { // 文件大小大于9GB时分为10000片
if chunks, err = SplitFileByPartNum(fileSize, 10000); err != nil {
return
}
}
// 单个分片大小不能小于100KB
if chunks[0].Size < 100*utils.KB {
if chunks, err = SplitFileByPartSize(fileSize, 100*utils.KB); err != nil {
return
}
}
return
}
// SplitFileByPartNum splits big file into parts by the num of parts.
// Split the file with specified parts count, returns the split result when error is nil.
func SplitFileByPartNum(fileSize int64, chunkNum int) ([]oss.FileChunk, error) {
if chunkNum <= 0 || chunkNum > 10000 {
return nil, errors.New("chunkNum invalid")
}
if int64(chunkNum) > fileSize {
return nil, errors.New("oss: chunkNum invalid")
}
var chunks []oss.FileChunk
chunk := oss.FileChunk{}
chunkN := (int64)(chunkNum)
for i := int64(0); i < chunkN; i++ {
chunk.Number = int(i + 1)
chunk.Offset = i * (fileSize / chunkN)
if i == chunkN-1 {
chunk.Size = fileSize/chunkN + fileSize%chunkN
} else {
chunk.Size = fileSize / chunkN
}
chunks = append(chunks, chunk)View on GitHub (pinned to 843d9dc814)
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.
Example fix
// before (alist-internal, serve.go): missing return corrupts the response
sendSize, err = rangesMIMESize(ranges, contentType, size)
if err != nil {
http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)
}
code = http.StatusPartialContent
// after: stop after reporting the error
sendSize, err = rangesMIMESize(ranges, contentType, size)
if err != nil {
http.Error(w, err.Error(), http.StatusRequestedRangeNotSatisfiable)
return nil
}
code = http.StatusPartialContent Defensive patterns
Strategy: validation
Validate before calling
// Before serving, normalize the Content-Type so mime.FormatMediaType cannot fail:
func sanitizeContentType(ct string) string {
mt, params, err := mime.ParseMediaType(ct)
if err != nil {
return "application/octet-stream"
}
clean, err := mime.FormatMediaType(mt, params)
if err != nil {
return "application/octet-stream"
}
return clean
}
// client-side: avoid multi-range requests entirely
func requestRanges(url string, ranges [][2]int64) ([]*http.Response, error) {
// one request per range -> single-range path, rangesMIMESize never runs
for _, rg := range ranges {
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("Range", fmt.Sprintf("bytes=%d-%d", rg[0], rg[1]))
resp, err := http.DefaultClient.Do(req)
if err != nil || resp.StatusCode >= 400 {
return nil, fmt.Errorf("range %v failed: %v", rg, err)
}
_ = resp
}
return nil, nil
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- not get file info
- missing cookie or qrcode account
- can't create RangeReadCloser since URL is empty in link
- http request failure, err:%s
- up status: %d, error: %s
AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15).
Data as JSON: /api/errors/17803857b0381ad5.
Report an issue: GitHub.