summaryrefslogtreecommitdiff
path: root/js/url.ts
diff options
context:
space:
mode:
authorNayeem Rahman <muhammed.9939@gmail.com>2019-09-06 01:01:27 +0100
committerRyan Dahl <ry@tinyclouds.org>2019-09-05 20:01:27 -0400
commitca000392857b4c79a3609ddbc20073222498998b (patch)
treed78ed51b0bbcb3b206a498a43aa94e65a6a7446f /js/url.ts
parent49ea932af8b30be7da109a3c0ba879ef6e3a3c41 (diff)
Fix basing in URL constructor (#2867)
Diffstat (limited to 'js/url.ts')
-rw-r--r--js/url.ts66
1 files changed, 63 insertions, 3 deletions
diff --git a/js/url.ts b/js/url.ts
index 334d1c09b..a2562b9a3 100644
--- a/js/url.ts
+++ b/js/url.ts
@@ -80,6 +80,66 @@ function generateUUID(): string {
// Keep it outside of URL to avoid any attempts of access.
export const blobURLMap = new Map<string, domTypes.Blob>();
+function isAbsolutePath(path: string): boolean {
+ return path.startsWith("/");
+}
+
+// Resolves `.`s and `..`s where possible.
+// Preserves repeating and trailing `/`s by design.
+function normalizePath(path: string): string {
+ const isAbsolute = isAbsolutePath(path);
+ path = path.replace(/^\//, "");
+ const pathSegments = path.split("/");
+
+ const newPathSegments: string[] = [];
+ for (let i = 0; i < pathSegments.length; i++) {
+ const previous = newPathSegments[newPathSegments.length - 1];
+ if (
+ pathSegments[i] == ".." &&
+ previous != ".." &&
+ (previous != undefined || isAbsolute)
+ ) {
+ newPathSegments.pop();
+ } else if (pathSegments[i] != ".") {
+ newPathSegments.push(pathSegments[i]);
+ }
+ }
+
+ let newPath = newPathSegments.join("/");
+ if (!isAbsolute) {
+ if (newPathSegments.length == 0) {
+ newPath = ".";
+ }
+ } else {
+ newPath = `/${newPath}`;
+ }
+ return newPath;
+}
+
+// Standard URL basing logic, applied to paths.
+function resolvePathFromBase(path: string, basePath: string): string {
+ const normalizedPath = normalizePath(path);
+ if (isAbsolutePath(normalizedPath)) {
+ return normalizedPath;
+ }
+ const normalizedBasePath = normalizePath(basePath);
+ if (!isAbsolutePath(normalizedBasePath)) {
+ throw new TypeError("Base path must be absolute.");
+ }
+
+ // Special case.
+ if (path == "") {
+ return normalizedBasePath;
+ }
+
+ // Remove everything after the last `/` in `normalizedBasePath`.
+ const prefix = normalizedBasePath.replace(/[^\/]*$/, "");
+ // If `normalizedPath` ends with `.` or `..`, add a trailing space.
+ const suffix = normalizedPath.replace(/(?<=(^|\/)(\.|\.\.))$/, "/");
+
+ return normalizePath(prefix + suffix);
+}
+
export class URL {
private _parts: URLParts;
private _searchParams!: urlSearchParams.URLSearchParams;
@@ -254,7 +314,7 @@ export class URL {
let baseParts: URLParts | undefined;
if (base) {
baseParts = typeof base === "string" ? parse(base) : base._parts;
- if (!baseParts) {
+ if (!baseParts || baseParts.protocol == "") {
throw new TypeError("Invalid base URL.");
}
}
@@ -273,8 +333,8 @@ export class URL {
password: baseParts.password,
hostname: baseParts.hostname,
port: baseParts.port,
- path: urlParts.path || baseParts.path,
- query: urlParts.query || baseParts.query,
+ path: resolvePathFromBase(urlParts.path, baseParts.path),
+ query: urlParts.query,
hash: urlParts.hash
};
} else {