{"record":{"id":"a2ad208c0fd8b709","repo":"angular/angular","slug":"bad-url-cannot-parse-url-url","errorCode":null,"errorMessage":"Bad URL - Cannot parse URL: ${url}","messagePattern":"Bad URL - Cannot parse URL: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/common/upgrade/src/location_shim.ts","lineNumber":365,"sourceCode":"  private getServerBase() {\n    const {protocol, hostname, port} = this.platformLocation;\n    const baseHref = this.locationStrategy.getBaseHref();\n    let url = `${protocol}//${hostname}${port ? ':' + port : ''}${baseHref || '/'}`;\n    return url.endsWith('/') ? url : url + '/';\n  }\n\n  private parseAppUrl(url: string) {\n    if (DOUBLE_SLASH_REGEX.test(url)) {\n      throw new Error(`Bad Path - URL cannot start with double slashes: ${url}`);\n    }\n\n    let prefixed = url.charAt(0) !== '/';\n    if (prefixed) {\n      url = '/' + url;\n    }\n    let match = this.urlCodec.parse(url, this.getServerBase());\n    if (typeof match === 'string') {\n      throw new Error(`Bad URL - Cannot parse URL: ${url}`);\n    }\n    let path =\n      prefixed && match.pathname.charAt(0) === '/' ? match.pathname.substring(1) : match.pathname;\n    this.$$path = this.urlCodec.decodePath(path);\n    this.$$search = this.urlCodec.decodeSearch(match.search);\n    this.$$hash = this.urlCodec.decodeHash(match.hash);\n\n    // make sure path starts with '/';\n    if (this.$$path && this.$$path.charAt(0) !== '/') {\n      this.$$path = '/' + this.$$path;\n    }\n  }\n\n  /**\n   * Registers listeners for URL changes. This API is used to catch updates performed by the\n   * AngularJS framework. These changes are a subset of the `$locationChangeStart` and\n   * `$locationChangeSuccess` events which fire when AngularJS updates its internally-referenced\n   * version of the browser URL.","sourceCodeStart":347,"sourceCodeEnd":383,"githubUrl":"https://github.com/angular/angular/blob/51cb07e98081ab7e4e84a9e0949266a8e19cca84/packages/common/upgrade/src/location_shim.ts#L347-L383","documentation":"After the double-slash check passes, parseAppUrl() delegates to UrlCodec.parse(url, serverBase); the codec signals failure by returning a string instead of a parsed object, and the shim converts that into 'Bad URL - Cannot parse URL'. It means the URL survived the first check but is still unparseable relative to the server base.","triggerScenarios":"$location.url('http://:8080/x') (malformed host:port), URLs with invalid percent-encodings that later decoding rejects, or unusual base tags making the codec return an error string in a hybrid upgrade app.","commonSituations":"Raw window.location or backend-supplied URLs pushed into $location during upgrade; <base href> mismatches; hand-assembled URL strings with bad encoding.","solutions":["Pass simple app URLs of the form '/path?query#hash' to $location","Normalize/encode before setting: encodeURI on dynamic segments","Verify the <base href> and LocationUpgradeModule serverBase match the URLs being parsed"],"exampleFix":"// before\n$location.url(externalUrl); // raw cross-origin/oddly-encoded URL\n\n// after\nconst appUrl = new URL(externalUrl);\n$location.url(appUrl.pathname + appUrl.search + appUrl.hash);","handlingStrategy":"validation","validationCode":"function parseableUrl(url: string, base: string): boolean {\n  try { new URL(url, base); return true; } catch { return false; }\n}\nif (!parseableUrl(candidate, serverBase)) throw new Error('Refusing unparseable URL');\n$location.url(candidate);","typeGuard":"function isWellFormedUrlCandidate(u: string): boolean {\n  try { new URL(u, 'http://localhost/'); return true; } catch { return false; }\n}","tryCatchPattern":null,"preventionTips":["Set simple '/path?query#hash' values on $location instead of raw external URLs","Encode dynamic segments before assembling URLs","Keep <base href> consistent with the shim's serverBase in hybrid apps"],"tags":["angularjs-upgrade","location","url-parsing","hybrid"],"backgroundTag":"url-parsing-failed","analyzedSha":"51cb07e98081ab7e4e84a9e0949266a8e19cca84","analyzedAt":"2026-08-22T07:04:54.531Z","schemaVersion":2},"datasetVersion":"2026-08-22T09:17:25.309Z"}