{"record":{"id":"2ef46f85fa5fb36d","repo":"tonhowtf/omniget","slug":"server-returned-html-instead-of-media","errorCode":null,"errorMessage":"server returned HTML instead of media","messagePattern":"server returned HTML instead of media","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/http_fetcher.rs","lineNumber":991,"sourceCode":"\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)\n        .await?;\n    file.seek(std::io::SeekFrom::Start(range_start)).await?;\n\n    let mut stream = resp.bytes_stream();\n    let mut written: u64 = 0;\n    let started = std::time::Instant::now();\n    tracing::debug!(\n        \"[http_fetcher] segment {}: GET bytes={}-{} ({} bytes)\",\n        seg.id,\n        range_start,\n        end,","sourceCodeStart":973,"sourceCodeEnd":1009,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/http_fetcher.rs#L973-L1009","documentation":"download_segment checks the Content-Type of the 206 response; if it is text/html the server is serving an error/interstitial page (login, block page, 404 HTML) instead of the file bytes, which must not be written into the target segment.","triggerScenarios":"The ranged GET returns 206/200 with Content-Type: text/html — typically a captive portal, antivirus/proxy block page, SSO login redirect, or a soft-404 HTML page.","commonSituations":"Hotel/office captive portals intercepting HTTPS-failed requests; expired signed URLs returning an HTML error page; corporate proxies injecting HTML; hotspot DNS hijacking.","solutions":["Check network connectivity / captive-portal status and re-authenticate","Verify the URL is correct and still valid (HTML pages often mask 403/404)","Bypass or reconfigure intercepting proxies/SSL-inspection appliances","Retry the segment; if HTML persists, fail the download with a clear network-error message"],"exampleFix":"// response: Content-Type: text/html -> block/login page\n// fix: detect portal and retry after re-auth\nif body.starts_with(b\"<\") { reconnect_to_portal(); retry_segment(); }","handlingStrategy":"type-guard","validationCode":"let ct = resp.headers().get(reqwest::header::CONTENT_TYPE)\n    .and_then(|v| v.to_str().ok()).unwrap_or(\"\");\nif ct.contains(\"text/html\") {\n    eprintln!(\"captive portal or error page; abort download\");\n    return;\n}","typeGuard":"fn is_media_response(resp: &reqwest::Response) -> bool {\n    resp.headers().get(reqwest::header::CONTENT_TYPE)\n        .and_then(|v| v.to_str().ok())\n        .map(|s| !s.contains(\"text/html\"))\n        .unwrap_or(true)\n}","tryCatchPattern":"match download(url).await {\n    Err(e) if e.to_string().contains(\"HTML instead of media\") => {\n        show_captive_portal_prompt(); // ask user to log in / fix network\n    }\n    Err(e) => return Err(e),\n    Ok(f) => f,\n}","preventionTips":["Detect captive portals before starting downloads (probe a known endpoint)","Validate Content-Type on HEAD before segmenting","Surface a user-friendly 'network blocked' message instead of raw error","Whitelist/bypass SSL-inspection proxies for the download host"],"tags":["http","content-type","network"],"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"}