Skip to content
Merged
Show file tree
Hide file tree
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
7 changes: 5 additions & 2 deletions src/configuration.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import type * as http from 'node:http';

import { ERRORS } from './errors.js';
import { HttpProxyMiddlewareError } from './errors.js';
import type { Options } from './types.js';

export function verifyConfig<TReq extends http.IncomingMessage, TRes extends http.ServerResponse>(
options: Options<TReq, TRes>,
): void {
if (!options.target && !options.router) {
throw new Error(ERRORS.ERR_CONFIG_FACTORY_TARGET_MISSING);
throw new HttpProxyMiddlewareError(
'[HPM] Missing "target" option. Example: {target: "http://www.example.org"}',
'ERR_CONFIG_FACTORY_TARGET_MISSING',
);
}
}
7 changes: 0 additions & 7 deletions src/errors.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
export enum ERRORS {
ERR_CONFIG_FACTORY_TARGET_MISSING = '[HPM] Missing "target" option. Example: {target: "http://www.example.org"}',
ERR_CONTEXT_MATCHER_GENERIC = '[HPM] Invalid pathFilter. Expecting something like: "/api" or ["/api", "/ajax"]',
ERR_CONTEXT_MATCHER_INVALID_ARRAY = '[HPM] Invalid pathFilter. Plain paths (e.g. "/api") can not be mixed with globs (e.g. "/api/**"). Expecting something like: ["/api", "/ajax"] or ["/api/**", "!**.html"].',
ERR_PATH_REWRITER_CONFIG = '[HPM] Invalid pathRewrite config. Expecting object with pathRewrite config or a rewrite function',
}

