feat: new helper functions to replace regexes #807

Implemented new StringUtil helper functions: splitCommaSeparated, sanitizeUrlPart, isValidFileName.
Added helper functions to HeaderUtil: matchesAuthorizationScheme, hasScheme.
Added unit tests for the new helper functions.
Refactored codebase to use helper functions instead of regexes if applicable.
This commit is contained in:
Wannes Kerckhove
2022-04-08 16:54:33 +02:00
committed by Joachim Van Herwegen
parent 1b7cc1ea3a
commit 283c301f08
18 changed files with 186 additions and 23 deletions

View File

@@ -3,6 +3,7 @@ import request from 'supertest';
import type { BaseHttpServerFactory } from '../../src/server/BaseHttpServerFactory';
import type { HttpHandlerInput } from '../../src/server/HttpHandler';
import { HttpHandler } from '../../src/server/HttpHandler';
import { splitCommaSeparated } from '../../src/util/StringUtil';
import { getPort } from '../util/Util';
import { getTestConfigPath, instantiateFromConfig } from './Config';
@@ -96,46 +97,46 @@ describe('An http server with middleware', (): void => {
it('exposes the Accept-[Method] header via CORS.', async(): Promise<void> => {
const res = await request(server).get('/').expect(200);
const exposed = res.header['access-control-expose-headers'];
expect(exposed.split(/\s*,\s*/u)).toContain('Accept-Patch');
expect(exposed.split(/\s*,\s*/u)).toContain('Accept-Post');
expect(exposed.split(/\s*,\s*/u)).toContain('Accept-Put');
expect(splitCommaSeparated(exposed)).toContain('Accept-Patch');
expect(splitCommaSeparated(exposed)).toContain('Accept-Post');
expect(splitCommaSeparated(exposed)).toContain('Accept-Put');
});
it('exposes the Last-Modified and ETag headers via CORS.', async(): Promise<void> => {
const res = await request(server).get('/').expect(200);
const exposed = res.header['access-control-expose-headers'];
expect(exposed.split(/\s*,\s*/u)).toContain('ETag');
expect(exposed.split(/\s*,\s*/u)).toContain('Last-Modified');
expect(splitCommaSeparated(exposed)).toContain('ETag');
expect(splitCommaSeparated(exposed)).toContain('Last-Modified');
});
it('exposes the Link header via CORS.', async(): Promise<void> => {
const res = await request(server).get('/').expect(200);
const exposed = res.header['access-control-expose-headers'];
expect(exposed.split(/\s*,\s*/u)).toContain('Link');
expect(splitCommaSeparated(exposed)).toContain('Link');
});
it('exposes the Location header via CORS.', async(): Promise<void> => {
const res = await request(server).get('/').expect(200);
const exposed = res.header['access-control-expose-headers'];
expect(exposed.split(/\s*,\s*/u)).toContain('Location');
expect(splitCommaSeparated(exposed)).toContain('Location');
});
it('exposes the MS-Author-Via header via CORS.', async(): Promise<void> => {
const res = await request(server).get('/').expect(200);
const exposed = res.header['access-control-expose-headers'];
expect(exposed.split(/\s*,\s*/u)).toContain('MS-Author-Via');
expect(splitCommaSeparated(exposed)).toContain('MS-Author-Via');
});
it('exposes the WAC-Allow header via CORS.', async(): Promise<void> => {
const res = await request(server).get('/').expect(200);
const exposed = res.header['access-control-expose-headers'];
expect(exposed.split(/\s*,\s*/u)).toContain('WAC-Allow');
expect(splitCommaSeparated(exposed)).toContain('WAC-Allow');
});
it('exposes the Updates-Via header via CORS.', async(): Promise<void> => {
const res = await request(server).get('/').expect(200);
const exposed = res.header['access-control-expose-headers'];
expect(exposed.split(/\s*,\s*/u)).toContain('Updates-Via');
expect(splitCommaSeparated(exposed)).toContain('Updates-Via');
});
it('sends incoming requests to the handler.', async(): Promise<void> => {

View File

@@ -2,6 +2,8 @@ import type { HttpResponse } from '../../../src/server/HttpResponse';
import { BadRequestHttpError } from '../../../src/util/errors/BadRequestHttpError';
import {
addHeader,
hasScheme,
matchesAuthorizationScheme,
parseAccept,
parseAcceptCharset,
parseAcceptDateTime,
@@ -419,4 +421,52 @@ describe('HeaderUtil', (): void => {
expect(parseLinkHeader()).toEqual([]);
});
});
describe('#matchesAuthorizationScheme', (): void => {
it('returns true if the provided authorization header value matches the provided scheme.', (): void => {
const authorization = `Bearer Q0xXTzl1dTM4RF8xLXllSGx5am51WFUzbzZ2LTZ1WU1GWXpfMTBEajBjaw==`;
expect(matchesAuthorizationScheme('Bearer', authorization)).toBeTruthy();
});
it('returns false if the provided authorization header value does not match the provided scheme.', (): void => {
const authorization = `Basic YWxpY2U6YWxpY2U=`;
expect(matchesAuthorizationScheme('Bearer', authorization)).toBeFalsy();
});
it('correctly detects scheme matches when a different casing is used.', (): void => {
const authorization = `bAsIc YWxpY2U6YWxpY2U=`;
expect(matchesAuthorizationScheme('Basic', authorization)).toBeTruthy();
});
it('escapes special regex characters in the scheme argument, resulting in a correct match.', (): void => {
const authorization = `bA.*sIc$ YWxpY2U6YWxpY2U=`;
expect(matchesAuthorizationScheme('bA.*sIc$', authorization)).toBeTruthy();
});
it('returns false if the authorization argument is undefined.', (): void => {
expect(matchesAuthorizationScheme('Bearer')).toBeFalsy();
});
});
describe('#hasScheme', (): void => {
it('returns true if the provided url matches the provided scheme.', (): void => {
expect(hasScheme('http://example.com', 'http')).toBeTruthy();
});
it('returns true if the provided url matches one of the provided schemes.', (): void => {
expect(hasScheme('ws://example.com', 'http', 'https', 'ws')).toBeTruthy();
});
it('returns false if the provided url does not match the provided scheme.', (): void => {
expect(hasScheme('http://example.com', 'https')).toBeFalsy();
});
it('returns false if the provided value is not a valid url.', (): void => {
expect(hasScheme('not-a-URL:test', 'http')).toBeFalsy();
});
it('is case insensitive: schemes with different case, result in a correct match.', (): void => {
expect(hasScheme('wss://example.com', 'http', 'WSS')).toBeTruthy();
});
});
});

View File

@@ -0,0 +1,34 @@
import {
sanitizeUrlPart,
splitCommaSeparated,
isValidFileName,
} from '../../../src/util/StringUtil';
describe('HeaderUtil', (): void => {
describe('#sanitizeUrlPart', (): void => {
it('sanitizes part of a URL by replacing non-word characters with dashes (\'-\').', (): void => {
expect(sanitizeUrlPart('$path segment containing=non-word+chars'))
.toBe('-path-segment-containing-non-word-chars');
});
});
describe('#splitCommaSeparated', (): void => {
it('splits strings containing commas into parts based on the location of these commas.', (): void => {
expect(splitCommaSeparated('this,is,a,comma-separated,string'))
.toEqual([ 'this', 'is', 'a', 'comma-separated', 'string' ]);
});
it('handles strings without commas by returning an array containing solely the original string.', (): void => {
const strVal = 'this string has no commas';
expect(splitCommaSeparated(strVal)).toEqual([ strVal ]);
});
});
describe('#validateFileName', (): void => {
it('returns true if the provided file name is valid.', (): void => {
expect(isValidFileName('valid-file.test')).toBeTruthy();
});
it('returns false if the provided file name is invalid.', (): void => {
expect(isValidFileName('$%^*')).toBeFalsy();
});
});
});