diff --git a/lib/url-state-machine.js b/lib/url-state-machine.js index 990339214..d7eec7ca1 100644 --- a/lib/url-state-machine.js +++ b/lib/url-state-machine.js @@ -486,7 +486,7 @@ function URLStateMachine(input, base, encodingOverride, url, stateOverride) { this.stateOverride = stateOverride; this.url = url; this.failure = false; - this.parseError = false; + this.validationErrors = []; if (!this.url) { this.url = { @@ -502,14 +502,14 @@ function URLStateMachine(input, base, encodingOverride, url, stateOverride) { const res = trimControlChars(this.input); if (res !== this.input) { - this.parseError = true; + this.validationErrors.push("invalid-URL-unit"); } this.input = res; } const res = trimTabAndNewline(this.input); if (res !== this.input) { - this.parseError = true; + this.validationErrors.push("invalid-URL-unit"); } this.input = res; @@ -545,7 +545,6 @@ URLStateMachine.prototype["parse scheme start"] = function parseSchemeStart(c, c this.state = "no scheme"; --this.pointer; } else { - this.parseError = true; return failure; } @@ -583,7 +582,7 @@ URLStateMachine.prototype["parse scheme"] = function parseScheme(c, cStr) { this.buffer = ""; if (this.url.scheme === "file") { if (this.input[this.pointer + 1] !== p("/") || this.input[this.pointer + 2] !== p("/")) { - this.parseError = true; + this.validationErrors.push("special-scheme-missing-following-solidus"); } this.state = "file"; } else if (isSpecial(this.url) && this.base !== null && this.base.scheme === this.url.scheme) { @@ -602,7 +601,6 @@ URLStateMachine.prototype["parse scheme"] = function parseScheme(c, cStr) { this.state = "no scheme"; this.pointer = -1; } else { - this.parseError = true; return failure; } @@ -611,6 +609,7 @@ URLStateMachine.prototype["parse scheme"] = function parseScheme(c, cStr) { URLStateMachine.prototype["parse no scheme"] = function parseNoScheme(c) { if (this.base === null || (hasAnOpaquePath(this.base) && c !== p("#"))) { + this.validationErrors.push("missing-scheme-non-relative-URL"); return failure; } else if (hasAnOpaquePath(this.base) && c === p("#")) { this.url.scheme = this.base.scheme; @@ -634,7 +633,7 @@ URLStateMachine.prototype["parse special relative or authority"] = function pars this.state = "special authority ignore slashes"; ++this.pointer; } else { - this.parseError = true; + this.validationErrors.push("special-scheme-missing-following-solidus"); this.state = "relative"; --this.pointer; } @@ -658,7 +657,7 @@ URLStateMachine.prototype["parse relative"] = function parseRelative(c) { if (c === p("/")) { this.state = "relative slash"; } else if (isSpecial(this.url) && c === p("\\")) { - this.parseError = true; + this.validationErrors.push("invalid-reverse-solidus"); this.state = "relative slash"; } else { this.url.username = this.base.username; @@ -687,7 +686,7 @@ URLStateMachine.prototype["parse relative"] = function parseRelative(c) { URLStateMachine.prototype["parse relative slash"] = function parseRelativeSlash(c) { if (isSpecial(this.url) && (c === p("/") || c === p("\\"))) { if (c === p("\\")) { - this.parseError = true; + this.validationErrors.push("invalid-reverse-solidus"); } this.state = "special authority ignore slashes"; } else if (c === p("/")) { @@ -709,7 +708,7 @@ URLStateMachine.prototype["parse special authority slashes"] = function parseSpe this.state = "special authority ignore slashes"; ++this.pointer; } else { - this.parseError = true; + this.validationErrors.push("special-scheme-missing-following-solidus"); this.state = "special authority ignore slashes"; --this.pointer; } @@ -722,7 +721,7 @@ URLStateMachine.prototype["parse special authority ignore slashes"] = function p this.state = "authority"; --this.pointer; } else { - this.parseError = true; + this.validationErrors.push("special-scheme-missing-following-solidus"); } return true; @@ -730,7 +729,7 @@ URLStateMachine.prototype["parse special authority ignore slashes"] = function p URLStateMachine.prototype["parse authority"] = function parseAuthority(c, cStr) { if (c === p("@")) { - this.parseError = true; + this.validationErrors.push("invalid-credentials"); if (this.atFlag) { this.buffer = `%40${this.buffer}`; } @@ -756,7 +755,7 @@ URLStateMachine.prototype["parse authority"] = function parseAuthority(c, cStr) } else if (isNaN(c) || c === p("/") || c === p("?") || c === p("#") || (isSpecial(this.url) && c === p("\\"))) { if (this.atFlag && this.buffer === "") { - this.parseError = true; + this.validationErrors.push("host-missing"); return failure; } this.pointer -= countSymbols(this.buffer) + 1; @@ -776,7 +775,7 @@ URLStateMachine.prototype["parse host"] = function parseHostName(c, cStr) { this.state = "file host"; } else if (c === p(":") && !this.arrFlag) { if (this.buffer === "") { - this.parseError = true; + this.validationErrors.push("host-missing"); return failure; } @@ -796,11 +795,10 @@ URLStateMachine.prototype["parse host"] = function parseHostName(c, cStr) { (isSpecial(this.url) && c === p("\\"))) { --this.pointer; if (isSpecial(this.url) && this.buffer === "") { - this.parseError = true; + this.validationErrors.push("host-missing"); return failure; } else if (this.stateOverride && this.buffer === "" && (includesCredentials(this.url) || this.url.port !== null)) { - this.parseError = true; return false; } @@ -836,7 +834,7 @@ URLStateMachine.prototype["parse port"] = function parsePort(c, cStr) { if (this.buffer !== "") { const port = parseInt(this.buffer); if (port > 2 ** 16 - 1) { - this.parseError = true; + this.validationErrors.push("port-out-of-range"); return failure; } this.url.port = port === defaultPort(this.url.scheme) ? null : port; @@ -848,7 +846,7 @@ URLStateMachine.prototype["parse port"] = function parsePort(c, cStr) { this.state = "path start"; --this.pointer; } else { - this.parseError = true; + this.validationErrors.push("port-invalid"); return failure; } @@ -870,7 +868,7 @@ URLStateMachine.prototype["parse file"] = function parseFile(c) { if (c === p("/") || c === p("\\")) { if (c === p("\\")) { - this.parseError = true; + this.validationErrors.push("invalid-reverse-solidus"); } this.state = "file slash"; } else if (this.base !== null && this.base.scheme === "file") { @@ -888,7 +886,7 @@ URLStateMachine.prototype["parse file"] = function parseFile(c) { if (!startsWithWindowsDriveLetter(this.input, this.pointer)) { shortenPath(this.url); } else { - this.parseError = true; + this.validationErrors.push("file-invalid-Windows-drive-letter"); this.url.path = []; } @@ -906,7 +904,7 @@ URLStateMachine.prototype["parse file"] = function parseFile(c) { URLStateMachine.prototype["parse file slash"] = function parseFileSlash(c) { if (c === p("/") || c === p("\\")) { if (c === p("\\")) { - this.parseError = true; + this.validationErrors.push("invalid-reverse-solidus"); } this.state = "file host"; } else { @@ -928,7 +926,7 @@ URLStateMachine.prototype["parse file host"] = function parseFileHost(c, cStr) { if (isNaN(c) || c === p("/") || c === p("\\") || c === p("?") || c === p("#")) { --this.pointer; if (!this.stateOverride && isWindowsDriveLetterString(this.buffer)) { - this.parseError = true; + this.validationErrors.push("file-invalid-Windows-drive-letter-host"); this.state = "path"; } else if (this.buffer === "") { this.url.host = ""; @@ -963,7 +961,7 @@ URLStateMachine.prototype["parse file host"] = function parseFileHost(c, cStr) { URLStateMachine.prototype["parse path start"] = function parsePathStart(c) { if (isSpecial(this.url)) { if (c === p("\\")) { - this.parseError = true; + this.validationErrors.push("invalid-reverse-solidus"); } this.state = "path"; @@ -992,7 +990,7 @@ URLStateMachine.prototype["parse path"] = function parsePath(c) { if (isNaN(c) || c === p("/") || (isSpecial(this.url) && c === p("\\")) || (!this.stateOverride && (c === p("?") || c === p("#")))) { if (isSpecial(this.url) && c === p("\\")) { - this.parseError = true; + this.validationErrors.push("invalid-reverse-solidus"); } if (isDoubleDot(this.buffer)) { @@ -1019,12 +1017,12 @@ URLStateMachine.prototype["parse path"] = function parsePath(c) { this.state = "fragment"; } } else { - // TODO: If c is not a URL code point and not "%", parse error. + // TODO: If c is not a URL code point and not "%", invalid-URL-unit validation error. if (c === p("%") && (!infra.isASCIIHex(this.input[this.pointer + 1]) || !infra.isASCIIHex(this.input[this.pointer + 2]))) { - this.parseError = true; + this.validationErrors.push("invalid-URL-unit"); } this.buffer += utf8PercentEncodeCodePoint(c, isPathPercentEncode); @@ -1041,15 +1039,13 @@ URLStateMachine.prototype["parse opaque path"] = function parseOpaquePath(c) { this.url.fragment = ""; this.state = "fragment"; } else { - // TODO: Add: not a URL code point - if (!isNaN(c) && c !== p("%")) { - this.parseError = true; - } + // TODO: If c is not the EOF code point, not a URL code point, and not U+0025 (%), + // invalid-URL-unit validation error. if (c === p("%") && (!infra.isASCIIHex(this.input[this.pointer + 1]) || !infra.isASCIIHex(this.input[this.pointer + 2]))) { - this.parseError = true; + this.validationErrors.push("invalid-URL-unit"); } if (!isNaN(c)) { @@ -1076,12 +1072,12 @@ URLStateMachine.prototype["parse query"] = function parseQuery(c, cStr) { this.state = "fragment"; } } else if (!isNaN(c)) { - // TODO: If c is not a URL code point and not "%", parse error. + // TODO: If c is not a URL code point and not "%", invalid-URL-unit validation error. if (c === p("%") && (!infra.isASCIIHex(this.input[this.pointer + 1]) || !infra.isASCIIHex(this.input[this.pointer + 2]))) { - this.parseError = true; + this.validationErrors.push("invalid-URL-unit"); } this.buffer += cStr; @@ -1092,11 +1088,11 @@ URLStateMachine.prototype["parse query"] = function parseQuery(c, cStr) { URLStateMachine.prototype["parse fragment"] = function parseFragment(c) { if (!isNaN(c)) { - // TODO: If c is not a URL code point and not "%", parse error. + // TODO: If c is not a URL code point and not "%", invalid-URL-unit validation error. if (c === p("%") && (!infra.isASCIIHex(this.input[this.pointer + 1]) || !infra.isASCIIHex(this.input[this.pointer + 2]))) { - this.parseError = true; + this.validationErrors.push("invalid-URL-unit"); } this.url.fragment += utf8PercentEncodeCodePoint(c, isFragmentPercentEncode); @@ -1206,17 +1202,17 @@ module.exports.serializeURLOrigin = function (url) { } }; -module.exports.basicURLParse = function (input, options) { +module.exports.basicURLParseWithErrors = function (input, options) { if (options === undefined) { options = {}; } const usm = new URLStateMachine(input, options.baseURL, options.encodingOverride, options.url, options.stateOverride); - if (usm.failure) { - return null; - } + return { url: usm.failure ? null : usm.url, validationErrors: usm.validationErrors }; +}; - return usm.url; +module.exports.basicURLParse = function (input, options) { + return module.exports.basicURLParseWithErrors(input, options).url; }; module.exports.setTheUsername = function (url, username) {