export class HttpProxyMiddlewareError extends Error {
code: string;

Expand Down
13 changes: 9 additions & 4 deletions src/handlers/fix-request-body-utils/stringify-form-data.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { HttpProxyMiddlewareError } from '../../errors.js';

const CR_OR_LF = /[\r\n]/;
const ERROR_CODE_PREFIX = 'HPM_ERR_INVALID_MULTIPART';

/**
* HPM_ERR_INVALID_MULTIPART prefixed error code will be used in
* [status-code.ts]({@link ../../status-code.ts}) to return status code 400.
*/
export const HPM_ERR_INVALID_MULTIPART = 'HPM_ERR_INVALID_MULTIPART';

/**
* stringify FormData data
Expand Down Expand Up @@ -35,7 +40,7 @@ function getMultipartBoundary(contentType: string): string {
if (!boundary || CR_OR_LF.test(boundary)) {
throw new HttpProxyMiddlewareError(
'[HPM] invalid multipart boundary detected.',
`${ERROR_CODE_PREFIX}_BOUNDARY`,
`${HPM_ERR_INVALID_MULTIPART}_BOUNDARY`,
);
}

Expand All @@ -48,14 +53,14 @@ function validateMultipartField(fieldName: string, fieldValue: string, boundary:
if (CR_OR_LF.test(fieldName)) {
throw new HttpProxyMiddlewareError(
`[HPM] invalid multipart field name "${fieldName}" detected.`,
`${ERROR_CODE_PREFIX}_FIELD_NAME`,
`${HPM_ERR_INVALID_MULTIPART}_FIELD_NAME`,
);
}

if (CR_OR_LF.test(fieldValue) || fieldValue.includes(boundaryDelimiter)) {
throw new HttpProxyMiddlewareError(
`[HPM] invalid multipart field value for "${fieldName}" detected.`,
`${ERROR_CODE_PREFIX}_FIELD_VALUE`,
`${HPM_ERR_INVALID_MULTIPART}_FIELD_VALUE`,
);
}
}
Expand Down
12 changes: 9 additions & 3 deletions src/path-filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type * as http from 'node:http';
import isGlob from 'is-glob';
import micromatch from 'micromatch';

import { ERRORS } from './errors.js';
import { HttpProxyMiddlewareError } from './errors.js';
import type { Filter } from './types.js';

export function matchPathFilter<TReq extends http.IncomingMessage = http.IncomingMessage>(
Expand All @@ -30,7 +30,10 @@ export function matchPathFilter<TReq extends http.IncomingMessage = http.Incomin
return matchMultiGlobPath(pathFilter as string[], uri);
}

throw new Error(ERRORS.ERR_CONTEXT_MATCHER_INVALID_ARRAY);
throw new HttpProxyMiddlewareError(
'[HPM] Invalid pathFilter. Plain paths (e.g. "/api") can not be mixed with globs (e.g. "/api/**"). Expecting something like: ["/api", "/ajax"] or ["/api/**", "!**.html"].',
'HPM_INVALID_PATH_FILTER_ARRAY_CONFIG',
);
}

// custom matching
Expand All @@ -39,7 +42,10 @@ export function matchPathFilter<TReq extends http.IncomingMessage = http.Incomin
return Boolean(pathFilter(pathname, req as TReq));
}

throw new Error(ERRORS.ERR_CONTEXT_MATCHER_GENERIC);
throw new HttpProxyMiddlewareError(
'[HPM] Invalid pathFilter. Expecting something like: "/api" or ["/api", "/ajax"]',
'HPM_INVALID_PATH_FILTER_CONFIG',
);
}

/**
Expand Down
7 changes: 5 additions & 2 deletions src/path-rewriter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { IncomingMessage, ServerResponse } from 'node:http';
import isPlainObject from 'is-plain-obj';

import { Debug } from './debug.js';
import { ERRORS } from './errors.js';
import { HttpProxyMiddlewareError } from './errors.js';
import type { PathRewriteConfig } from './types.js';

const debug = Debug.extend('path-rewriter');
Expand Down Expand Up @@ -57,7 +57,10 @@ function isValidRewriteConfig<
} else if (rewriteConfig === undefined || rewriteConfig === null) {
return false;
} else {
throw new Error(ERRORS.ERR_PATH_REWRITER_CONFIG);
throw new HttpProxyMiddlewareError(
'[HPM] Invalid pathRewrite config. Expecting object with pathRewrite config or a rewrite function',
'HPM_INVALID_PATH_REWRITER_CONFIG',
);
}
}

Expand Down
4 changes: 3 additions & 1 deletion test/unit/configuration.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { beforeEach, describe, expect, it } from 'vitest';

import { verifyConfig } from '../../src/configuration.js';
import { HttpProxyMiddlewareError } from '../../src/errors.js';

describe('configFactory', () => {
describe('verifyConfig()', () => {
Expand All @@ -14,7 +15,8 @@ describe('configFactory', () => {
});

it('should throw an error when target and router option are missing', () => {
expect(fn).toThrow(Error);
expect(fn).toThrow(HttpProxyMiddlewareError);
expect(fn).toThrow(expect.objectContaining({ code: 'ERR_CONFIG_FACTORY_TARGET_MISSING' }));
});
});

Expand Down
21 changes: 17 additions & 4 deletions test/unit/path-filter.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it } from 'vitest';

import { HttpProxyMiddlewareError } from '../../src/errors.js';
import { matchPathFilter } from '../../src/path-filter.js';
import { createMockRequest } from '../test-utils.js';

Expand Down Expand Up @@ -215,19 +216,31 @@ describe('Path Filter', () => {

describe('Throw error', () => {
it('should throw error with null', () => {
expect(testPathFilter(null)).toThrow(Error);
expect(testPathFilter(null)).toThrow(HttpProxyMiddlewareError);
expect(testPathFilter(null)).toThrow(
expect.objectContaining({ code: 'HPM_INVALID_PATH_FILTER_CONFIG' }),
);
});

it('should throw error with object literal', () => {
expect(testPathFilter(mockReq)).toThrow(Error);
expect(testPathFilter(mockReq)).toThrow(HttpProxyMiddlewareError);
expect(testPathFilter(mockReq)).toThrow(
expect.objectContaining({ code: 'HPM_INVALID_PATH_FILTER_CONFIG' }),
);
});

it('should throw error with integers', () => {
expect(testPathFilter(123)).toThrow(Error);
expect(testPathFilter(123)).toThrow(HttpProxyMiddlewareError);
expect(testPathFilter(123)).toThrow(
expect.objectContaining({ code: 'HPM_INVALID_PATH_FILTER_CONFIG' }),
);
});

it('should throw error with mixed string and glob pattern', () => {
expect(testPathFilter(['/api', '!*.html'])).toThrow(Error);
expect(testPathFilter(['/api', '!*.html'])).toThrow(HttpProxyMiddlewareError);
expect(testPathFilter(['/api', '!*.html'])).toThrow(
expect.objectContaining({ code: 'HPM_INVALID_PATH_FILTER_ARRAY_CONFIG' }),
);
});
});

Expand Down
13 changes: 9 additions & 4 deletions test/unit/path-rewriter.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';

import { HttpProxyMiddlewareError } from '../../src/errors.js';
import { createPathRewriter } from '../../src/path-rewriter.js';
import type { Options } from '../../src/types.js';
import type { PathRewriteConfig } from '../../src/types.js';
Expand Down Expand Up @@ -150,10 +151,14 @@ describe('Path rewriting', () => {
});

it('should throw when bad config is provided', () => {
expect(badFn(123 as unknown as PathRewriteConfig)).toThrow(Error);
expect(badFn('abc' as unknown as PathRewriteConfig)).toThrow(Error);
expect(badFn([] as unknown as PathRewriteConfig)).toThrow(Error);
expect(badFn([1, 2, 3] as unknown as PathRewriteConfig)).toThrow(Error);
expect(badFn(123 as unknown as PathRewriteConfig)).toThrow(HttpProxyMiddlewareError);
expect(badFn('abc' as unknown as PathRewriteConfig)).toThrow(
expect.objectContaining({ code: 'HPM_INVALID_PATH_REWRITER_CONFIG' }),
);
expect(badFn([] as unknown as PathRewriteConfig)).toThrow(HttpProxyMiddlewareError);
expect(badFn([1, 2, 3] as unknown as PathRewriteConfig)).toThrow(
expect.objectContaining({ code: 'HPM_INVALID_PATH_REWRITER_CONFIG' }),
);
});

it('should not throw when empty Object config is provided', () => {
Expand Down
Loading