Skip to content

Commit

Permalink
fix: decode path before matching it
Browse files Browse the repository at this point in the history
  • Loading branch information
rainum committed Aug 20, 2021
1 parent 0a76691 commit ed5bce8
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 13 deletions.
25 changes: 25 additions & 0 deletions packages/http/src/router/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,31 @@ describe('http router', () => {
);
});

test('given encoded path should match to not encoded path', () => {
const method = pickOneHttpMethod();
const url = faker.internet.url();
const path = randomPath({ includeTemplates: false, includeSpaces: true });

const expectedResource = createResource(method, path, [
{
url,
},
]);
assertRight(
route({
resources: [expectedResource],
input: {
method,
url: {
baseUrl: url,
path: encodeURI(path),
},
},
}),
resource => expect(resource).toBe(expectedResource)
);
});

describe('given a concrete and a templated path', () => {
const path = '/test/fixed';
const templatedPath = '/test/{userId}';
Expand Down
11 changes: 11 additions & 0 deletions packages/http/src/router/__tests__/matchPath.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,17 @@ describe('matchPath()', () => {
assertRight(matchPath(path, path), e => expect(e).toEqual(MatchType.CONCRETE));
});

test('any concrete path with spaces should match an equal concrete path', () => {
// e.g. /a b/c should match /a b/c
const path = randomPath({
pathFragments: faker.datatype.number({ min: 2, max: 6 }),
includeTemplates: false,
includeSpaces: true,
});

assertRight(matchPath(path, path), e => expect(e).toEqual(MatchType.CONCRETE));
});

test('any concrete path should match an equal concrete path', () => {
// e.g. /a/b/c should match /a/b/c
const path = randomPath({
Expand Down
16 changes: 11 additions & 5 deletions packages/http/src/router/__tests__/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,29 @@ type IRandomPathOptions = {
includeTemplates?: boolean;
trailingSlash?: boolean;
leadingSlash?: boolean;
includeSpaces?: boolean;
};

const defaultRandomPathOptions: DeepNonNullable<IRandomPathOptions> = {
pathFragments: 3,
includeTemplates: true,
leadingSlash: true,
trailingSlash: false,
includeSpaces: true,
};

export function randomPath(opts: IRandomPathOptions = defaultRandomPathOptions): string {
const options = defaults(defaultRandomPathOptions, opts);

const randomPathFragments = new Array(options.pathFragments)
.fill(0)
.map(() =>
options.includeTemplates && faker.datatype.boolean() ? `{${faker.random.word()}}` : faker.random.word()
);
const randomPathFragments = new Array(options.pathFragments).fill(0).map(() => {
const words = faker.random.words(options.includeSpaces ? 3 : 1);

if (options.includeTemplates && faker.datatype.boolean()) {
return `{${words}}`;
}

return words;
});

const leadingSlash = options.leadingSlash ? '/' : '';
const trailingSlash = options.trailingSlash ? '/' : '';
Expand Down
28 changes: 20 additions & 8 deletions packages/http/src/router/matchPath.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,29 @@
import { MatchType } from './types';
import * as E from 'fp-ts/Either';

function fragmentarize(path: string): string[] {
function fragmentize(path: string): string[] {
return path.split('/').slice(1);
}

function getTemplateParamName(pathFragment: string) {
const match = /{(.*)}/.exec(pathFragment);
// Attempt to decode path fragment. Decode should not do any harm since it is
// decoding only URI sequences which are previously created by encodeURIComponent
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent
function decodePathFragment(pathFragment?: string) {
try {
return pathFragment && decodeURIComponent(pathFragment);
} catch (_) {
return pathFragment;
}
}

function getTemplateParamName(pathFragment?: string) {
const match = typeof pathFragment === 'string' && /{(.*)}/.exec(pathFragment);
return match && match[1];
}

export function matchPath(requestPath: string, operationPath: string): E.Either<Error, MatchType> {
const operationPathFragments = fragmentarize(operationPath);
const requestPathFragments = fragmentarize(requestPath);
const operationPathFragments = fragmentize(operationPath);
const requestPathFragments = fragmentize(requestPath);

if (
operationPathFragments.length < requestPathFragments.length ||
Expand All @@ -23,10 +34,11 @@ export function matchPath(requestPath: string, operationPath: string): E.Either<

const params = [];
while (requestPathFragments.length) {
const requestPathFragment = requestPathFragments.shift();
const operationPathFragment = operationPathFragments.shift();
// make sure fragments are decoded before comparing them
const requestPathFragment = decodePathFragment(requestPathFragments.shift());
const operationPathFragment = decodePathFragment(operationPathFragments.shift());

const paramName = getTemplateParamName(operationPathFragment as string);
const paramName = getTemplateParamName(operationPathFragment);

if (paramName === null && operationPathFragment !== requestPathFragment) {
// if concrete fragment and fragments are different return false
Expand Down

0 comments on commit ed5bce8

Please sign in to comment.