{"record":{"id":"b9267f2ea93cf7ec","repo":"GopeedLab/gopeed","slug":"too-many-redirects","errorCode":null,"errorMessage":"too many redirects","messagePattern":"too many redirects","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/download/engine/inject/xhr/module.go","lineNumber":247,"sourceCode":"\tif contentType != \"\" && (!isStringBody || xhr.requestHeaders.Get(\"Content-Type\") == \"\") {\n\t\treqBuilder.SetHeader(\"Content-Type\", contentType)\n\t}\n\n\t// Set timeout\n\tif xhr.Timeout > 0 {\n\t\txhr.client.SetTimeout(time.Duration(xhr.Timeout) * time.Millisecond)\n\t}\n\n\t// Configure redirect behavior\n\txhr.client.SetRedirectPolicy(func(req *http.Request, via []*http.Request) error {\n\t\tif xhr.Redirect == redirectManual {\n\t\t\treturn http.ErrUseLastResponse\n\t\t}\n\t\tif xhr.Redirect == redirectError {\n\t\t\treturn errors.New(\"redirect failed\")\n\t\t}\n\t\tif len(via) > 20 {\n\t\t\treturn errors.New(\"too many redirects\")\n\t\t}\n\t\treturn nil\n\t})\n\n\t// Execute request\n\tresp, err := reqBuilder.Send(xhr.method, xhr.url)\n\tif err != nil {\n\t\t// handle timeout error\n\t\tvar ne net.Error\n\t\tif errors.As(err, &ne) && ne.Timeout() {\n\t\t\tif xhr.Timeout > 0 {\n\t\t\t\txhr.Upload.callOntimeout()\n\t\t\t\txhr.callOntimeout()\n\t\t\t}\n\t\t\treturn\n\t\t}\n\t\txhr.Upload.callOnerror()\n\t\txhr.callOnerror()","sourceCodeStart":229,"sourceCodeEnd":265,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/pkg/download/engine/inject/xhr/module.go#L229-L265","documentation":"In the same redirect policy (pkg/download/engine/inject/xhr/module.go:246-248), once more than 20 redirects have been followed for a single request (len(via) > 20), the http client stops and returns 'too many redirects'. This mirrors the browser/fetch safety limit and protects against redirect loops between two or more URLs.","triggerScenarios":"Two URLs redirecting to each other (A -> B -> A ...) with redirect:'follow'; a redirect chain longer than 20 hops (common with ad/tracking chains or auth gates); a server that rewrites a URL and redirects to the rewritten form which again fails the rewrite; cookies/auth not being forwarded so the target keeps bouncing back to login -> original URL.","commonSituations":"Login-protected download URLs where the injected client lacks the session cookie, producing an infinite auth bounce; misconfigured vhosts alternating between www/non-www or http/https; CDN chains; relative Location headers that resolve back onto the redirecting endpoint.","solutions":["Trace the chain: run with redirect:'manual' in a loop (max ~5) and log each Location to find the loop pair","Fix the loop server-side (canonicalize host/scheme, correct rewrite rules)","If auth-bounce, attach the needed Cookie/Authorization header in the script's fetch options so the target stops redirecting","Request the final destination URL directly once identified"],"exampleFix":"// before (script)\nconst r = await fetch('http://a.example/file') // a <-> b redirect loop -> \"too many redirects\"\n\n// after (script)\nlet u = 'http://a.example/file'\nfor (let i = 0; i < 5; i++) {\n  const m = await fetch(u, { redirect: 'manual' })\n  if (m.status < 300 || m.status >= 400) break\n  console.log('hop:', u, '->', m.headers.get('location'))\n  u = new URL(m.headers.get('location'), u).href\n}\nconst r = await fetch(u)","handlingStrategy":"try-catch","validationCode":"// Script-side: bounded manual follow to detect loops before the 20-hop limit\nasync function fetchBounded(url, maxHops = 5) {\n    let u = url\n    for (let i = 0; i <= maxHops; i++) {\n        const m = await fetch(u, { redirect: 'manual' })\n        if (m.status < 300 || m.status >= 400) return m\n        const loc = m.headers.get('location')\n        if (!loc) throw new Error('redirect without location')\n        u = new URL(loc, u).href\n    }\n    throw new Error('redirect loop suspected at ' + u)\n}","typeGuard":null,"tryCatchPattern":"try {\n    const r = await fetch(url) // follow\n} catch (e) {\n    if (String(e).includes('too many redirects')) {\n        // hop-trace manually (see validationCode) to find the loop pair, then request the final URL directly\n    }\n}","preventionTips":["Trace short redirect chains manually with redirect:'manual' to catch loops early","Forward session cookies/auth headers so auth redirects terminate","Fix server-side canonicalization (single host/scheme) instead of chasing long chains"],"tags":["network","http","redirect","loop"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}