{"record":{"id":"7e66c9b8a303a4eb","repo":"abiosoft/colima","slug":"invalid-url-s-w","errorCode":null,"errorMessage":"invalid URL '%s': %w","messagePattern":"invalid URL '(.+?)': %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/downloader/http.go","lineNumber":76,"sourceCode":"\t\tResponseHeaderTimeout: 30 * time.Second,\n\t\tExpectContinueTimeout: 1 * time.Second,\n\t}\n\n\treturn &HTTPClient{\n\t\tclient: &http.Client{\n\t\t\tTransport: transport,\n\t\t\t// checkRedirect is left default - Go follows up to 10 redirects\n\t\t\t// and returns the final response\n\t\t},\n\t\tuserAgent: \"colima/\" + config.AppVersion().Version,\n\t}\n}\n\n// GetFinalURL follows redirects and returns the final URL\nfunc (h *HTTPClient) GetFinalURL(ctx context.Context, rawURL string) (string, error) {\n\treq, err := http.NewRequestWithContext(ctx, http.MethodHead, rawURL, nil)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"invalid URL '%s': %w\", rawURL, err)\n\t}\n\treq.Header.Set(\"User-Agent\", h.userAgent)\n\n\tresp, err := h.client.Do(req)\n\tif err != nil {\n\t\treturn \"\", &NetworkError{Op: \"resolve redirect\", URL: rawURL, Err: err}\n\t}\n\tdefer func() { _ = resp.Body.Close() }()\n\n\t// check for HTTP errors\n\tif resp.StatusCode >= 400 {\n\t\treturn \"\", &HTTPStatusError{\n\t\t\tStatusCode: resp.StatusCode,\n\t\t\tStatus:     resp.Status,\n\t\t\tURL:        rawURL,\n\t\t}\n\t}\n","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/abiosoft/colima/blob/c3a5f9184d83a197184f897a9f07eb3c01b3bc88/util/downloader/http.go#L58-L94","documentation":"GetFinalURL builds a HEAD request from rawURL and http.NewRequestWithContext rejected the URL string before any network traffic: it does not parse as a request target (control characters, invalid percent-escapes, missing/unsupported scheme). This is a malformed-input error, not a network failure.","triggerScenarios":"Passing a URL containing spaces, tabs, or newlines (e.g. concatenated with an unescaped version string like '1.0 beta'), invalid % sequences, or no scheme; url.ParseRequestURI fails on the same input.","commonSituations":"Templated download URLs with unescaped dynamic segments; env-provided URLs with trailing whitespace; copy-paste from docs adding invisible characters.","solutions":["Validate with url.ParseRequestURI before calling GetFinalURL and reject non-http(s) schemes","Trim whitespace and percent-encode dynamic path segments with url.PathEscape","Log the raw URL: the %s in the message shows the exact offending string","Reproduce with curl -I <url> — curl fails loudly on the same malformed input"],"exampleFix":"// before\nfinalURL, err := client.GetFinalURL(ctx, u)\n// after\nu = strings.TrimSpace(u)\nif _, err := url.ParseRequestURI(u); err != nil {\n    return fmt.Errorf(\"invalid download url %q: %w\", u, err)\n}\nfinalURL, err := client.GetFinalURL(ctx, u)","handlingStrategy":"validation","validationCode":"u := strings.TrimSpace(rawURL)\nif _, err := url.ParseRequestURI(u); err != nil {\n    return fmt.Errorf(\"invalid download url %q: %w\", u, err)\n}\nparsed, err := url.Parse(u)\nif err != nil || (parsed.Scheme != \"http\" && parsed.Scheme != \"https\") {\n    return fmt.Errorf(\"url must be absolute http(s): %q\", u)\n}\nfinalURL, err := client.GetFinalURL(ctx, u)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate URLs once at config load time, not per request","Build URLs from url.URL (u.JoinPath, url.PathEscape) instead of string concatenation","Add table-driven tests with ugly inputs (spaces, newlines, empty) for URL sources"],"tags":["url","validation","download","http"],"backgroundTag":null,"analyzedSha":"c3a5f9184d83a197184f897a9f07eb3c01b3bc88","analyzedAt":"2026-08-15T18:58:08.334Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}