Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Track specific validation errors #260

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 36 additions & 40 deletions lib/url-state-machine.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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;

Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}

Expand All @@ -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;
Expand All @@ -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;
}
Expand All @@ -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;
Expand Down Expand Up @@ -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("/")) {
Expand All @@ -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;
}
Expand All @@ -722,15 +721,15 @@ 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;
};

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}`;
}
Expand All @@ -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;
Expand All @@ -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;
}

Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
Expand All @@ -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;
}

Expand All @@ -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") {
Expand All @@ -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 = [];
}

Expand All @@ -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 {
Expand All @@ -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 = "";
Expand Down Expand Up @@ -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";

Expand Down Expand Up @@ -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)) {
Expand All @@ -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);
Expand All @@ -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)) {
Expand All @@ -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;
Expand All @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down