{"record":{"id":"9296bb3b4128ecd9","repo":"abi/screenshot-to-code","slug":"unsupported-protocol-parsed-scheme","errorCode":null,"errorMessage":"Unsupported protocol: {parsed.scheme}","messagePattern":"Unsupported protocol: (.+?)","errorType":"exception","errorClass":"ValueError","httpStatus":500,"severity":"warning","filePath":"backend/routes/screenshot.py","lineNumber":35,"sourceCode":"    # Parse the URL\n    parsed = urlparse(url)\n    \n    # Check if we have a scheme\n    if not parsed.scheme:\n        # No scheme, add https://\n        url = f\"https://{url}\"\n    elif parsed.scheme in ['http', 'https']:\n        # Valid scheme, keep as is\n        pass\n    else:\n        # Check if this might be a domain with port (like example.com:8080)\n        # urlparse treats this as scheme:netloc, but we want to handle it as domain:port\n        if ':' in url and not url.startswith(('http://', 'https://', 'ftp://', 'file://')):\n            # Likely a domain:port without protocol\n            url = f\"https://{url}\"\n        else:\n            # Invalid protocol\n            raise ValueError(f\"Unsupported protocol: {parsed.scheme}\")\n    \n    return url\n\n\ndef bytes_to_data_url(image_bytes: bytes, mime_type: str) -> str:\n    base64_image = base64.b64encode(image_bytes).decode(\"utf-8\")\n    return f\"data:{mime_type};base64,{base64_image}\"\n\n\nasync def capture_screenshot(\n    target_url: str, api_key: str, device: str = \"desktop\"\n) -> bytes:\n    api_base_url = \"https://api.screenshotone.com/take\"\n\n    params = {\n        \"access_key\": api_key,\n        \"url\": target_url,\n        \"full_page\": \"true\",","sourceCodeStart":17,"sourceCodeEnd":53,"githubUrl":"https://github.com/abi/screenshot-to-code/blob/d026163f586dfa8c5c10d28c36edd59a9d3b0e88/backend/routes/screenshot.py#L17-L53","documentation":"ValueError(\"Unsupported protocol: {scheme}\") raised by normalize_url() when urlparse assigns the URL a scheme that is not http/https and the fallback heuristics do not rescue it. The function auto-prefixes https:// for scheme-less inputs and for bare host:port strings, but explicitly refuses ftp://, file://, and anything else scheme-like (mailto:, javascript:, etc.). It is an input-rejection error, not an upstream failure.","triggerScenarios":"POST /api/screenshot (or any caller of normalize_url) with url=\"ftp://example.com\", url=\"file:///etc/passwd\", or url=\"mailto:someone@example.com\". Note \"example.com:8080\" does NOT trigger it — the host:port branch converts it to https://example.com:8080.","commonSituations":"Users paste an ftp:// or file:// link into the screenshot box; automated pipelines pass unvalidated URLs harvested from documents.","solutions":["Pass an http:// or https:// URL, or a bare domain like example.com (the function adds https:// itself)","If the target truly is ftp/file content, screenshotting is not supported — fetch the resource by another means","Validate the scheme client-side before calling the endpoint"],"exampleFix":"# before\nurl = \"ftp://example.com\"\nnormalized = normalize_url(url)  # ValueError\n\n# after\nurl = \"https://example.com\"\nnormalized = normalize_url(url)  # ok","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\n\ndef is_screenshotable_url(url: str) -> bool:\n    p = urlparse(url)\n    if p.scheme in (\"http\", \"https\"):\n        return True\n    return p.scheme == \"\" and bool(p.netloc or p.path)  # bare domain gets https:// prefixed","typeGuard":null,"tryCatchPattern":"try:\n    normalize_url(url)\nexcept ValueError as e:\n    if str(e).startswith(\"Unsupported protocol\"):\n        reject_input_to_user(url)  # input problem, not a server fault","preventionTips":["Validate the scheme client-side before calling /api/screenshot","Auto-prefix https:// in the UI for scheme-less input instead of sending raw paste","Reject ftp://, file://, mailto:, javascript: early with a clear user message"],"tags":["url-validation","valueerror","screenshot","user-input"],"backgroundTag":null,"analyzedSha":"d026163f586dfa8c5c10d28c36edd59a9d3b0e88","analyzedAt":"2026-08-14T22:02:06.951Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}