From 7435afbb555ca696c155a73fac68db3cc2c3e78d Mon Sep 17 00:00:00 2001 From: Muhammad Ibragimov <53621505+mibragimov@users.noreply.github.com> Date: Fri, 28 Jan 2022 17:57:48 +0500 Subject: [PATCH] [Console] Encode pathname (#122080) * Fix encoding pathname * Add a test case Co-authored-by: Muhammad Ibragimov --- .../console/server/lib/proxy_request.test.ts | 37 ++++++++++++++++++- .../console/server/lib/proxy_request.ts | 19 +++++++++- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/src/plugins/console/server/lib/proxy_request.test.ts b/src/plugins/console/server/lib/proxy_request.test.ts index 25257aa4c5579..3740d7e16a3c9 100644 --- a/src/plugins/console/server/lib/proxy_request.test.ts +++ b/src/plugins/console/server/lib/proxy_request.test.ts @@ -9,7 +9,7 @@ import http, { ClientRequest } from 'http'; import * as sinon from 'sinon'; import { proxyRequest } from './proxy_request'; -import { URL } from 'url'; +import { URL, URLSearchParams } from 'url'; import { fail } from 'assert'; describe(`Console's send request`, () => { @@ -101,4 +101,39 @@ describe(`Console's send request`, () => { 'transfer-encoding': 'chunked', }); }); + + it('should decode percent-encoded uri and encode it correctly', async () => { + fakeRequest = { + abort: sinon.stub(), + on() {}, + once(event: string, fn: (v: string) => void) { + if (event === 'response') { + return fn('done'); + } + }, + } as any; + + const uri = new URL( + `http://noone.nowhere.none/%{[@metadata][beat]}-%{[@metadata][version]}-2020.08.23` + ); + + const result = await proxyRequest({ + agent: null as any, + headers: {}, + method: 'get', + payload: null as any, + timeout: 30000, + uri, + }); + + expect(result).toEqual('done'); + + const decoded = new URLSearchParams(`path=${uri.pathname}`).get('path'); + const encoded = decoded + ?.split('/') + .map((str) => encodeURIComponent(str)) + .join('/'); + const [httpRequestOptions] = stub.firstCall.args; + expect((httpRequestOptions as any).path).toEqual(encoded); + }); }); diff --git a/src/plugins/console/server/lib/proxy_request.ts b/src/plugins/console/server/lib/proxy_request.ts index 46b4aa642a70e..a1b34e08b916e 100644 --- a/src/plugins/console/server/lib/proxy_request.ts +++ b/src/plugins/console/server/lib/proxy_request.ts @@ -11,7 +11,7 @@ import https from 'https'; import net from 'net'; import stream from 'stream'; import Boom from '@hapi/boom'; -import { URL } from 'url'; +import { URL, URLSearchParams } from 'url'; interface Args { method: 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head'; @@ -30,6 +30,20 @@ interface Args { const sanitizeHostname = (hostName: string): string => hostName.trim().replace(/^\[/, '').replace(/\]$/, ''); +/** + * Node URL percent-encodes any invalid characters in the pathname which results a 400 bad request error. + * We need to decode the percent-encoded pathname, and encode it correctly with encodeURIComponent + */ + +const encodePathname = (pathname: string) => { + const decodedPath = new URLSearchParams(`path=${pathname}`).get('path') ?? ''; + + return decodedPath + .split('/') + .map((str) => encodeURIComponent(str)) + .join('/'); +}; + // We use a modified version of Hapi's Wreck because Hapi, Axios, and Superagent don't support GET requests // with bodies, but ES APIs do. Similarly with DELETE requests with bodies. Another library, `request` // diverged too much from current behaviour. @@ -44,6 +58,7 @@ export const proxyRequest = ({ }: Args) => { const { hostname, port, protocol, pathname, search } = uri; const client = uri.protocol === 'https:' ? https : http; + const encodedPath = encodePathname(pathname); let resolved = false; let resolve: (res: http.IncomingMessage) => void; @@ -66,7 +81,7 @@ export const proxyRequest = ({ host: sanitizeHostname(hostname), port: port === '' ? undefined : parseInt(port, 10), protocol, - path: `${pathname}${search || ''}`, + path: `${encodedPath}${search || ''}`, headers: { ...finalUserHeaders, 'content-type': 'application/json',