tonhowtf/omniget · error · anyhow::Error

server returned HTML instead of media

Error message

server returned HTML instead of media

What it means

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.

Solutions

  1. Check network connectivity / captive-portal status and re-authenticate
  2. Verify the URL is correct and still valid (HTML pages often mask 403/404)
  3. Bypass or reconfigure intercepting proxies/SSL-inspection appliances
  4. Retry the segment; if HTML persists, fail the download with a clear network-error message

Example fix

// response: Content-Type: text/html -> block/login page
// fix: detect portal and retry after re-auth
if body.starts_with(b"<") { reconnect_to_portal(); retry_segment(); }
Defensive patterns

Strategy: type-guard

Validate before calling

let ct = resp.headers().get(reqwest::header::CONTENT_TYPE)
    .and_then(|v| v.to_str().ok()).unwrap_or("");
if ct.contains("text/html") {
    eprintln!("captive portal or error page; abort download");
    return;
}

Type guard

fn is_media_response(resp: &reqwest::Response) -> bool {
    resp.headers().get(reqwest::header::CONTENT_TYPE)
        .and_then(|v| v.to_str().ok())
        .map(|s| !s.contains("text/html"))
        .unwrap_or(true)
}

Try / catch

match download(url).await {
    Err(e) if e.to_string().contains("HTML instead of media") => {
        show_captive_portal_prompt(); // ask user to log in / fix network
    }
    Err(e) => return Err(e),
    Ok(f) => f,
}

Prevention

When it happens

Trigger: 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.

Common situations: Hotel/office captive portals intercepting HTTPS-failed requests; expired signed URLs returning an HTML error page; corporate proxies injecting HTML; hotspot DNS hijacking.

Related errors


AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12). Data as JSON: /api/errors/2ef46f85fa5fb36d. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/omniget-core/src/core/http_fetcher.rs:991

    // A 206 alone is not proof the server gave us the slice we asked for. If it
    // answers a different offset and we write the body at ours anyway, the file
    // still ends up the right size and is silently corrupt — the one failure
    // mode a segmented download cannot detect later.
    if let Some(start) = content_range_start(resp.headers()) {
        if start != range_start {
            return Err(anyhow!(
                "server answered range at byte {} but {} was requested",
                start,
                range_start
            ));
        }
    }

    if let Some(ct) = resp.headers().get(reqwest::header::CONTENT_TYPE) {
        if let Ok(s) = ct.to_str() {
            if s.contains("text/html") {
                return Err(anyhow!("server returned HTML instead of media"));
            }
        }
    }

    let mut file = tokio::fs::OpenOptions::new()
        .write(true)
        .open(part_path)
        .await?;
    file.seek(std::io::SeekFrom::Start(range_start)).await?;

    let mut stream = resp.bytes_stream();
    let mut written: u64 = 0;
    let started = std::time::Instant::now();
    tracing::debug!(
        "[http_fetcher] segment {}: GET bytes={}-{} ({} bytes)",
        seg.id,
        range_start,
        end,

View on GitHub (pinned to 8600b91f42)