diff --git a/lib/message/error_message.ts b/lib/message/error_message.ts index 83059f93f..20433e153 100644 --- a/lib/message/error_message.ts +++ b/lib/message/error_message.ts @@ -87,6 +87,7 @@ export const OUT_OF_BOUNDS = export const REQUEST_TIMEOUT = 'Request timeout'; export const REQUEST_ERROR = 'Request error'; export const NO_STATUS_CODE_IN_RESPONSE = 'No status code in response'; +export const INVALID_REQUEST_URL = 'Invalid request URL: %s'; export const UNSUPPORTED_PROTOCOL = 'Unsupported protocol: %s'; export const RETRY_CANCELLED = 'Retry cancelled'; export const ONLY_POST_REQUESTS_ARE_SUPPORTED = 'Only POST requests are supported'; diff --git a/lib/utils/http_request_handler/request_handler.node.spec.ts b/lib/utils/http_request_handler/request_handler.node.spec.ts index 8f5b62806..b982c5ec5 100644 --- a/lib/utils/http_request_handler/request_handler.node.spec.ts +++ b/lib/utils/http_request_handler/request_handler.node.spec.ts @@ -21,6 +21,8 @@ import nock from 'nock'; import zlib from 'zlib'; import { NodeRequestHandler } from './request_handler.node'; import { getMockLogger } from '../../tests/mock/mock_logger'; +import { OptimizelyError } from '../../error/optimizly_error'; +import { INVALID_REQUEST_URL } from 'error_message'; beforeAll(() => { nock.disableNetConnect(); @@ -173,6 +175,16 @@ describe('NodeRequestHandler', () => { await expect(request.responsePromise).rejects.toThrow(); }); + it('should return a rejected response promise when the URL is malformed', async () => { + const malformedUrl = 'not a valid url'; + const request = nodeRequestHandler.makeRequest(malformedUrl, {}, 'get'); + + const error = await request.responsePromise.catch((e) => e); + expect(error).toBeInstanceOf(OptimizelyError); + expect(error.baseMessage).toBe(INVALID_REQUEST_URL); + expect(error.params).toEqual([malformedUrl]); + }); + it('should return a rejected promise when there is a request error', async () => { const scope = nock(host) .get(path) diff --git a/lib/utils/http_request_handler/request_handler.node.ts b/lib/utils/http_request_handler/request_handler.node.ts index 1c7ef4435..51202ca13 100644 --- a/lib/utils/http_request_handler/request_handler.node.ts +++ b/lib/utils/http_request_handler/request_handler.node.ts @@ -15,12 +15,11 @@ */ import http from 'http'; import https from 'https'; -import url from 'url'; import { AbortableRequest, Headers, RequestHandler, Response } from './http'; import decompressResponse from 'decompress-response'; import { LoggerFacade } from '../../logging/logger'; import { REQUEST_TIMEOUT_MS } from '../enums'; -import { NO_STATUS_CODE_IN_RESPONSE, REQUEST_ERROR, REQUEST_TIMEOUT, UNSUPPORTED_PROTOCOL } from 'error_message'; +import { INVALID_REQUEST_URL, NO_STATUS_CODE_IN_RESPONSE, REQUEST_ERROR, REQUEST_TIMEOUT, UNSUPPORTED_PROTOCOL } from 'error_message'; import { OptimizelyError } from '../../error/optimizly_error'; import { Platform } from '../../platform_support'; /** @@ -46,7 +45,15 @@ export class NodeRequestHandler implements RequestHandler { * @returns AbortableRequest contains both the response Promise and capability to abort() */ makeRequest(requestUrl: string, headers: Headers, method: string, data?: string): AbortableRequest { - const parsedUrl = url.parse(requestUrl); + let parsedUrl: URL; + try { + parsedUrl = new URL(requestUrl); + } catch { + return { + responsePromise: Promise.reject(new OptimizelyError(INVALID_REQUEST_URL, requestUrl)), + abort: () => {}, + }; + } if (parsedUrl.protocol !== 'https:' && parsedUrl.protocol !== 'http:') { return { @@ -79,11 +86,11 @@ export class NodeRequestHandler implements RequestHandler { * @private * @returns http.RequestOptions Standard request options dictionary compatible with both http and https */ - private getRequestOptionsFromUrl(url: url.UrlWithStringQuery): http.RequestOptions { + private getRequestOptionsFromUrl(url: URL): http.RequestOptions { return { hostname: url.hostname, - path: url.path, - port: url.port, + path: url.pathname + url.search, + port: url.port || null, protocol: url.protocol, }; }