{"record":{"id":"f5a478a017209791","repo":"garrytan/gstack","slug":"invalid-url-url","errorCode":null,"errorMessage":"Invalid URL: ${url}","messagePattern":"Invalid URL: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"browse/src/url-validation.ts","lineNumber":239,"sourceCode":" *\n * Callers (keep this list current, grep before removing):\n *   - write-commands.ts:goto\n *   - meta-commands.ts:diff (both URL args)\n *   - browser-manager.ts:newTab\n *   - browser-manager.ts:restoreState\n */\nexport async function validateNavigationUrl(url: string): Promise<string> {\n  // Normalize non-standard file:// shapes before the URL parser sees them.\n  let normalized = url;\n  if (url.toLowerCase().startsWith('file:')) {\n    normalized = normalizeFileUrl(url);\n  }\n\n  let parsed: URL;\n  try {\n    parsed = new URL(normalized);\n  } catch {\n    throw new Error(`Invalid URL: ${url}`);\n  }\n\n  // file:// path: validate against safe-dirs and allow; otherwise defer to http(s) logic.\n  if (parsed.protocol === 'file:') {\n    // Reject non-empty non-localhost hosts (UNC / network paths).\n    if (parsed.host !== '' && parsed.host.toLowerCase() !== 'localhost') {\n      throw new Error(\n        `Unsupported file URL host: ${parsed.host}. Use file:///<absolute-path> for local files.`\n      );\n    }\n\n    // Convert URL → filesystem path with proper decoding (handles %20, %2F, etc.)\n    // fileURLToPath strips query + hash; we reattach them after validation so SPA\n    // fixture URLs like file:///tmp/app.html?route=home#login survive intact.\n    let fsPath: string;\n    try {\n      fsPath = fileURLToPath(parsed);\n    } catch (e: any) {","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/browse/src/url-validation.ts#L221-L257","documentation":"Thrown by validateNavigationUrl when the (already-normalized) URL cannot be parsed by the standard URL constructor. This is the catch-all for malformed inputs that survived normalizeFileUrl but are still not a valid http/https/file URL — empty strings, embedded whitespace, missing scheme, broken percent-encoding, or unsupported schemes that the parser rejects outright.","triggerScenarios":"Calling goto with `''`, `'ht tp://x'` (embedded space), `'example.com'` (no scheme — the parser requires one), `'javascript:void(0)'` after the scheme blocklist, or any string the WHATWG URL constructor rejects.","commonSituations":"User input without a scheme; copy-paste introducing a leading/trailing space or smart quote; templating that left the URL empty when a variable was undefined; non-ASCII hostnames without IDNA normalization.","solutions":["Ensure the URL has an explicit http://, https://, or file:// scheme.","Trim whitespace and reject empty strings before calling validateNavigationUrl: `if (!url.trim()) throw ...`.","Prepend `https://` when the user types a bare hostname: `url = /^https?:\\/\\//.test(s) ? s : 'https://' + s`.","Validate with `new URL(url)` in a try/catch at the caller boundary to give a friendlier error."],"exampleFix":"// before\nawait goto(userInput); // userInput === 'example.com' → throws\n// after\nconst safe = /^https?:\\/\\//i.test(userInput) ? userInput : `https://${userInput}`;\nawait goto(safe);","handlingStrategy":"validation","validationCode":"function normalizeUserUrl(input: string): string {\n  const trimmed = input.trim();\n  if (!trimmed) throw new Error('Invalid URL: empty input');\n  if (/^file:/i.test(trimmed)) return trimmed; // file: handled by normalizeFileUrl\n  const withScheme = /^[a-z][a-z0-9+.-]*:\\/\\//i.test(trimmed) ? trimmed : `https://${trimmed}`;\n  try { new URL(withScheme); } catch { throw new Error(`Invalid URL: ${input}`); }\n  return withScheme;\n}","typeGuard":"function isValidUrl(u: string): boolean {\n  try { new URL(u); return true; } catch { return false; }\n}","tryCatchPattern":"try {\n  await goto(url);\n} catch (e: any) {\n  if (/^Invalid URL:/.test(e.message)) {\n    const fixed = /^https?:\\/\\//i.test(url) ? url : `https://${url}`;\n    await goto(fixed);\n  } else throw e;\n}","preventionTips":["Trim and reject empty strings before calling validateNavigationUrl.","Prepend https:// when the user input is a bare hostname.","Run `new URL(input)` at your own boundary to give a friendlier error.","Strip smart quotes and stray whitespace from pasted URLs."],"tags":["url-validation","navigation","input-validation","scheme"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}