diff --git a/CHANGELOG.md b/CHANGELOG.md index 98fefffd..2c8b52f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# 1.11.1 (Common, Node.js, Web) + +## Bug fixes + +- Fixed an issue with URLEncoded special characters in the URL configuration for username or password. ([#407](https://github.com/ClickHouse/clickhouse-js/issues/407)) + +## Improvements + +- Added support for streaming on 32-bit platforms. ([#403](https://github.com/ClickHouse/clickhouse-js/pull/403), [shevchenkonik](https://github.com/shevchenkonik)) + # 1.11.0 (Common, Node.js, Web) ## New features diff --git a/packages/client-common/src/config.ts b/packages/client-common/src/config.ts index b7a71795..a9c07c9a 100644 --- a/packages/client-common/src/config.ts +++ b/packages/client-common/src/config.ts @@ -320,12 +320,12 @@ export function loadConfigOptionsFromURL( handleExtraURLParams: HandleImplSpecificURLParams | null, ): [URL, BaseClickHouseClientConfigOptions] { let config: BaseClickHouseClientConfigOptions = {} - if (url.username.trim() !== '') { - config.username = url.username + // trim is not needed, cause space is not allowed in the URL basic auth and should be encoded as %20 + if (url.username !== '') { + config.username = decodeURIComponent(url.username) } - // no trim for password if (url.password !== '') { - config.password = url.password + config.password = decodeURIComponent(url.password) } if (url.pathname.trim().length > 1) { config.database = url.pathname.slice(1) diff --git a/packages/client-common/src/version.ts b/packages/client-common/src/version.ts index 932d48a7..9797bab9 100644 --- a/packages/client-common/src/version.ts +++ b/packages/client-common/src/version.ts @@ -1 +1 @@ -export default '1.11.0' +export default '1.11.1' diff --git a/packages/client-node/__tests__/unit/node_client.test.ts b/packages/client-node/__tests__/unit/node_client.test.ts index fd0523ed..60a8b919 100644 --- a/packages/client-node/__tests__/unit/node_client.test.ts +++ b/packages/client-node/__tests__/unit/node_client.test.ts @@ -108,5 +108,39 @@ describe('[Node.js] createClient', () => { } satisfies CreateConnectionParams) expect(createConnectionStub).toHaveBeenCalledTimes(1) }) + + it('should parse username and password with special characters', async () => { + const username = '! $' + const password = '(#%%@) ' + const auth = `${encodeURIComponent(username)}:${encodeURIComponent(password)}` + createClient({ + url: + `https://${auth}@my.host:8443/analytics?` + + [ + // base config parameters + 'application=my_app', + 'pathname=my_proxy', + 'request_timeout=42000', + 'http_header_X-ClickHouse-Auth=secret_token', + // Node.js specific + 'keep_alive_idle_socket_ttl=1500', + ].join('&'), + }) + expect(createConnectionStub).toHaveBeenCalledWith({ + connection_params: { + ...params, + url: new URL('https://my.host:8443/my_proxy'), + auth: { username, password, type: 'Credentials' }, + }, + tls: undefined, + keep_alive: { + enabled: true, + idle_socket_ttl: 1500, + }, + set_basic_auth_header: true, + http_agent: undefined, + } satisfies CreateConnectionParams) + expect(createConnectionStub).toHaveBeenCalledTimes(1) + }) }) }) diff --git a/packages/client-node/src/utils/stream.ts b/packages/client-node/src/utils/stream.ts index 3e2dd68e..980c43f4 100644 --- a/packages/client-node/src/utils/stream.ts +++ b/packages/client-node/src/utils/stream.ts @@ -1,10 +1,17 @@ import Stream from 'stream' +import { constants } from 'buffer' -// See https://github.com/v8/v8/commit/ea56bf5513d0cbd2a35a9035c5c2996272b8b728 -const MaxStringLength = Math.pow(2, 29) - 24 +const { MAX_STRING_LENGTH } = constants -export function isStream(obj: any): obj is Stream.Readable { - return obj !== null && typeof obj.pipe === 'function' +export function isStream(obj: unknown): obj is Stream.Readable { + return ( + typeof obj === 'object' && + obj !== null && + 'pipe' in obj && + typeof obj.pipe === 'function' && + 'on' in obj && + typeof obj.on === 'function' + ) } export async function getAsText(stream: Stream.Readable): Promise { @@ -13,10 +20,10 @@ export async function getAsText(stream: Stream.Readable): Promise { const textDecoder = new TextDecoder() for await (const chunk of stream) { const decoded = textDecoder.decode(chunk, { stream: true }) - if (decoded.length + text.length > MaxStringLength) { + if (decoded.length + text.length > MAX_STRING_LENGTH) { throw new Error( 'The response length exceeds the maximum allowed size of V8 String: ' + - `${MaxStringLength}; consider limiting the amount of requested rows.`, + `${MAX_STRING_LENGTH}; consider limiting the amount of requested rows.`, ) } text += decoded diff --git a/packages/client-node/src/version.ts b/packages/client-node/src/version.ts index 932d48a7..9797bab9 100644 --- a/packages/client-node/src/version.ts +++ b/packages/client-node/src/version.ts @@ -1 +1 @@ -export default '1.11.0' +export default '1.11.1' diff --git a/packages/client-web/src/version.ts b/packages/client-web/src/version.ts index 932d48a7..9797bab9 100644 --- a/packages/client-web/src/version.ts +++ b/packages/client-web/src/version.ts @@ -1 +1 @@ -export default '1.11.0' +export default '1.11.1'