(
urlStr: string | undefined,
origin?: string | URL,
options: ResolveUrlOptions = {},
)
| 54 | options?: ResolveUrlOptions, |
| 55 | ): URL; |
| 56 | export function resolveUrl( |
| 57 | urlStr: string | undefined, |
| 58 | origin?: string | URL, |
| 59 | options: ResolveUrlOptions = {}, |
| 60 | ): URL | null { |
| 61 | const originUrl = typeof origin === 'string' ? new URL('/', origin) : origin; |
| 62 | |
| 63 | if (!urlStr) { |
| 64 | return originUrl || null; |
| 65 | } |
| 66 | |
| 67 | // Fast-path: if the URL is a valid, standard absolute URL, parse and return it immediately. |
| 68 | // URLs with no authority (e.g. `http:/path` or `http:path`) must be resolved against the origin when one is provided. |
| 69 | let resolved: URL | undefined; |
| 70 | if (!originUrl || !HTTP_OR_HTTPS_NO_AUTHORITY_REGEXP.test(urlStr)) { |
| 71 | try { |
| 72 | resolved = new URL(urlStr); |
| 73 | } catch {} |
| 74 | } |
| 75 | const {allowProtocolRelative = false, allowOriginChange = true} = options; |
| 76 | |
| 77 | if (resolved) { |
| 78 | if (originUrl && !isSafeOriginChange(resolved, originUrl, urlStr, allowOriginChange)) { |
| 79 | throwSuspiciousUrlError(urlStr); |
| 80 | } |
| 81 | |
| 82 | return resolved; |
| 83 | } |
| 84 | |
| 85 | // We identify and throw on malformed absolute URLs (like double port). |
| 86 | // Per the WHATWG URL standard, parsing an input starting with a scheme (like 'http:') against |
| 87 | // a standard base (like 'http://fake') ignores the base argument and parses strictly as an |
| 88 | // absolute URL. Since it is malformed, the native URL constructor will throw a validation |
| 89 | // error. Standard relative/protocol-relative paths parse successfully, allowing the flow to continue. |
| 90 | if (!URL.canParse(urlStr, 'http://fake')) { |
| 91 | throw new RuntimeError( |
| 92 | RuntimeErrorCode.INVALID_URL, |
| 93 | typeof ngDevMode === 'undefined' || ngDevMode ? `Invalid URL: ${urlStr}` : urlStr, |
| 94 | ); |
| 95 | } |
| 96 | |
| 97 | if (!originUrl) { |
| 98 | return null; |
| 99 | } |
| 100 | |
| 101 | // Check if we have a legitimate protocol-relative URL (starts with '//' and not a duplicate/backslash bypass) |
| 102 | // and we are configured to allow and preserve standard cross-origin protocol-relative requests. |
| 103 | if (urlStr.startsWith('//')) { |
| 104 | if (!allowProtocolRelative) { |
| 105 | throw new RuntimeError( |
| 106 | RuntimeErrorCode.PROTOCOL_RELATIVE_URL_NOT_ALLOWED, |
| 107 | typeof ngDevMode === 'undefined' || ngDevMode |
| 108 | ? `Protocol relative URLs are not allowed in this context. URL: ${urlStr}` |
| 109 | : urlStr, |
| 110 | ); |
| 111 | } |
| 112 | |
| 113 | return new URL(urlStr, origin); |
no test coverage detected