{"record":{"id":"2690f9297282fc86","repo":"tonhowtf/omniget","slug":"http-downloading","errorCode":null,"errorMessage":"HTTP {} downloading {}","messagePattern":"HTTP (.+?) downloading (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-tauri/omniget-core/src/core/direct_downloader.rs","lineNumber":504,"sourceCode":"        if let Some(total) = total_size {\n            if existing_bytes >= total {\n                return Ok(());\n            }\n        }\n        request = request.header(\"Range\", format!(\"bytes={}-\", existing_bytes));\n    }\n\n    let response = request.send().await?;\n\n    let mut offset = 0u64;\n    if existing_bytes > 0 {\n        if response.status() == reqwest::StatusCode::PARTIAL_CONTENT {\n            offset = existing_bytes;\n        } else if response.status() == reqwest::StatusCode::RANGE_NOT_SATISFIABLE {\n            let _ = std::fs::remove_file(part_path);\n            return Err(anyhow!(\"Range not satisfiable, restarting\"));\n        } else if !response.status().is_success() {\n            return Err(anyhow!(\"HTTP {} downloading {}\", response.status(), url));\n        }\n    } else if !response.status().is_success() {\n        return Err(anyhow!(\"HTTP {} downloading {}\", response.status(), url));\n    }\n\n    if let Some(ct) = response.headers().get(\"content-type\") {\n        if let Ok(ct_str) = ct.to_str() {\n            if ct_str.contains(\"text/html\") {\n                return Err(anyhow!(\n                    \"Server returned HTML instead of media — URL may have expired\"\n                ));\n            }\n        }\n    }\n\n    use std::io::Write;\n    let raw_file = if offset > 0 {\n        std::fs::OpenOptions::new().append(true).open(part_path)?","sourceCodeStart":486,"sourceCodeEnd":522,"githubUrl":"https://github.com/tonhowtf/omniget/blob/8600b91f4246848bac346874daa9e61c1fc5677a/src-tauri/omniget-core/src/core/direct_downloader.rs#L486-L522","documentation":"download_single_stream checks the HTTP status before reading the body: for both a fresh download (no existing bytes) and a resume where the server did not return 206 or 416, any non-success status (4xx/5xx other than the two handled cases) aborts the attempt with 'HTTP <status> downloading <url>'. It deliberately embeds the status code and URL so the caller can see exactly which request failed and why class of failure (auth, not-found, server error).","triggerScenarios":"Any request during download/download_direct/download_direct_with_headers where the server replies with a non-2xx status — 404 for a removed file, 403 for forbidden/hotlink-protected content, 401 for missing auth, 429 rate-limiting, or 5xx server errors. For resumes, 200 instead of 206 also lands here (falls through to the non-success branch only if... it is success, so in practice 4xx/5xx).","commonSituations":"URL typo'd or file deleted (404); hotlink protection or missing Referer/Cookie/Authorization headers (403/401); aggressive rate limiting (429) especially across retry loops; temporary upstream outages (502/503); expired signed URLs returning 403.","solutions":["Read the embedded status code: 404 -> verify the URL/link still exists; 403/401 -> supply credentials via download_direct_with_headers headers (Authorization, Cookie, Referer); 429 -> back off and retry later; 5xx -> retry after the server recovers.","Test the exact URL with curl -I to reproduce the status outside the app and inspect response headers (WWW-Authenticate, Retry-After) for the fix.","Refresh expired signed/tokenized URLs before downloading.","Add required headers (User-Agent, Referer) that hotlink-protected CDNs demand; a missing browser-like User-Agent commonly causes 403.","If 5xx persists across retries, switch mirrors or inform the user the source is temporarily unavailable."],"exampleFix":"// before\nlet file = downloader.download_direct(\"https://cdn.example.com/gone.mp4\", &out).await?; // HTTP 403 downloading ...\n// after\nlet mut headers = HeaderMap::new();\nheaders.insert(header::REFERER, \"https://example.com/page\".parse()?);\nheaders.insert(header::USER_AGENT, \"Mozilla/5.0\".parse()?);\nheaders.insert(header::COOKIE, format!(\"session={}\", session_cookie).parse()?);\nlet file = downloader.download_direct_with_headers(&url, &out, &headers).await?;","handlingStrategy":"try-catch","validationCode":"// Rust: classify the URL's status before committing to a download\nasync fn check_status(client: &reqwest::Client, url: &str) -> Result<u16, String> {\n    let r = client.head(url).send().await.map_err(|e| e.to_string())?;\n    Ok(r.status().as_u16())\n} // 403/401 -> add headers; 404 -> refresh link; 429 -> wait; else proceed","typeGuard":null,"tryCatchPattern":"match downloader.download(&url, &out).await {\n    Err(e) if e.to_string().starts_with(\"HTTP \") => {\n        let msg = e.to_string(); // e.g. 'HTTP 403 downloading https://...'\n        let status: u16 = msg.split_whitespace().nth(1).and_then(|s| s.parse().ok()).unwrap_or(0);\n        match status {\n            401 | 403 => Err(anyhow!(\"auth/hotlink blocked; supply headers\")),\n            404 => Err(anyhow!(\"file gone; refresh link\")),\n            429 => Err(anyhow!(\"rate limited; back off\")),\n            _ => Err(anyhow!(\"server error; retry later\")),\n        }\n    }\n    other => other,\n}","preventionTips":["Pre-flight HEAD the URL and branch on the status before downloading","Send browser-like User-Agent plus Referer/Cookie/Authorization where the CDN expects them","Refresh signed URLs before they expire to avoid 403s","Honor Retry-After on 429/503 instead of hammering the retry loop"],"tags":["http","download","http-status","network","rust"],"backgroundTag":"http-error-response","analyzedSha":"8600b91f4246848bac346874daa9e61c1fc5677a","analyzedAt":"2026-09-12T14:29:19.317Z","contentChangedAt":"2026-09-12T14:29:19.317Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}