fix: Make sure all URI characters are correctly encoded

This commit is contained in:
Joachim Van Herwegen
2020-10-13 17:07:35 +02:00
parent e8e96b903d
commit e85ca622da
5 changed files with 49 additions and 29 deletions

View File

@@ -1,7 +1,7 @@
import type { TLSSocket } from 'tls';
import { format } from 'url';
import type { HttpRequest } from '../../server/HttpRequest';
import { toCanonicalUrl } from '../../util/Util';
import { toCanonicalUriPath } from '../../util/Util';
import type { ResourceIdentifier } from '../representation/ResourceIdentifier';
import { TargetExtractor } from './TargetExtractor';
@@ -23,12 +23,12 @@ export class BasicTargetExtractor extends TargetExtractor {
throw new Error('Missing host.');
}
const isHttps = input.connection && (input.connection as TLSSocket).encrypted;
const url = format({
const path = format({
protocol: `http${isHttps ? 's' : ''}`,
host: input.headers.host,
pathname: input.url,
host: toCanonicalUriPath(input.headers.host),
pathname: toCanonicalUriPath(input.url),
});
return { path: toCanonicalUrl(url) };
return { path };
}
}

View File

@@ -6,7 +6,7 @@ import { APPLICATION_OCTET_STREAM, TEXT_TURTLE } from '../util/ContentTypes';
import { ConflictHttpError } from '../util/errors/ConflictHttpError';
import { NotFoundHttpError } from '../util/errors/NotFoundHttpError';
import { UnsupportedHttpError } from '../util/errors/UnsupportedHttpError';
import { trimTrailingSlashes } from '../util/Util';
import { decodeUriPathComponents, encodeUriPathComponents, trimTrailingSlashes } from '../util/Util';
import type { FileIdentifierMapper, ResourceLink } from './FileIdentifierMapper';
const { join: joinPath, normalize: normalizePath } = posix;
@@ -137,7 +137,7 @@ export class ExtensionBasedMapper implements FileIdentifierMapper {
let relative = filePath.slice(this.rootFilepath.length);
if (isContainer) {
return {
identifier: { path: encodeURI(this.baseRequestURI + relative) },
identifier: { path: this.baseRequestURI + encodeUriPathComponents(relative) },
filePath,
};
}
@@ -150,7 +150,7 @@ export class ExtensionBasedMapper implements FileIdentifierMapper {
}
return {
identifier: { path: encodeURI(this.baseRequestURI + relative) },
identifier: { path: this.baseRequestURI + encodeUriPathComponents(relative) },
filePath,
contentType,
};
@@ -201,7 +201,7 @@ export class ExtensionBasedMapper implements FileIdentifierMapper {
if (!identifier.path.startsWith(this.baseRequestURI)) {
throw new NotFoundHttpError();
}
return decodeURI(identifier.path).slice(this.baseRequestURI.length);
return decodeUriPathComponents(identifier.path.slice(this.baseRequestURI.length));
}
/**

View File

@@ -68,14 +68,19 @@ export const pipeStreamsAndErrors = <T extends Writable>(readable: Readable, des
};
/**
* Converts a URL string to the "canonical" version that should be used internally for consistency.
* Decodes all percent encodings and then makes sure only the necessary characters are encoded again.
* Converts a URI path to the canonical version by splitting on slashes,
* decoding any percent-based encodings,
* and then encoding any special characters.
*/
export const toCanonicalUrl = (url: string): string => {
const match = /(\w+:\/\/[^/]+\/)(.*)/u.exec(url);
if (!match) {
throw new UnsupportedHttpError(`Invalid URL ${url}`);
}
const [ , domain, path ] = match;
return encodeURI(domain + path.split('/').map(decodeURIComponent).join('/'));
};
export const toCanonicalUriPath = (path: string): string => path.split('/').map((part): string =>
encodeURIComponent(decodeURIComponent(part))).join('/');
/**
* Decodes all components of a URI path.
*/
export const decodeUriPathComponents = (path: string): string => path.split('/').map(decodeURIComponent).join('/');
/**
* Encodes all (non-slash) special characters in a URI path.
*/
export const encodeUriPathComponents = (path: string): string => path.split('/').map(encodeURIComponent).join('/');