{"record":{"id":"77dcfb5237419003","repo":"tonhowtf/omniget","slug":"server-answered-range-at-byte-but-was-requested","errorCode":null,"errorMessage":"server answered range at byte {} but {} was requested","messagePattern":"server answered range at byte (.+?) but (.+?) was requested","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/http_fetcher.rs","lineNumber":980,"sourceCode":"    let resp = tokio::time::timeout(cfg.connect_timeout, req.send())\n        .await\n        .map_err(|_| anyhow!(\"connect timeout\"))??;\n\n    let status = resp.status();\n    if status != reqwest::StatusCode::PARTIAL_CONTENT {\n        if status.is_success() {\n            return Err(anyhow!(\"server did not honor Range (HTTP 200)\"));\n        }\n        return Err(anyhow!(\"HTTP {}\", status));\n    }\n\n    // A 206 alone is not proof the server gave us the slice we asked for. If it\n    // answers a different offset and we write the body at ours anyway, the file\n    // still ends up the right size and is silently corrupt — the one failure\n    // mode a segmented download cannot detect later.\n    if let Some(start) = content_range_start(resp.headers()) {\n        if start != range_start {\n            return Err(anyhow!(\n                \"server answered range at byte {} but {} was requested\",\n                start,\n                range_start\n            ));\n        }\n    }\n\n    if let Some(ct) = resp.headers().get(reqwest::header::CONTENT_TYPE) {\n        if let Ok(s) = ct.to_str() {\n            if s.contains(\"text/html\") {\n                return Err(anyhow!(\"server returned HTML instead of media\"));\n            }\n        }\n    }\n\n    let mut file = tokio::fs::OpenOptions::new()\n        .write(true)\n        .open(part_path)","sourceCodeStart":962,"sourceCodeEnd":998,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/http_fetcher.rs#L962-L998","documentation":"Even with a 206 response, download_segment validates the Content-Range start offset against the requested range_start. A 206 for a different offset would be written at the wrong file position, producing a right-sized but corrupt file, so the mismatch is rejected.","triggerScenarios":"Server returns 206 whose Content-Range start differs from the requested range_start, e.g. answering bytes=200- instead of the requested bytes=100-, or resuming from a cached/stale offset.","commonSituations":"Misbehaving CDNs or caching layers that rewrite Content-Range; servers that normalize or clamp the requested range; buggy origin scripts computing their own offsets.","solutions":["Inspect the Content-Range header returned by the server for the exact Range request sent","Disable or bypass caching layers/CDN transforms for range requests","Drop and rebuild any persistent connection pools after noticing stale offsets","Retry the segment with a fresh connection; if the server keeps mis-slicing, fall back to single-stream download"],"exampleFix":"// request: Range: bytes=100-199\n// response: Content-Range: bytes 200-299/1000 -> error\n// fix: verify server honors exact offsets\ncurl -sD - -o /dev/null -H \"Range: bytes=100-199\" https://example.com/file.bin | grep Content-Range","handlingStrategy":"validation","validationCode":"// verify exact offset honoring before trusting the server\nlet r = client.get(url).header(\"Range\", \"bytes=100-199\").send().await?;\nlet cr = r.headers().get(\"content-range\").and_then(|v| v.to_str().ok()).unwrap_or(\"\");\nif !cr.starts_with(\"bytes 100-\") { eprintln!(\"server rewrites ranges; disable segmentation\"); }","typeGuard":"fn content_range_matches(hdr: Option<&reqwest::header::HeaderValue>, want: u64) -> bool {\n    hdr.and_then(|h| h.to_str().ok())\n       .and_then(|s| s.strip_prefix(\"bytes \"))\n       .and_then(|s| s.split('-').next())\n       .and_then(|s| s.parse::<u64>().ok())\n       .map(|start| start == want)\n       .unwrap_or(false)\n}","tryCatchPattern":"if let Err(e) = segment.download().await {\n    if e.to_string().contains(\"answered range at byte\") {\n        tracing::warn!(\"server mis-slices ranges; restarting segment on new connection\");\n        segment.download_fresh_connection().await?;\n    } else { return Err(e); }\n}","preventionTips":["Validate a server's Content-Range behavior once and cache the result","Avoid caching proxies/CDN transforms for range requests","Use fresh connections when resuming segments","Add integrity checks (hash) for assembled files"],"tags":["http","range-requests","data-corruption"],"backgroundTag":"unexpected-response-shape","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}