From deda49e9f02c572b0b508c9b06f9c81b4a78d521 Mon Sep 17 00:00:00 2001 From: Alexey Antonov Date: Wed, 12 Feb 2020 19:51:03 +0300 Subject: [PATCH] [ui/utils/query_string]: Remove unused methods & migrate apps to querystring lib (#56957) * replace querystring (querystring-browser) -> query-string * QueryString remove encode/decode methods * remove query_string file * remove querystring-browser from package.json * add kibana_utils\url module * cleanup * update notice.txt * fix merge conflict * fix CI * fix wrong import * fix CI * fix X-Pack firefox smoke test * remove urlUtils.parseUrlQuery * remove url.stringifyUrlQuery * use url.encodeQuery * Record -> ParsedQuery * Update src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx Co-Authored-By: Luke Elmers * add more tests for APM Co-authored-by: Elastic Machine Co-authored-by: Luke Elmers --- .eslintrc.js | 8 +- NOTICE.txt | 25 ---- package.json | 2 +- .../lib/get_webpack_config.js | 1 - src/core/server/http/http_server.mocks.ts | 6 +- src/core/utils/url.ts | 5 +- src/legacy/server/logging/log_format.js | 4 +- .../public/state_management/global_state.js | 5 - src/legacy/ui/public/utils/query_string.js | 129 ------------------ .../ui/ui_exports/ui_export_defaults.js | 1 - src/legacy/utils/index.js | 1 - .../editor/legacy/console_editor/editor.tsx | 14 +- src/plugins/console/public/lib/es/es.ts | 4 +- .../query/timefilter/lib/parse_querystring.ts | 5 +- src/plugins/kibana_utils/common/index.ts | 1 + .../url/encode_uri_query.test.ts} | 45 +++--- .../common/url/encode_uri_query.ts} | 21 ++- .../kibana_utils/common/url/index.ts} | 13 +- .../public/history/remove_query_param.ts | 10 +- src/plugins/kibana_utils/public/index.ts | 1 + .../public/state_management/url/format.ts | 12 +- .../state_management/url/kbn_url_storage.ts | 7 +- .../url/stringify_query_string.ts | 57 -------- src/plugins/kibana_utils/server/index.ts | 2 +- .../server/series_functions/quandl.test.js | 8 +- .../shared/Links/url_helpers.test.tsx | 38 ++++++ .../components/shared/Links/url_helpers.ts | 15 +- .../public/containers/with_url_state.tsx | 17 ++- .../workpad_header/workpad_export/utils.ts | 9 +- x-pack/legacy/plugins/canvas/public/legacy.ts | 3 - .../plugins/canvas/public/lib/app_state.ts | 4 +- .../plugins/canvas/public/lib/modify_url.ts | 4 +- .../legacy/plugins/canvas/public/plugin.tsx | 1 - .../public/app/services/query_params.js | 4 +- .../public/app/services/routing.js | 13 +- .../analyze_in_ml_button.tsx | 10 +- .../containers/with_state_from_location.tsx | 17 ++- .../pages/link_to/redirect_to_logs.test.tsx | 6 +- .../link_to/redirect_to_node_logs.test.tsx | 12 +- .../plugins/infra/public/utils/url_state.tsx | 22 +-- .../infra/public/utils/use_url_state.ts | 25 ++-- .../analytics_job_exploration.tsx | 7 +- .../routes/datavisualizer/index_based.tsx | 6 +- .../routing/routes/new_job/job_type.tsx | 6 +- .../routing/routes/new_job/recognize.tsx | 11 +- .../routing/routes/new_job/wizard.tsx | 5 +- .../routing/routes/timeseriesexplorer.tsx | 2 - .../ml/public/application/util/url_state.ts | 15 +- .../public/app/services/query_params.js | 4 +- .../server/lib/encode_uri_query.js | 39 ------ .../printable_pdf/server/lib/uri_encode.js | 12 +- .../reporting/public/lib/reporting_client.ts | 9 +- .../ml_host_conditional_container.tsx | 41 ++++-- .../ml_network_conditional_container.tsx | 42 ++++-- .../public/components/url_state/helpers.ts | 29 ++-- .../url_state/index_mocked.test.tsx | 2 +- .../home/snapshot_list/snapshot_list.tsx | 4 +- .../repository_add/repository_add.tsx | 6 +- .../uptime/public/hooks/use_url_params.ts | 14 +- .../public/lib/helper/stringify_url_params.ts | 4 +- .../url_params/get_supported_url_params.ts | 2 +- .../plugins/uptime/public/state/api/ping.ts | 4 +- x-pack/package.json | 3 +- .../endpoint/store/alerts/middleware.ts | 4 +- x-pack/plugins/spaces/server/lib/utils/url.ts | 4 +- .../rollup/index_patterns_extensions.js | 26 ++-- x-pack/test/functional/apps/infra/link_to.ts | 2 +- .../apis/security/saml_login.ts | 6 +- .../fixtures/saml_tools.ts | 4 +- yarn.lock | 24 +++- 70 files changed, 399 insertions(+), 525 deletions(-) delete mode 100644 src/legacy/ui/public/utils/query_string.js rename src/plugins/kibana_utils/{public/state_management/url/stringify_query_string.test.ts => common/url/encode_uri_query.test.ts} (78%) rename src/{legacy/utils/encode_query_component.ts => plugins/kibana_utils/common/url/encode_uri_query.ts} (72%) rename src/{legacy/ui/public/utils/query_string.d.ts => plugins/kibana_utils/common/url/index.ts} (77%) delete mode 100644 src/plugins/kibana_utils/public/state_management/url/stringify_query_string.ts delete mode 100644 x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/encode_uri_query.js diff --git a/.eslintrc.js b/.eslintrc.js index abfe5e0a6cc270..9b00135df5bac7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -355,13 +355,7 @@ module.exports = { settings: { // instructs import/no-extraneous-dependencies to treat certain modules // as core modules, even if they aren't listed in package.json - 'import/core-modules': [ - 'plugins', - 'legacy/ui', - 'uiExports', - // TODO: Remove once https://github.com/benmosher/eslint-plugin-import/issues/1374 is fixed - 'querystring', - ], + 'import/core-modules': ['plugins', 'legacy/ui', 'uiExports'], 'import/resolver': { '@kbn/eslint-import-resolver-kibana': { diff --git a/NOTICE.txt b/NOTICE.txt index 69be6db72cff2c..33c1d535d7df32 100644 --- a/NOTICE.txt +++ b/NOTICE.txt @@ -218,28 +218,3 @@ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- -This product includes code that was extracted from angular@1.3. -Original license: -The MIT License - -Copyright (c) 2010-2014 Google, Inc. http://angularjs.org - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - diff --git a/package.json b/package.json index c3762c2eabd28b..5bf33f0ab0bcb4 100644 --- a/package.json +++ b/package.json @@ -230,7 +230,7 @@ "prop-types": "15.6.0", "proxy-from-env": "1.0.0", "pug": "^2.0.4", - "querystring-browser": "1.0.4", + "query-string": "6.10.1", "raw-loader": "3.1.0", "react": "^16.12.0", "react-color": "^2.13.8", diff --git a/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js b/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js index e02c38494991a9..da0b799b338edc 100755 --- a/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js +++ b/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js @@ -29,7 +29,6 @@ exports.getWebpackConfig = function(kibanaPath, projectRoot, config) { // Kibana defaults https://github.com/elastic/kibana/blob/6998f074542e8c7b32955db159d15661aca253d7/src/legacy/ui/ui_bundler_env.js#L30-L36 ui: fromKibana('src/legacy/ui/public'), test_harness: fromKibana('src/test_harness/public'), - querystring: 'querystring-browser', // Dev defaults for test bundle https://github.com/elastic/kibana/blob/6998f074542e8c7b32955db159d15661aca253d7/src/core_plugins/tests_bundle/index.js#L73-L78 ng_mock$: fromKibana('src/test_utils/public/ng_mock'), diff --git a/src/core/server/http/http_server.mocks.ts b/src/core/server/http/http_server.mocks.ts index 230a229b36888f..81d756f47d7609 100644 --- a/src/core/server/http/http_server.mocks.ts +++ b/src/core/server/http/http_server.mocks.ts @@ -19,8 +19,7 @@ import { Request } from 'hapi'; import { merge } from 'lodash'; import { Socket } from 'net'; - -import querystring from 'querystring'; +import { stringify } from 'query-string'; import { schema } from '@kbn/config-schema'; @@ -55,7 +54,8 @@ function createKibanaRequestMock({ socket = new Socket(), routeTags, }: RequestFixtureOptions = {}) { - const queryString = querystring.stringify(query); + const queryString = stringify(query, { sort: false }); + return KibanaRequest.from( createRawRequestMock({ headers, diff --git a/src/core/utils/url.ts b/src/core/utils/url.ts index 67b379e729ca46..31de7e18140380 100644 --- a/src/core/utils/url.ts +++ b/src/core/utils/url.ts @@ -16,8 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - -import { ParsedUrlQuery } from 'querystring'; +import { ParsedQuery } from 'query-string'; import { format as formatUrl, parse as parseUrl, UrlObject } from 'url'; /** @@ -33,7 +32,7 @@ export interface URLMeaningfulParts { protocol?: string | null; slashes?: boolean | null; port?: string | null; - query: ParsedUrlQuery; + query: ParsedQuery; } /** diff --git a/src/legacy/server/logging/log_format.js b/src/legacy/server/logging/log_format.js index 0e284df230ef4b..ca1d756704dd0c 100644 --- a/src/legacy/server/logging/log_format.js +++ b/src/legacy/server/logging/log_format.js @@ -20,10 +20,10 @@ import Stream from 'stream'; import moment from 'moment-timezone'; import { get, _ } from 'lodash'; +import queryString from 'query-string'; import numeral from '@elastic/numeral'; import chalk from 'chalk'; import stringify from 'json-stringify-safe'; -import querystring from 'querystring'; import applyFiltersToKeys from './apply_filters_to_keys'; import { inspect } from 'util'; import { logWithMetadata } from './log_with_metadata'; @@ -108,7 +108,7 @@ export default class TransformObjStream extends Stream.Transform { contentLength: contentLength, }; - const query = querystring.stringify(event.query); + const query = queryString.stringify(event.query, { sort: false }); if (query) data.req.url += '?' + query; data.message = data.req.method.toUpperCase() + ' '; diff --git a/src/legacy/ui/public/state_management/global_state.js b/src/legacy/ui/public/state_management/global_state.js index 955759e3059508..d8ff38106b978a 100644 --- a/src/legacy/ui/public/state_management/global_state.js +++ b/src/legacy/ui/public/state_management/global_state.js @@ -17,7 +17,6 @@ * under the License. */ -import { QueryString } from '../utils/query_string'; import { StateProvider } from './state'; import { uiModules } from '../modules'; import { createLegacyClass } from '../utils/legacy_class'; @@ -35,10 +34,6 @@ export function GlobalStateProvider(Private) { // if the url param is missing, write it back GlobalState.prototype._persistAcrossApps = true; - GlobalState.prototype.removeFromUrl = function(url) { - return QueryString.replaceParamInUrl(url, this._urlParam, null); - }; - return new GlobalState(); } diff --git a/src/legacy/ui/public/utils/query_string.js b/src/legacy/ui/public/utils/query_string.js deleted file mode 100644 index 5fbc6da67bc980..00000000000000 --- a/src/legacy/ui/public/utils/query_string.js +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { encodeQueryComponent } from '../../../utils'; - -export const QueryString = {}; - -/***** -/*** originally copied from angular, modified our purposes -/*****/ - -function tryDecodeURIComponent(value) { - try { - return decodeURIComponent(value); - } catch (e) { - // Ignore any invalid uri component - } // eslint-disable-line no-empty -} - -/** - * Parses an escaped url query string into key-value pairs. - * @returns {Object.} - */ -QueryString.decode = function(keyValue) { - const obj = {}; - let keyValueParts; - let key; - - (keyValue || '').split('&').forEach(function(keyValue) { - if (keyValue) { - keyValueParts = keyValue.split('='); - key = tryDecodeURIComponent(keyValueParts[0]); - if (key !== void 0) { - const val = keyValueParts[1] !== void 0 ? tryDecodeURIComponent(keyValueParts[1]) : true; - if (!obj[key]) { - obj[key] = val; - } else if (Array.isArray(obj[key])) { - obj[key].push(val); - } else { - obj[key] = [obj[key], val]; - } - } - } - }); - return obj; -}; - -/** - * Creates a queryString out of an object - * @param {Object} obj - * @return {String} - */ -QueryString.encode = function(obj) { - const parts = []; - const keys = Object.keys(obj).sort(); - keys.forEach(function(key) { - const value = obj[key]; - if (Array.isArray(value)) { - value.forEach(function(arrayValue) { - parts.push(QueryString.param(key, arrayValue)); - }); - } else { - parts.push(QueryString.param(key, value)); - } - }); - return parts.length ? parts.join('&') : ''; -}; - -QueryString.param = function(key, val) { - return ( - encodeQueryComponent(key, true) + (val === true ? '' : '=' + encodeQueryComponent(val, true)) - ); -}; - -/** - * Extracts the query string from a url - * @param {String} url - * @return {Object} - returns an object describing the start/end index of the url in the string. The indices will be - * the same if the url does not have a query string - */ -QueryString.findInUrl = function(url) { - let qsStart = url.indexOf('?'); - let hashStart = url.lastIndexOf('#'); - - if (hashStart === -1) { - // out of bounds - hashStart = url.length; - } - - if (qsStart === -1) { - qsStart = hashStart; - } - - return { - start: qsStart, - end: hashStart, - }; -}; - -QueryString.replaceParamInUrl = function(url, param, newVal) { - const loc = QueryString.findInUrl(url); - const parsed = QueryString.decode(url.substring(loc.start + 1, loc.end)); - - if (newVal != null) { - parsed[param] = newVal; - } else { - delete parsed[param]; - } - - const chars = url.split(''); - chars.splice(loc.start, loc.end - loc.start, '?' + QueryString.encode(parsed)); - return chars.join(''); -}; diff --git a/src/legacy/ui/ui_exports/ui_export_defaults.js b/src/legacy/ui/ui_exports/ui_export_defaults.js index 1cb23d2ad2a236..459559e84b1a76 100644 --- a/src/legacy/ui/ui_exports/ui_export_defaults.js +++ b/src/legacy/ui/ui_exports/ui_export_defaults.js @@ -30,7 +30,6 @@ export const UI_EXPORT_DEFAULTS = { ui: resolve(ROOT, 'src/legacy/ui/public'), __kibanaCore__$: resolve(ROOT, 'src/core/public'), test_harness: resolve(ROOT, 'src/test_harness/public'), - querystring: 'querystring-browser', moment$: resolve(ROOT, 'webpackShims/moment'), 'moment-timezone$': resolve(ROOT, 'webpackShims/moment-timezone'), }, diff --git a/src/legacy/utils/index.js b/src/legacy/utils/index.js index 2e6381b31ecee8..a4c0cdf958fc25 100644 --- a/src/legacy/utils/index.js +++ b/src/legacy/utils/index.js @@ -21,7 +21,6 @@ export { BinderBase } from './binder'; export { BinderFor } from './binder_for'; export { deepCloneWithBuffers } from './deep_clone_with_buffers'; export { unset } from './unset'; -export { encodeQueryComponent } from './encode_query_component'; export { watchStdioForLine } from './watch_stdio_for_line'; export { IS_KIBANA_DISTRIBUTABLE } from './artifact_type'; export { IS_KIBANA_RELEASE } from './artifact_type'; diff --git a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx index b3e966ddffa4c2..bfa74392c14fb8 100644 --- a/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx +++ b/src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx @@ -21,11 +21,7 @@ import React, { CSSProperties, useCallback, useEffect, useRef, useState } from ' import { EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { debounce } from 'lodash'; - -// Node v5 querystring for browser. -// @ts-ignore -import * as qs from 'querystring-browser'; - +import { parse } from 'query-string'; import { EuiIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { useServicesContext, useEditorReadContext } from '../../../../contexts'; import { useUIAceKeyboardMode } from '../use_ui_ace_keyboard_mode'; @@ -51,6 +47,10 @@ export interface EditorProps { initialTextValue: string; } +interface QueryParams { + load_from: string; +} + const abs: CSSProperties = { position: 'absolute', top: '0', @@ -98,7 +98,8 @@ function EditorUI({ initialTextValue }: EditorProps) { const readQueryParams = () => { const [, queryString] = (window.location.hash || '').split('?'); - return qs.parse(queryString || ''); + + return parse(queryString || '', { sort: false }) as Required; }; const loadBufferFromRemote = (url: string) => { @@ -138,6 +139,7 @@ function EditorUI({ initialTextValue }: EditorProps) { window.addEventListener('hashchange', onHashChange); const initialQueryParams = readQueryParams(); + if (initialQueryParams.load_from) { loadBufferFromRemote(initialQueryParams.load_from); } else { diff --git a/src/plugins/console/public/lib/es/es.ts b/src/plugins/console/public/lib/es/es.ts index 52aba98d9e6621..f11692e1befadb 100644 --- a/src/plugins/console/public/lib/es/es.ts +++ b/src/plugins/console/public/lib/es/es.ts @@ -17,8 +17,8 @@ * under the License. */ -import { stringify as formatQueryString } from 'querystring'; import $ from 'jquery'; +import { stringify } from 'query-string'; const esVersion: string[] = []; @@ -35,7 +35,7 @@ export function send(method: string, path: string, data: any) { const wrappedDfd = $.Deferred(); // eslint-disable-line new-cap const options: JQuery.AjaxSettings = { - url: '../api/console/proxy?' + formatQueryString({ path, method }), + url: '../api/console/proxy?' + stringify({ path, method }, { sort: false }), data, contentType: getContentType(data), cache: false, diff --git a/src/plugins/data/public/query/timefilter/lib/parse_querystring.ts b/src/plugins/data/public/query/timefilter/lib/parse_querystring.ts index 467110b6f32ea3..2220ad4eef1b7f 100644 --- a/src/plugins/data/public/query/timefilter/lib/parse_querystring.ts +++ b/src/plugins/data/public/query/timefilter/lib/parse_querystring.ts @@ -16,8 +16,7 @@ * specific language governing permissions and limitations * under the License. */ - -import qs from 'querystring'; +import { parse } from 'query-string'; export function parseQueryString() { // window.location.search is an empty string @@ -27,5 +26,5 @@ export function parseQueryString() { return {}; } - return qs.parse(hrefSplit[1]); + return parse(hrefSplit[1], { sort: false }); } diff --git a/src/plugins/kibana_utils/common/index.ts b/src/plugins/kibana_utils/common/index.ts index 4551d0e63c4bef..3b07674315dce3 100644 --- a/src/plugins/kibana_utils/common/index.ts +++ b/src/plugins/kibana_utils/common/index.ts @@ -24,3 +24,4 @@ export * from './state_containers'; export * from './typed_json'; export { createGetterSetter, Get, Set } from './create_getter_setter'; export { distinctUntilChangedWithInitialValue } from './distinct_until_changed_with_initial_value'; +export { url } from './url'; diff --git a/src/plugins/kibana_utils/public/state_management/url/stringify_query_string.test.ts b/src/plugins/kibana_utils/common/url/encode_uri_query.test.ts similarity index 78% rename from src/plugins/kibana_utils/public/state_management/url/stringify_query_string.test.ts rename to src/plugins/kibana_utils/common/url/encode_uri_query.test.ts index 3ca6cb42146829..b600822946299f 100644 --- a/src/plugins/kibana_utils/public/state_management/url/stringify_query_string.test.ts +++ b/src/plugins/kibana_utils/common/url/encode_uri_query.test.ts @@ -17,27 +17,10 @@ * under the License. */ -import { encodeUriQuery, stringifyQueryString } from './stringify_query_string'; +import { encodeUriQuery, encodeQuery } from './encode_uri_query'; -describe('stringifyQueryString', () => { - it('stringifyQueryString', () => { - expect( - stringifyQueryString({ - a: 'asdf1234asdf', - b: "-_.!~*'() -_.!~*'()", - c: ':@$, :@$,', - d: "&;=+# &;=+#'", - f: ' ', - g: 'null', - }) - ).toMatchInlineSnapshot( - `"a=asdf1234asdf&b=-_.!~*'()%20-_.!~*'()&c=:@$,%20:@$,&d=%26;%3D%2B%23%20%26;%3D%2B%23'&f=%20&g=null"` - ); - }); -}); - -describe('encodeUriQuery', function() { - it('should correctly encode uri query and not encode chars defined as pchar set in rfc3986', () => { +describe('encodeUriQuery', () => { + test('should correctly encode uri query and not encode chars defined as pchar set in rfc3986', () => { // don't encode alphanum expect(encodeUriQuery('asdf1234asdf')).toBe('asdf1234asdf'); @@ -63,3 +46,25 @@ describe('encodeUriQuery', function() { expect(encodeUriQuery('null')).toBe('null'); }); }); + +describe('encodeQuery', () => { + test('encodeQuery', () => { + expect( + encodeQuery({ + a: 'asdf1234asdf', + b: "-_.!~*'() -_.!~*'()", + c: ':@$, :@$,', + d: "&;=+# &;=+#'", + f: ' ', + g: 'null', + }) + ).toEqual({ + a: 'asdf1234asdf', + b: "-_.!~*'()%20-_.!~*'()", + c: ':@$,%20:@$,', + d: "%26;%3D%2B%23%20%26;%3D%2B%23'", + f: '%20', + g: 'null', + }); + }); +}); diff --git a/src/legacy/utils/encode_query_component.ts b/src/plugins/kibana_utils/common/url/encode_uri_query.ts similarity index 72% rename from src/legacy/utils/encode_query_component.ts rename to src/plugins/kibana_utils/common/url/encode_uri_query.ts index 698d11803649d5..fb60f0ceff10f2 100644 --- a/src/legacy/utils/encode_query_component.ts +++ b/src/plugins/kibana_utils/common/url/encode_uri_query.ts @@ -17,6 +17,9 @@ * under the License. */ +import { ParsedQuery } from 'query-string'; +import { transform } from 'lodash'; + /** * This method is intended for encoding *key* or *value* parts of query component. We need a custom * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be @@ -28,11 +31,27 @@ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" * / "*" / "+" / "," / ";" / "=" */ -export function encodeQueryComponent(val: string, pctEncodeSpaces = false) { +export function encodeUriQuery(val: string, pctEncodeSpaces = false) { return encodeURIComponent(val) .replace(/%40/gi, '@') .replace(/%3A/gi, ':') .replace(/%24/g, '$') .replace(/%2C/gi, ',') + .replace(/%3B/gi, ';') .replace(/%20/g, pctEncodeSpaces ? '%20' : '+'); } + +export const encodeQuery = ( + query: ParsedQuery, + encodeFunction: (val: string, pctEncodeSpaces?: boolean) => string = encodeUriQuery +) => + transform(query, (result, value, key) => { + if (key) { + const singleValue = Array.isArray(value) ? value.join(',') : value; + + result[key] = encodeFunction( + singleValue === undefined || singleValue === null ? '' : singleValue, + true + ); + } + }); diff --git a/src/legacy/ui/public/utils/query_string.d.ts b/src/plugins/kibana_utils/common/url/index.ts similarity index 77% rename from src/legacy/ui/public/utils/query_string.d.ts rename to src/plugins/kibana_utils/common/url/index.ts index 959171443185e0..7b74f07e598ee2 100644 --- a/src/legacy/ui/public/utils/query_string.d.ts +++ b/src/plugins/kibana_utils/common/url/index.ts @@ -17,12 +17,9 @@ * under the License. */ -declare class QueryStringClass { - public decode(queryString: string): any; - public encode(obj: any): string; - public param(key: string, value: string): string; -} +import { encodeUriQuery, encodeQuery } from './encode_uri_query'; -declare const QueryString: QueryStringClass; - -export { QueryString }; +export const url = { + encodeQuery, + encodeUriQuery, +}; diff --git a/src/plugins/kibana_utils/public/history/remove_query_param.ts b/src/plugins/kibana_utils/public/history/remove_query_param.ts index fbf985998b4cd1..bf945e5b064aa5 100644 --- a/src/plugins/kibana_utils/public/history/remove_query_param.ts +++ b/src/plugins/kibana_utils/public/history/remove_query_param.ts @@ -17,16 +17,18 @@ * under the License. */ +import { parse, stringify } from 'query-string'; import { History, Location } from 'history'; -import { parse } from 'querystring'; -import { stringifyQueryString } from '../state_management/url/stringify_query_string'; // TODO: extract it to ../url +import { url } from '../../common'; export function removeQueryParam(history: History, param: string, replace: boolean = true) { const oldLocation = history.location; const search = (oldLocation.search || '').replace(/^\?/, ''); - const query = parse(search); + const query = parse(search, { sort: false }); + delete query[param]; - const newSearch = stringifyQueryString(query); + + const newSearch = stringify(url.encodeQuery(query), { sort: false, encode: false }); const newLocation: Location = { ...oldLocation, search: newSearch, diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index 6a285de12135b2..6971d96e471bdb 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -26,6 +26,7 @@ export { Set, UiComponent, UiComponentInstance, + url, JsonValue, JsonObject, JsonArray, diff --git a/src/plugins/kibana_utils/public/state_management/url/format.ts b/src/plugins/kibana_utils/public/state_management/url/format.ts index 988ee086273823..2912b665ff0147 100644 --- a/src/plugins/kibana_utils/public/state_management/url/format.ts +++ b/src/plugins/kibana_utils/public/state_management/url/format.ts @@ -18,18 +18,22 @@ */ import { format as formatUrl } from 'url'; -import { ParsedUrlQuery } from 'querystring'; +import { stringify, ParsedQuery } from 'query-string'; import { parseUrl, parseUrlHash } from './parse'; -import { stringifyQueryString } from './stringify_query_string'; +import { url as urlUtils } from '../../../common'; export function replaceUrlHashQuery( rawUrl: string, - queryReplacer: (query: ParsedUrlQuery) => ParsedUrlQuery + queryReplacer: (query: ParsedQuery) => ParsedQuery ) { const url = parseUrl(rawUrl); const hash = parseUrlHash(rawUrl); const newQuery = queryReplacer(hash?.query || {}); - const searchQueryString = stringifyQueryString(newQuery); + const searchQueryString = stringify(urlUtils.encodeQuery(newQuery), { + sort: false, + encode: false, + }); + if ((!hash || !hash.search) && !searchQueryString) return rawUrl; // nothing to change. return original url return formatUrl({ ...url, diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts index 1dd204e7172132..40a411d425a54f 100644 --- a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts @@ -18,11 +18,12 @@ */ import { format as formatUrl } from 'url'; +import { stringify } from 'query-string'; import { createBrowserHistory, History } from 'history'; import { decodeState, encodeState } from '../state_encoder'; import { getCurrentUrl, parseUrl, parseUrlHash } from './parse'; -import { stringifyQueryString } from './stringify_query_string'; import { replaceUrlHashQuery } from './format'; +import { url as urlUtils } from '../../../common'; /** * Parses a kibana url and retrieves all the states encoded into url, @@ -243,11 +244,11 @@ export function getRelativeToHistoryPath(absoluteUrl: string, history: History): return formatUrl({ pathname: stripBasename(parsedUrl.pathname), - search: stringifyQueryString(parsedUrl.query), + search: stringify(urlUtils.encodeQuery(parsedUrl.query), { sort: false, encode: false }), hash: parsedHash ? formatUrl({ pathname: parsedHash.pathname, - search: stringifyQueryString(parsedHash.query), + search: stringify(urlUtils.encodeQuery(parsedHash.query), { sort: false, encode: false }), }) : parsedUrl.hash, }); diff --git a/src/plugins/kibana_utils/public/state_management/url/stringify_query_string.ts b/src/plugins/kibana_utils/public/state_management/url/stringify_query_string.ts deleted file mode 100644 index e951dfac29c025..00000000000000 --- a/src/plugins/kibana_utils/public/state_management/url/stringify_query_string.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { stringify, ParsedUrlQuery } from 'querystring'; - -// encodeUriQuery implements the less-aggressive encoding done naturally by -// the browser. We use it to generate the same urls the browser would -export const stringifyQueryString = (query: ParsedUrlQuery) => - stringify(query, undefined, undefined, { - // encode spaces with %20 is needed to produce the same queries as angular does - // https://github.com/angular/angular.js/blob/51c516e7d4f2d10b0aaa4487bd0b52772022207a/src/Angular.js#L1377 - encodeURIComponent: (val: string) => encodeUriQuery(val, true), - }); - -/** - * Extracted from angular.js - * repo: https://github.com/angular/angular.js - * license: MIT - https://github.com/angular/angular.js/blob/51c516e7d4f2d10b0aaa4487bd0b52772022207a/LICENSE - * source: https://github.com/angular/angular.js/blob/51c516e7d4f2d10b0aaa4487bd0b52772022207a/src/Angular.js#L1413-L1432 - */ - -/** - * This method is intended for encoding *key* or *value* parts of query component. We need a custom - * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be - * encoded per http://tools.ietf.org/html/rfc3986: - * query = *( pchar / "/" / "?" ) - * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" - * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" - * pct-encoded = "%" HEXDIG HEXDIG - * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" - * / "*" / "+" / "," / ";" / "=" - */ -export function encodeUriQuery(val: string, pctEncodeSpaces: boolean = false) { - return encodeURIComponent(val) - .replace(/%40/gi, '@') - .replace(/%3A/gi, ':') - .replace(/%24/g, '$') - .replace(/%2C/gi, ',') - .replace(/%3B/gi, ';') - .replace(/%20/g, pctEncodeSpaces ? '%20' : '+'); -} diff --git a/src/plugins/kibana_utils/server/index.ts b/src/plugins/kibana_utils/server/index.ts index f8b79a1b8b339d..b8b768da0192e5 100644 --- a/src/plugins/kibana_utils/server/index.ts +++ b/src/plugins/kibana_utils/server/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { Get, Set, createGetterSetter } from '../common'; +export { Get, Set, createGetterSetter, url } from '../common'; diff --git a/src/plugins/timelion/server/series_functions/quandl.test.js b/src/plugins/timelion/server/series_functions/quandl.test.js index fe5aab512370f9..67d81e56f145f9 100644 --- a/src/plugins/timelion/server/series_functions/quandl.test.js +++ b/src/plugins/timelion/server/series_functions/quandl.test.js @@ -17,16 +17,16 @@ * under the License. */ +import { parse } from 'query-string'; import fn from './quandl'; +import moment from 'moment'; +import fetchMock from 'node-fetch'; const parseURL = require('url').parse; -const parseQueryString = require('querystring').parse; const tlConfig = require('./fixtures/tl_config')(); -import moment from 'moment'; -import fetchMock from 'node-fetch'; function parseUrlParams(url) { - return parseQueryString(parseURL(url).query); + return parse(parseURL(url).query, { sort: false }); } jest.mock('node-fetch', () => diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/url_helpers.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/url_helpers.test.tsx index ac728e72fa8779..286af610707e1e 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/url_helpers.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/url_helpers.test.tsx @@ -16,6 +16,44 @@ describe('toQuery', () => { }); describe('fromQuery', () => { + it('should not encode the following characters', () => { + expect( + fromQuery({ + a: true, + b: 5000, + c: ':' + }) + ).toEqual('a=true&b=5000&c=:'); + }); + + it('should encode the following characters', () => { + expect( + fromQuery({ + a: '@', + b: '.', + c: ';', + d: ' ' + }) + ).toEqual('a=%40&b=.&c=%3B&d=%20'); + }); + + it('should handle null and undefined', () => { + expect( + fromQuery({ + a: undefined, + b: null + }) + ).toEqual('a=&b='); + }); + + it('should handle arrays', () => { + expect( + fromQuery({ + arr: ['a', 'b'] + }) + ).toEqual('arr=a%2Cb'); + }); + it('should parse object to string', () => { expect( fromQuery({ diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/url_helpers.ts b/x-pack/legacy/plugins/apm/public/components/shared/Links/url_helpers.ts index 357ea23d522a01..36465309b736e9 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/url_helpers.ts +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/url_helpers.ts @@ -4,19 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import qs from 'querystring'; +import { parse, stringify } from 'query-string'; import { LocalUIFilterName } from '../../../../server/lib/ui_filters/local_ui_filters/config'; +import { url } from '../../../../../../../../src/plugins/kibana_utils/public'; export function toQuery(search?: string): APMQueryParamsRaw { - return search ? qs.parse(search.slice(1)) : {}; + return search ? parse(search.slice(1), { sort: false }) : {}; } export function fromQuery(query: Record) { - return qs.stringify(query, undefined, undefined, { - encodeURIComponent: (value: string) => { - return encodeURIComponent(value).replace(/%3A/g, ':'); - } - }); + const encodedQuery = url.encodeQuery(query, value => + encodeURIComponent(value).replace(/%3A/g, ':') + ); + + return stringify(encodedQuery, { sort: false, encode: false }); } export type APMQueryParams = { diff --git a/x-pack/legacy/plugins/beats_management/public/containers/with_url_state.tsx b/x-pack/legacy/plugins/beats_management/public/containers/with_url_state.tsx index 71e9163fe22e7d..c8f756da985a70 100644 --- a/x-pack/legacy/plugins/beats_management/public/containers/with_url_state.tsx +++ b/x-pack/legacy/plugins/beats_management/public/containers/with_url_state.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { parse, stringify } from 'querystring'; +import { parse, stringify } from 'query-string'; import React from 'react'; import { withRouter, RouteComponentProps } from 'react-router-dom'; import { FlatObject } from '../frontend_types'; @@ -31,7 +31,9 @@ export class WithURLStateComponent extends React.Compon > { private get URLState(): URLState { // slice because parse does not account for the initial ? in the search string - return parse(decodeURIComponent(this.props.history.location.search).substring(1)) as URLState; + return parse(decodeURIComponent(this.props.history.location.search).substring(1), { + sort: false, + }) as URLState; } private historyListener: (() => void) | null = null; @@ -63,10 +65,13 @@ export class WithURLStateComponent extends React.Compon newState = state; } - const search: string = stringify({ - ...pastState, - ...newState, - }); + const search: string = stringify( + { + ...pastState, + ...newState, + }, + { sort: false } + ); const newLocation = { ...this.props.history.location, diff --git a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts index f7f191a48de825..5adbf4ce66c130 100644 --- a/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts +++ b/x-pack/legacy/plugins/canvas/public/components/workpad_header/workpad_export/utils.ts @@ -7,8 +7,8 @@ import rison from 'rison-node'; // @ts-ignore Untyped local. import { fetch } from '../../../../common/lib/fetch'; -import { getStartPlugins } from '../../../legacy'; import { CanvasWorkpad } from '../../../../types'; +import { url } from '../../../../../../../../src/plugins/kibana_utils/public'; // type of the desired pdf output (print or preserve_layout) const PDF_LAYOUT_TYPE = 'preserve_layout'; @@ -71,11 +71,10 @@ function getPdfUrlParts( export function getPdfUrl(...args: Arguments): string { const urlParts = getPdfUrlParts(...args); + const param = (key: string, val: any) => + url.encodeUriQuery(key, true) + (val === true ? '' : '=' + url.encodeUriQuery(val, true)); - return `${urlParts.createPdfUri}?${getStartPlugins().__LEGACY.QueryString.param( - 'jobParams', - urlParts.createPdfPayload.jobParams - )}`; + return `${urlParts.createPdfUri}?${param('jobParams', urlParts.createPdfPayload.jobParams)}`; } export function createPdf(...args: Arguments) { diff --git a/x-pack/legacy/plugins/canvas/public/legacy.ts b/x-pack/legacy/plugins/canvas/public/legacy.ts index c16bc124747c6e..ea873e6f2296d2 100644 --- a/x-pack/legacy/plugins/canvas/public/legacy.ts +++ b/x-pack/legacy/plugins/canvas/public/legacy.ts @@ -13,8 +13,6 @@ import { absoluteToParsedUrl } from 'ui/url/absolute_to_parsed_url'; // eslint-d import { Storage } from '../../../../../src/plugins/kibana_utils/public'; // eslint-disable-line import/order // @ts-ignore Untyped Kibana Lib import { formatMsg } from '../../../../../src/plugins/kibana_legacy/public'; // eslint-disable-line import/order -// @ts-ignore Untyped Kibana Lib -import { QueryString } from 'ui/utils/query_string'; // eslint-disable-line import/order const shimCoreSetup = { ...npSetup.core, @@ -33,7 +31,6 @@ const shimStartPlugins: CanvasStartDeps = { absoluteToParsedUrl, // ToDo: Copy directly into canvas formatMsg, - QueryString, storage: Storage, // ToDo: Won't be a part of New Platform. Will need to handle internally trackSubUrlForApp: chrome.trackSubUrlForApp, diff --git a/x-pack/legacy/plugins/canvas/public/lib/app_state.ts b/x-pack/legacy/plugins/canvas/public/lib/app_state.ts index c93e505c595fd0..d431202ba75a4d 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/app_state.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/app_state.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import querystring from 'querystring'; +import { parse } from 'query-string'; import { get } from 'lodash'; // @ts-ignore untyped local import { getInitialState } from '../state/initial_state'; @@ -38,7 +38,7 @@ export function getDefaultAppState(): AppState { export function getCurrentAppState(): AppState { const history = historyProvider(getWindow()); const { search } = history.getLocation(); - const qs = !!search ? querystring.parse(search.replace(/^\?/, '')) : {}; + const qs = !!search ? parse(search.replace(/^\?/, ''), { sort: false }) : {}; const appState = assignAppState({}, qs); return appState; diff --git a/x-pack/legacy/plugins/canvas/public/lib/modify_url.ts b/x-pack/legacy/plugins/canvas/public/lib/modify_url.ts index 890138c41d7bf5..d128dc432e9cf6 100644 --- a/x-pack/legacy/plugins/canvas/public/lib/modify_url.ts +++ b/x-pack/legacy/plugins/canvas/public/lib/modify_url.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ParsedUrlQuery } from 'querystring'; +import { ParsedQuery } from 'query-string'; import { format as formatUrl, parse as parseUrl, UrlObject } from 'url'; /** @@ -20,7 +20,7 @@ export interface URLMeaningfulParts { protocol?: string | null; slashes?: boolean | null; port?: string | null; - query: ParsedUrlQuery; + query: ParsedQuery; } /** diff --git a/x-pack/legacy/plugins/canvas/public/plugin.tsx b/x-pack/legacy/plugins/canvas/public/plugin.tsx index a24fd758808bae..44731628cf6535 100644 --- a/x-pack/legacy/plugins/canvas/public/plugin.tsx +++ b/x-pack/legacy/plugins/canvas/public/plugin.tsx @@ -43,7 +43,6 @@ export interface CanvasStartDeps { __LEGACY: { absoluteToParsedUrl: (url: string, basePath: string) => any; formatMsg: any; - QueryString: any; storage: typeof Storage; trackSubUrlForApp: Chrome['trackSubUrlForApp']; }; diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/query_params.js b/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/query_params.js index f0871d62976edf..af462bfeffcf50 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/query_params.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/query_params.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { parse } from 'querystring'; +import { parse } from 'query-string'; export function extractQueryParams(queryString) { const hrefSplit = queryString.split('?'); @@ -12,5 +12,5 @@ export function extractQueryParams(queryString) { return {}; } - return parse(hrefSplit[1]); + return parse(hrefSplit[1], { sort: false }); } diff --git a/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/routing.js b/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/routing.js index bb4b4540b19226..487b1068794f97 100644 --- a/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/routing.js +++ b/x-pack/legacy/plugins/cross_cluster_replication/public/app/services/routing.js @@ -9,7 +9,7 @@ */ import { createLocation } from 'history'; -import { stringify } from 'querystring'; +import { stringify } from 'query-string'; import { APPS, BASE_PATH, BASE_PATH_REMOTE_CLUSTERS } from '../../../common/constants'; const isModifiedEvent = event => @@ -22,16 +22,7 @@ const queryParamsFromObject = (params, encodeParams = false) => { return; } - const paramsStr = stringify( - params, - '&', - '=', - encodeParams - ? {} - : { - encodeURIComponent: val => val, // Don't encode special chars - } - ); + const paramsStr = stringify(params, { sort: false, encode: encodeParams }); return `?${paramsStr}`; }; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx index 536dd24faa7c16..9fbba94407dc07 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/analyze_in_ml_button.tsx @@ -8,10 +8,11 @@ import { EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import { encode } from 'rison-node'; -import { QueryString } from 'ui/utils/query_string'; import url from 'url'; +import { stringify } from 'query-string'; import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; import { TimeRange } from '../../../../common/http_api/shared/time_range'; +import { url as urlUtils } from '../../../../../../../../src/plugins/kibana_utils/public'; export const AnalyzeInMlButton: React.FunctionComponent<{ jobId: string; @@ -61,7 +62,7 @@ const getOverallAnomalyExplorerLink = (pathname: string, jobId: string, timeRang }, }); - const hash = `/explorer?${QueryString.encode({ _g })}`; + const hash = `/explorer?${stringify(urlUtils.encodeQuery({ _g }), { encode: false })}`; return url.format({ pathname, @@ -94,7 +95,10 @@ const getPartitionSpecificSingleMetricViewerLink = ( }, }); - const hash = `/timeseriesexplorer?${QueryString.encode({ _g, _a })}`; + const hash = `/timeseriesexplorer?${stringify(urlUtils.encodeQuery({ _g, _a }), { + sort: false, + encode: false, + })}`; return url.format({ pathname, diff --git a/x-pack/legacy/plugins/infra/public/containers/with_state_from_location.tsx b/x-pack/legacy/plugins/infra/public/containers/with_state_from_location.tsx index ec6345c49c3031..6f7baf6b98b625 100644 --- a/x-pack/legacy/plugins/infra/public/containers/with_state_from_location.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/with_state_from_location.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { parse, stringify } from 'query-string'; import { Location } from 'history'; import omit from 'lodash/fp/omit'; -import { parse as parseQueryString, stringify as stringifyQueryString } from 'querystring'; import React from 'react'; import { RouteComponentProps, withRouter } from 'react-router-dom'; // eslint-disable-next-line @typescript-eslint/camelcase @@ -102,7 +102,7 @@ const encodeRisonAppState = (state: AnyObject) => ({ export const mapRisonAppLocationToState = ( mapState: (risonAppState: AnyObject) => State = (state: AnyObject) => state as State ) => (location: Location): State => { - const queryValues = parseQueryString(location.search.substring(1)); + const queryValues = parse(location.search.substring(1), { sort: false }); const decodedState = decodeRisonAppState(queryValues); return mapState(decodedState); }; @@ -110,17 +110,20 @@ export const mapRisonAppLocationToState = ( export const mapStateToRisonAppLocation = ( mapState: (state: State) => AnyObject = (state: State) => state ) => (state: State, location: Location): Location => { - const previousQueryValues = parseQueryString(location.search.substring(1)); + const previousQueryValues = parse(location.search.substring(1), { sort: false }); const previousState = decodeRisonAppState(previousQueryValues); const encodedState = encodeRisonAppState({ ...previousState, ...mapState(state), }); - const newQueryValues = stringifyQueryString({ - ...previousQueryValues, - ...encodedState, - }); + const newQueryValues = stringify( + { + ...previousQueryValues, + ...encodedState, + }, + { sort: false } + ); return { ...location, search: `?${newQueryValues}`, diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx index a418be01d1ed2d..e9ec053f8c6099 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.test.tsx @@ -19,7 +19,7 @@ describe('RedirectToLogs component', () => { expect(component).toMatchInlineSnapshot(` `); }); @@ -33,7 +33,7 @@ describe('RedirectToLogs component', () => { expect(component).toMatchInlineSnapshot(` `); }); @@ -45,7 +45,7 @@ describe('RedirectToLogs component', () => { expect(component).toMatchInlineSnapshot(` `); }); diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.test.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.test.tsx index 2d1f3a32988aab..1e97072cac109f 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.test.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.test.tsx @@ -35,7 +35,7 @@ describe('RedirectToNodeLogs component', () => { expect(component).toMatchInlineSnapshot(` `); }); @@ -47,7 +47,7 @@ describe('RedirectToNodeLogs component', () => { expect(component).toMatchInlineSnapshot(` `); }); @@ -59,7 +59,7 @@ describe('RedirectToNodeLogs component', () => { expect(component).toMatchInlineSnapshot(` `); }); @@ -73,7 +73,7 @@ describe('RedirectToNodeLogs component', () => { expect(component).toMatchInlineSnapshot(` `); }); @@ -89,7 +89,7 @@ describe('RedirectToNodeLogs component', () => { expect(component).toMatchInlineSnapshot(` `); }); @@ -103,7 +103,7 @@ describe('RedirectToNodeLogs component', () => { expect(component).toMatchInlineSnapshot(` `); }); diff --git a/x-pack/legacy/plugins/infra/public/utils/url_state.tsx b/x-pack/legacy/plugins/infra/public/utils/url_state.tsx index 66bb4308d1d16a..58835715fe55ca 100644 --- a/x-pack/legacy/plugins/infra/public/utils/url_state.tsx +++ b/x-pack/legacy/plugins/infra/public/utils/url_state.tsx @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +import { parse, stringify } from 'query-string'; import { History, Location } from 'history'; import throttle from 'lodash/fp/throttle'; import React from 'react'; import { Route, RouteProps } from 'react-router-dom'; import { decode, encode, RisonValue } from 'rison-node'; - -import { QueryString } from 'ui/utils/query_string'; +import { url } from '../../../../../../src/plugins/kibana_utils/public'; interface UrlStateContainerProps { urlState: UrlState | undefined; @@ -145,7 +145,9 @@ const encodeRisonUrlState = (state: any) => encode(state); export const getQueryStringFromLocation = (location: Location) => location.search.substring(1); export const getParamFromQueryString = (queryString: string, key: string): string | undefined => { - const queryParam = QueryString.decode(queryString)[key]; + const parsedQueryString: Record = parse(queryString, { sort: false }); + const queryParam = parsedQueryString[key]; + return Array.isArray(queryParam) ? queryParam[0] : queryParam; }; @@ -153,13 +155,17 @@ export const replaceStateKeyInQueryString = ( stateKey: string, urlState: UrlState | undefined ) => (queryString: string) => { - const previousQueryValues = QueryString.decode(queryString); + const previousQueryValues = parse(queryString, { sort: false }); const encodedUrlState = typeof urlState !== 'undefined' ? encodeRisonUrlState(urlState) : undefined; - return QueryString.encode({ - ...previousQueryValues, - [stateKey]: encodedUrlState, - }); + + return stringify( + url.encodeQuery({ + ...previousQueryValues, + [stateKey]: encodedUrlState, + }), + { sort: false, encode: false } + ); }; const replaceQueryStringInLocation = (location: Location, queryString: string): Location => { diff --git a/x-pack/legacy/plugins/infra/public/utils/use_url_state.ts b/x-pack/legacy/plugins/infra/public/utils/use_url_state.ts index 79a5d552bcd782..284af62e52fbbb 100644 --- a/x-pack/legacy/plugins/infra/public/utils/use_url_state.ts +++ b/x-pack/legacy/plugins/infra/public/utils/use_url_state.ts @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +import { parse, stringify } from 'query-string'; import { Location } from 'history'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { decode, encode, RisonValue } from 'rison-node'; -import { QueryString } from 'ui/utils/query_string'; +import { url } from '../../../../../../src/plugins/kibana_utils/public'; import { useHistory } from './history_context'; @@ -84,7 +85,7 @@ export const useUrlState = ({ return [state, setState] as [typeof state, typeof setState]; }; -const decodeRisonUrlState = (value: string | undefined): RisonValue | undefined => { +const decodeRisonUrlState = (value: string | undefined | null): RisonValue | undefined => { try { return value ? decode(value) : undefined; } catch (error) { @@ -99,8 +100,10 @@ const encodeRisonUrlState = (state: any) => encode(state); const getQueryStringFromLocation = (location: Location) => location.search.substring(1); -const getParamFromQueryString = (queryString: string, key: string): string | undefined => { - const queryParam = QueryString.decode(queryString)[key]; +const getParamFromQueryString = (queryString: string, key: string) => { + const parsedQueryString = parse(queryString, { sort: false }); + const queryParam = parsedQueryString[key]; + return Array.isArray(queryParam) ? queryParam[0] : queryParam; }; @@ -108,13 +111,17 @@ export const replaceStateKeyInQueryString = ( stateKey: string, urlState: UrlState | undefined ) => (queryString: string) => { - const previousQueryValues = QueryString.decode(queryString); + const previousQueryValues = parse(queryString, { sort: false }); const encodedUrlState = typeof urlState !== 'undefined' ? encodeRisonUrlState(urlState) : undefined; - return QueryString.encode({ - ...previousQueryValues, - [stateKey]: encodedUrlState, - }); + + return stringify( + url.encodeQuery({ + ...previousQueryValues, + [stateKey]: encodedUrlState, + }), + { sort: false, encode: false } + ); }; const replaceQueryStringInLocation = (location: Location, queryString: string): Location => { diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx index 3ca23998d5b75c..e00ff0333bb732 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/data_frame_analytics/analytics_job_exploration.tsx @@ -4,12 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { parse } from 'query-string'; import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; import { decode } from 'rison-node'; - -// @ts-ignore -import queryString from 'query-string'; import { MlRoute, PageLoader, PageProps } from '../../router'; import { useResolver } from '../../use_resolver'; import { basicResolvers } from '../../resolvers'; @@ -36,7 +34,8 @@ export const analyticsJobExplorationRoute: MlRoute = { const PageWrapper: FC = ({ location, deps }) => { const { context } = useResolver('', undefined, deps.config, basicResolvers(deps)); - const { _g } = queryString.parse(location.search); + const { _g }: Record = parse(location.search, { sort: false }); + let globalState: any = null; try { globalState = decode(_g); diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/datavisualizer/index_based.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/datavisualizer/index_based.tsx index fa4745f19e3b4e..74ab916cb443fc 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/routes/datavisualizer/index_based.tsx +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/datavisualizer/index_based.tsx @@ -4,11 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { parse } from 'query-string'; import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; - -// @ts-ignore -import queryString from 'query-string'; import { MlRoute, PageLoader, PageProps } from '../../router'; import { useResolver } from '../../use_resolver'; import { Page } from '../../../datavisualizer/index_based'; @@ -37,7 +35,7 @@ export const indexBasedRoute: MlRoute = { }; const PageWrapper: FC = ({ location, deps }) => { - const { index, savedSearchId } = queryString.parse(location.search); + const { index, savedSearchId }: Record = parse(location.search, { sort: false }); const { context } = useResolver(index, savedSearchId, deps.config, { checkBasicLicense, loadIndexPatterns: () => loadIndexPatterns(deps.indexPatterns), diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/new_job/job_type.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/new_job/job_type.tsx index c2e87f065116e5..f0a25d880a082d 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/routes/new_job/job_type.tsx +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/new_job/job_type.tsx @@ -4,11 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { parse } from 'query-string'; import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; - -// @ts-ignore -import queryString from 'query-string'; import { MlRoute, PageLoader, PageProps } from '../../router'; import { useResolver } from '../../use_resolver'; import { basicResolvers } from '../../resolvers'; @@ -33,7 +31,7 @@ export const jobTypeRoute: MlRoute = { }; const PageWrapper: FC = ({ location, deps }) => { - const { index, savedSearchId } = queryString.parse(location.search); + const { index, savedSearchId }: Record = parse(location.search, { sort: false }); const { context } = useResolver(index, savedSearchId, deps.config, basicResolvers(deps)); return ( diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/new_job/recognize.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/new_job/recognize.tsx index 78f72a7b7a39b7..12687fd71edc5c 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/routes/new_job/recognize.tsx +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/new_job/recognize.tsx @@ -4,11 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { parse } from 'query-string'; import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; -// @ts-ignore -import queryString from 'query-string'; - import { MlRoute, PageLoader, PageProps } from '../../router'; import { useResolver } from '../../use_resolver'; import { basicResolvers } from '../../resolvers'; @@ -41,7 +39,7 @@ export const checkViewOrCreateRoute: MlRoute = { }; const PageWrapper: FC = ({ location, deps }) => { - const { id, index, savedSearchId } = queryString.parse(location.search); + const { id, index, savedSearchId }: Record = parse(location.search, { sort: false }); const { context, results } = useResolver(index, savedSearchId, deps.config, { ...basicResolvers(deps), existingJobsAndGroups: mlJobService.getJobAndGroupIds, @@ -55,7 +53,10 @@ const PageWrapper: FC = ({ location, deps }) => { }; const CheckViewOrCreateWrapper: FC = ({ location, deps }) => { - const { id: moduleId, index: indexPatternId } = queryString.parse(location.search); + const { id: moduleId, index: indexPatternId }: Record = parse(location.search, { + sort: false, + }); + // the single resolver checkViewOrCreateJobs redirects only. so will always reject useResolver(undefined, undefined, deps.config, { checkViewOrCreateJobs: () => checkViewOrCreateJobs(moduleId, indexPatternId), diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/new_job/wizard.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/new_job/wizard.tsx index 230d96456427c7..b1256e21888d91 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/routes/new_job/wizard.tsx +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/new_job/wizard.tsx @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { parse } from 'query-string'; import React, { FC } from 'react'; import { i18n } from '@kbn/i18n'; -// @ts-ignore -import queryString from 'query-string'; import { basicResolvers } from '../../resolvers'; import { MlRoute, PageLoader, PageProps } from '../../router'; @@ -113,7 +112,7 @@ export const categorizationRoute: MlRoute = { }; const PageWrapper: FC = ({ location, jobType, deps }) => { - const { index, savedSearchId } = queryString.parse(location.search); + const { index, savedSearchId }: Record = parse(location.search, { sort: false }); const { context, results } = useResolver(index, savedSearchId, deps.config, { ...basicResolvers(deps), privileges: checkCreateJobsPrivilege, diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx index 2bf3d50c3678c5..5bc2435db078c0 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -8,8 +8,6 @@ import { isEqual } from 'lodash'; import React, { FC, useCallback, useEffect, useState } from 'react'; import { usePrevious } from 'react-use'; import moment from 'moment'; -// @ts-ignore -import queryString from 'query-string'; import { i18n } from '@kbn/i18n'; diff --git a/x-pack/legacy/plugins/ml/public/application/util/url_state.ts b/x-pack/legacy/plugins/ml/public/application/util/url_state.ts index e7d5a94e2694fb..b0699116895d47 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/url_state.ts +++ b/x-pack/legacy/plugins/ml/public/application/util/url_state.ts @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { parse, stringify } from 'query-string'; import { useCallback } from 'react'; import { isEqual } from 'lodash'; -// @ts-ignore -import queryString from 'query-string'; import { decode, encode } from 'rison-node'; import { useHistory, useLocation } from 'react-router-dom'; @@ -33,12 +32,12 @@ function isRisonSerializationRequired(queryParam: string): boolean { export function getUrlState(search: string): Dictionary { const urlState: Dictionary = {}; - const parsedQueryString = queryString.parse(search); + const parsedQueryString = parse(search, { sort: false }); try { Object.keys(parsedQueryString).forEach(a => { if (isRisonSerializationRequired(a)) { - urlState[a] = decode(parsedQueryString[a]) as Dictionary; + urlState[a] = decode(parsedQueryString[a] as string); } else { urlState[a] = parsedQueryString[a]; } @@ -64,7 +63,7 @@ export const useUrlState = (accessor: string): UrlState => { const setUrlState = useCallback( (attribute: string | Dictionary, value?: any) => { const urlState = getUrlState(search); - const parsedQueryString = queryString.parse(search); + const parsedQueryString = parse(search, { sort: false }); if (!Object.prototype.hasOwnProperty.call(urlState, accessor)) { urlState[accessor] = {}; @@ -84,7 +83,7 @@ export const useUrlState = (accessor: string): UrlState => { } try { - const oldLocationSearch = queryString.stringify(parsedQueryString, { encode: false }); + const oldLocationSearch = stringify(parsedQueryString, { sort: false, encode: false }); Object.keys(urlState).forEach(a => { if (isRisonSerializationRequired(a)) { @@ -93,11 +92,11 @@ export const useUrlState = (accessor: string): UrlState => { parsedQueryString[a] = urlState[a]; } }); - const newLocationSearch = queryString.stringify(parsedQueryString, { encode: false }); + const newLocationSearch = stringify(parsedQueryString, { sort: false, encode: false }); if (oldLocationSearch !== newLocationSearch) { history.push({ - search: queryString.stringify(parsedQueryString), + search: stringify(parsedQueryString, { sort: false }), }); } } catch (error) { diff --git a/x-pack/legacy/plugins/remote_clusters/public/app/services/query_params.js b/x-pack/legacy/plugins/remote_clusters/public/app/services/query_params.js index f0871d62976edf..af462bfeffcf50 100644 --- a/x-pack/legacy/plugins/remote_clusters/public/app/services/query_params.js +++ b/x-pack/legacy/plugins/remote_clusters/public/app/services/query_params.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { parse } from 'querystring'; +import { parse } from 'query-string'; export function extractQueryParams(queryString) { const hrefSplit = queryString.split('?'); @@ -12,5 +12,5 @@ export function extractQueryParams(queryString) { return {}; } - return parse(hrefSplit[1]); + return parse(hrefSplit[1], { sort: false }); } diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/encode_uri_query.js b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/encode_uri_query.js deleted file mode 100644 index ce2346b0f28dcd..00000000000000 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/encode_uri_query.js +++ /dev/null @@ -1,39 +0,0 @@ -/* eslint-disable @kbn/eslint/require-license-header */ - -// This function was extracted from angular v1.3 - -/* @notice - * This product includes code that was extracted from angular@1.3. - * Original license: - * The MIT License - * - * Copyright (c) 2010-2014 Google, Inc. http://angularjs.org - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -export function encodeUriQuery(val, pctEncodeSpaces) { - return encodeURIComponent(val) - .replace(/%40/gi, '@') - .replace(/%3A/gi, ':') - .replace(/%24/g, '$') - .replace(/%2C/gi, ',') - .replace(/%3B/gi, ';') - .replace(/%20/g, pctEncodeSpaces ? '%20' : '+'); -} diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/uri_encode.js b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/uri_encode.js index 5b93461bfaffb4..f764271c22a2df 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/uri_encode.js +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/uri_encode.js @@ -5,20 +5,20 @@ */ import { forEach, isArray } from 'lodash'; -import { encodeUriQuery } from './encode_uri_query'; +import { url } from '../../../../../../../../src/plugins/kibana_utils/server'; function toKeyValue(obj) { const parts = []; forEach(obj, function(value, key) { if (isArray(value)) { forEach(value, function(arrayValue) { - const keyStr = encodeUriQuery(key, true); - const valStr = arrayValue === true ? '' : '=' + encodeUriQuery(arrayValue, true); + const keyStr = url.encodeUriQuery(key, true); + const valStr = arrayValue === true ? '' : '=' + url.encodeUriQuery(arrayValue, true); parts.push(keyStr + valStr); }); } else { - const keyStr = encodeUriQuery(key, true); - const valStr = value === true ? '' : '=' + encodeUriQuery(value, true); + const keyStr = url.encodeUriQuery(key, true); + const valStr = value === true ? '' : '=' + url.encodeUriQuery(value, true); parts.push(keyStr + valStr); } }); @@ -27,5 +27,5 @@ function toKeyValue(obj) { export const uriEncode = { stringify: toKeyValue, - string: encodeUriQuery, + string: url.encodeUriQuery, }; diff --git a/x-pack/legacy/plugins/reporting/public/lib/reporting_client.ts b/x-pack/legacy/plugins/reporting/public/lib/reporting_client.ts index 9056c7967b4a84..d471dc57fc9e1b 100644 --- a/x-pack/legacy/plugins/reporting/public/lib/reporting_client.ts +++ b/x-pack/legacy/plugins/reporting/public/lib/reporting_client.ts @@ -3,16 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - +import { stringify } from 'query-string'; import { npStart } from 'ui/new_platform'; -import querystring from 'querystring'; - -const { core } = npStart; - // @ts-ignore import rison from 'rison-node'; import { add } from './job_completion_notifications'; +const { core } = npStart; const API_BASE_URL = '/api/reporting/generate'; interface JobParams { @@ -20,7 +17,7 @@ interface JobParams { } export const getReportingJobPath = (exportType: string, jobParams: JobParams) => { - const params = querystring.stringify({ jobParams: rison.encode(jobParams) }); + const params = stringify({ jobParams: rison.encode(jobParams) }); return `${core.http.basePath.prepend(API_BASE_URL)}/${exportType}?${params}`; }; diff --git a/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/ml_host_conditional_container.tsx b/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/ml_host_conditional_container.tsx index 8bd97304a7e21f..b5aacdf664c67a 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/ml_host_conditional_container.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/conditional_links/ml_host_conditional_container.tsx @@ -4,16 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ +import { parse, stringify } from 'query-string'; import React from 'react'; import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom'; -import { QueryString } from 'ui/utils/query_string'; import { addEntitiesToKql } from './add_entities_to_kql'; import { replaceKQLParts } from './replace_kql_parts'; import { emptyEntity, multipleEntities, getMultipleEntities } from './entity_helpers'; import { SiemPageName } from '../../../pages/home/types'; import { HostsTableType } from '../../../store/hosts/model'; +import { url as urlUtils } from '../../../../../../../../src/plugins/kibana_utils/public'; + interface QueryStringType { '?_g': string; query: string | null; @@ -29,13 +31,17 @@ export const MlHostConditionalContainer = React.memo(({ exact path={url} render={({ location }) => { - const queryStringDecoded: QueryStringType = QueryString.decode( - location.search.substring(1) - ); + const queryStringDecoded = parse(location.search.substring(1), { + sort: false, + }) as Required; + if (queryStringDecoded.query != null) { queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query); } - const reEncoded = QueryString.encode(queryStringDecoded); + const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { + sort: false, + encode: false, + }); return ; }} /> @@ -47,14 +53,19 @@ export const MlHostConditionalContainer = React.memo(({ params: { hostName }, }, }) => { - const queryStringDecoded: QueryStringType = QueryString.decode( - location.search.substring(1) - ); + const queryStringDecoded = parse(location.search.substring(1), { + sort: false, + }) as Required; + if (queryStringDecoded.query != null) { queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query); } if (emptyEntity(hostName)) { - const reEncoded = QueryString.encode(queryStringDecoded); + const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { + sort: false, + encode: false, + }); + return ( ); @@ -65,12 +76,20 @@ export const MlHostConditionalContainer = React.memo(({ hosts, queryStringDecoded.query || '' ); - const reEncoded = QueryString.encode(queryStringDecoded); + const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { + sort: false, + encode: false, + }); + return ( ); } else { - const reEncoded = QueryString.encode(queryStringDecoded); + const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { + sort: false, + encode: false, + }); + return ( { - const queryStringDecoded: QueryStringType = QueryString.decode( - location.search.substring(1) - ); + const queryStringDecoded = parse(location.search.substring(1), { + sort: false, + }) as Required; + if (queryStringDecoded.query != null) { queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query); } - const reEncoded = QueryString.encode(queryStringDecoded); + + const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { + sort: false, + encode: false, + }); + return ; }} /> @@ -46,14 +54,20 @@ export const MlNetworkConditionalContainer = React.memo { - const queryStringDecoded: QueryStringType = QueryString.decode( - location.search.substring(1) - ); + const queryStringDecoded = parse(location.search.substring(1), { + sort: false, + }) as Required; + if (queryStringDecoded.query != null) { queryStringDecoded.query = replaceKQLParts(queryStringDecoded.query); } + if (emptyEntity(ip)) { - const reEncoded = QueryString.encode(queryStringDecoded); + const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { + sort: false, + encode: false, + }); + return ; } else if (multipleEntities(ip)) { const ips: string[] = getMultipleEntities(ip); @@ -62,10 +76,16 @@ export const MlNetworkConditionalContainer = React.memo; } else { - const reEncoded = QueryString.encode(queryStringDecoded); + const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), { + sort: false, + encode: false, + }); return ; } }} diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts b/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts index 34f1ea156eee79..7be775ef0c0e48 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/components/url_state/helpers.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { parse, stringify } from 'query-string'; import { decode, encode } from 'rison-node'; import * as H from 'history'; -import { QueryString } from 'ui/utils/query_string'; import { Query, Filter } from 'src/plugins/data/public'; import { isEmpty } from 'lodash/fp'; @@ -24,6 +24,8 @@ import { UpdateUrlStateString, } from './types'; +import { url } from '../../../../../../../src/plugins/kibana_utils/public'; + export const decodeRisonUrlState = (value: string | undefined): T | null => { try { return value ? ((decode(value) as unknown) as T) : null; @@ -40,30 +42,35 @@ export const encodeRisonUrlState = (state: any) => encode(state); export const getQueryStringFromLocation = (search: string) => search.substring(1); -export const getParamFromQueryString = (queryString: string, key: string): string | undefined => { - const queryParam = QueryString.decode(queryString)[key]; +export const getParamFromQueryString = (queryString: string, key: string) => { + const parsedQueryString = parse(queryString, { sort: false }); + const queryParam = parsedQueryString[key]; + return Array.isArray(queryParam) ? queryParam[0] : queryParam; }; export const replaceStateKeyInQueryString = (stateKey: string, urlState: T) => ( queryString: string ): string => { - const previousQueryValues = QueryString.decode(queryString); + const previousQueryValues = parse(queryString, { sort: false }); if (urlState == null || (typeof urlState === 'string' && urlState === '')) { delete previousQueryValues[stateKey]; - return QueryString.encode({ - ...previousQueryValues, - }); + + return stringify(url.encodeQuery(previousQueryValues), { sort: false, encode: false }); } // ಠ_ಠ Code was copied from x-pack/legacy/plugins/infra/public/utils/url_state.tsx ಠ_ಠ // Remove this if these utilities are promoted to kibana core const encodedUrlState = typeof urlState !== 'undefined' ? encodeRisonUrlState(urlState) : undefined; - return QueryString.encode({ - ...previousQueryValues, - [stateKey]: encodedUrlState, - }); + + return stringify( + url.encodeQuery({ + ...previousQueryValues, + [stateKey]: encodedUrlState, + }), + { sort: false, encode: false } + ); }; export const replaceQueryStringInLocation = ( diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/index_mocked.test.tsx b/x-pack/legacy/plugins/siem/public/components/url_state/index_mocked.test.tsx index 6995bc8bf1d401..4adc17b32e1891 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/index_mocked.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/url_state/index_mocked.test.tsx @@ -147,7 +147,7 @@ describe('UrlStateContainer - lodash.throttle mocked to test update url', () => hash: '', pathname: '/network', search: - '?timeline=(id:hello_timeline_id,isOpen:!t)&timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))', + '?timerange=(global:(linkTo:!(timeline),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1558048243696,fromStr:now-24h,kind:relative,to:1558134643697,toStr:now)))&timeline=(id:hello_timeline_id,isOpen:!t)', state: '', }); }); diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx index 642a12411e6f33..8192fe4e026af7 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/snapshot_list/snapshot_list.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +import { parse } from 'query-string'; import React, { Fragment, useState, useEffect } from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import { parse } from 'querystring'; import { EuiButton, EuiCallOut, EuiLink, EuiEmptyPrompt, EuiSpacer, EuiIcon } from '@elastic/eui'; import { APP_SLM_CLUSTER_PRIVILEGES } from '../../../../../common/constants'; @@ -86,7 +86,7 @@ export const SnapshotList: React.FunctionComponent(undefined); useEffect(() => { if (search) { - const parsedParams = parse(search.replace(/^\?/, '')); + const parsedParams = parse(search.replace(/^\?/, ''), { sort: false }); const { repository, policy } = parsedParams; if (policy && policy !== filteredPolicy) { diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/repository_add/repository_add.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/repository_add/repository_add.tsx index b4a76ff4329cf3..a12ecb4baef5da 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/repository_add/repository_add.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/repository_add/repository_add.tsx @@ -3,9 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + +import { parse } from 'query-string'; import React, { useEffect, useState } from 'react'; import { RouteComponentProps } from 'react-router-dom'; -import { parse } from 'querystring'; import { EuiPageBody, EuiPageContent, EuiSpacer, EuiTitle } from '@elastic/eui'; import { Repository, EmptyRepository } from '../../../../common/types'; @@ -44,7 +45,8 @@ export const RepositoryAdd: React.FunctionComponent = ({ if (error) { setSaveError(error); } else { - const { redirect } = parse(search.replace(/^\?/, '')); + const { redirect } = parse(search.replace(/^\?/, ''), { sort: false }); + history.push(redirect ? (redirect as string) : `${BASE_PATH}/${section}/${name}`); } }; diff --git a/x-pack/legacy/plugins/uptime/public/hooks/use_url_params.ts b/x-pack/legacy/plugins/uptime/public/hooks/use_url_params.ts index e509e14223006d..dc309943d7cf94 100644 --- a/x-pack/legacy/plugins/uptime/public/hooks/use_url_params.ts +++ b/x-pack/legacy/plugins/uptime/public/hooks/use_url_params.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import qs from 'querystring'; +import { parse, stringify } from 'query-string'; import { useLocation, useHistory } from 'react-router-dom'; import { UptimeUrlParams, getSupportedUrlParams } from '../lib/helper'; @@ -23,14 +23,17 @@ export const useUrlParams: UptimeUrlParamsHook = () => { search = location.search; } - const params = search ? { ...qs.parse(search[0] === '?' ? search.slice(1) : search) } : {}; + const params = search + ? parse(search[0] === '?' ? search.slice(1) : search, { sort: false }) + : {}; + return getSupportedUrlParams(params); }; const updateUrlParams: UpdateUrlParams = updatedParams => { if (!history || !location) return; const { pathname, search } = location; - const currentParams: any = qs.parse(search[0] === '?' ? search.slice(1) : search); + const currentParams = parse(search[0] === '?' ? search.slice(1) : search, { sort: false }); const mergedParams = { ...currentParams, ...updatedParams, @@ -38,7 +41,7 @@ export const useUrlParams: UptimeUrlParamsHook = () => { history.push({ pathname, - search: qs.stringify( + search: stringify( // drop any parameters that have no value Object.keys(mergedParams).reduce((params, key) => { const value = mergedParams[key]; @@ -49,7 +52,8 @@ export const useUrlParams: UptimeUrlParamsHook = () => { ...params, [key]: value, }; - }, {}) + }, {}), + { sort: false } ), }); }; diff --git a/x-pack/legacy/plugins/uptime/public/lib/helper/stringify_url_params.ts b/x-pack/legacy/plugins/uptime/public/lib/helper/stringify_url_params.ts index 7d00a27d69032d..a8ce86c4399e23 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/helper/stringify_url_params.ts +++ b/x-pack/legacy/plugins/uptime/public/lib/helper/stringify_url_params.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import qs from 'querystring'; +import { stringify } from 'query-string'; import { UptimeUrlParams } from './url_params'; import { CLIENT_DEFAULTS } from '../../../common/constants'; @@ -38,5 +38,5 @@ export const stringifyUrlParams = (params: Partial, ignoreEmpty } }); } - return `?${qs.stringify(params)}`; + return `?${stringify(params, { sort: false })}`; }; diff --git a/x-pack/legacy/plugins/uptime/public/lib/helper/url_params/get_supported_url_params.ts b/x-pack/legacy/plugins/uptime/public/lib/helper/url_params/get_supported_url_params.ts index f01448d9e37acd..11dfc3f21b1bf4 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/helper/url_params/get_supported_url_params.ts +++ b/x-pack/legacy/plugins/uptime/public/lib/helper/url_params/get_supported_url_params.ts @@ -46,7 +46,7 @@ const { * require further development. */ export const getSupportedUrlParams = (params: { - [key: string]: string | string[] | undefined; + [key: string]: string | string[] | undefined | null; }): UptimeUrlParams => { const filteredParams: { [key: string]: string | undefined } = {}; Object.keys(params).forEach(key => { diff --git a/x-pack/legacy/plugins/uptime/public/state/api/ping.ts b/x-pack/legacy/plugins/uptime/public/state/api/ping.ts index e0c358fe40e710..c61bf42c8c90e8 100644 --- a/x-pack/legacy/plugins/uptime/public/state/api/ping.ts +++ b/x-pack/legacy/plugins/uptime/public/state/api/ping.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import qs from 'querystring'; +import { stringify } from 'query-string'; import { getApiPath } from '../../lib/helper'; import { APIFn } from './types'; import { GetPingHistogramParams, HistogramResult } from '../../../common/types'; @@ -25,7 +25,7 @@ export const fetchPingHistogram: APIFn ...(statusFilter && { statusFilter }), ...(filters && { filters }), }; - const urlParams = qs.stringify(params).toString(); + const urlParams = stringify(params, { sort: false }); const response = await fetch(`${url}?${urlParams}`); if (!response.ok) { throw new Error(response.statusText); diff --git a/x-pack/package.json b/x-pack/package.json index 921f6ad9911886..c1225f609ebbbd 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -292,7 +292,9 @@ "proper-lockfile": "^3.2.0", "puid": "1.0.7", "puppeteer-core": "^1.19.0", + "query-string": "6.10.1", "raw-loader": "3.1.0", + "re-resizable": "^6.1.1", "react": "^16.12.0", "react-apollo": "^2.1.4", "react-beautiful-dnd": "^8.0.7", @@ -324,7 +326,6 @@ "request": "^2.88.0", "reselect": "3.0.1", "resize-observer-polyfill": "^1.5.0", - "re-resizable": "^6.1.1", "rison-node": "0.3.1", "rxjs": "^6.5.3", "semver": "5.7.0", diff --git a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts index 11bac195653c64..4a7fac147852ba 100644 --- a/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts +++ b/x-pack/plugins/endpoint/public/applications/endpoint/store/alerts/middleware.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import qs from 'querystring'; +import { parse } from 'query-string'; import { HttpFetchQuery } from 'src/core/public'; import { AppAction } from '../action'; import { MiddlewareFactory, AlertListData } from '../../types'; export const alertMiddlewareFactory: MiddlewareFactory = coreStart => { - const qp = qs.parse(window.location.search.slice(1)); + const qp = parse(window.location.search.slice(1), { sort: false }); return api => next => async (action: AppAction) => { next(action); diff --git a/x-pack/plugins/spaces/server/lib/utils/url.ts b/x-pack/plugins/spaces/server/lib/utils/url.ts index a5797c0f87868c..c91934bb99f1f3 100644 --- a/x-pack/plugins/spaces/server/lib/utils/url.ts +++ b/x-pack/plugins/spaces/server/lib/utils/url.ts @@ -8,7 +8,7 @@ // DIRECT COPY FROM `src/core/utils/url`, since it's not possible to import from there, // nor can I re-export from `src/core/server`... -import { ParsedUrlQuery } from 'querystring'; +import { ParsedQuery } from 'query-string'; import { format as formatUrl, parse as parseUrl, UrlObject } from 'url'; export interface URLMeaningfulParts { @@ -19,7 +19,7 @@ export interface URLMeaningfulParts { protocol: string | null; slashes: boolean | null; port: string | null; - query: ParsedUrlQuery | {}; + query: ParsedQuery | {}; } /** diff --git a/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js b/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js index be6139ed7a0a77..e1a435e000faed 100644 --- a/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js +++ b/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js @@ -5,8 +5,7 @@ */ import expect from '@kbn/expect'; -import querystring from 'querystring'; - +import { stringify } from 'query-string'; import { registerHelpers } from './rollup.test_helpers'; import { INDEX_TO_ROLLUP_MAPPINGS, INDEX_PATTERNS_EXTENSION_BASE_PATH } from './constants'; import { getRandomString } from './lib'; @@ -39,7 +38,7 @@ export default function({ getService }) { it('"params" is required', async () => { params = { pattern: 'foo' }; - uri = `${BASE_URI}?${querystring.stringify(params)}`; + uri = `${BASE_URI}?${stringify(params, { sort: false })}`; ({ body } = await supertest.get(uri).expect(400)); expect(body.message).to.contain( '[request query.params]: expected value of type [string]' @@ -48,14 +47,14 @@ export default function({ getService }) { it('"params" must be a valid JSON string', async () => { params = { pattern: 'foo', params: 'foobarbaz' }; - uri = `${BASE_URI}?${querystring.stringify(params)}`; + uri = `${BASE_URI}?${stringify(params, { sort: false })}`; ({ body } = await supertest.get(uri).expect(400)); expect(body.message).to.contain('[request query.params]: expected JSON string'); }); it('"params" requires a "rollup_index" property', async () => { params = { pattern: 'foo', params: JSON.stringify({}) }; - uri = `${BASE_URI}?${querystring.stringify(params)}`; + uri = `${BASE_URI}?${stringify(params, { sort: false })}`; ({ body } = await supertest.get(uri).expect(400)); expect(body.message).to.contain('[request query.params]: "rollup_index" is required'); }); @@ -65,7 +64,7 @@ export default function({ getService }) { pattern: 'foo', params: JSON.stringify({ rollup_index: 'my_index', someProp: 'bar' }), }; - uri = `${BASE_URI}?${querystring.stringify(params)}`; + uri = `${BASE_URI}?${stringify(params, { sort: false })}`; ({ body } = await supertest.get(uri).expect(400)); expect(body.message).to.contain('[request query.params]: someProp is not allowed'); }); @@ -76,7 +75,7 @@ export default function({ getService }) { params: JSON.stringify({ rollup_index: 'bar' }), meta_fields: 'stringValue', }; - uri = `${BASE_URI}?${querystring.stringify(params)}`; + uri = `${BASE_URI}?${stringify(params, { sort: false })}`; ({ body } = await supertest.get(uri).expect(400)); expect(body.message).to.contain( '[request query.meta_fields]: could not parse array value from [stringValue]' @@ -84,10 +83,13 @@ export default function({ getService }) { }); it('should return 404 the rollup index to query does not exist', async () => { - uri = `${BASE_URI}?${querystring.stringify({ - pattern: 'foo', - params: JSON.stringify({ rollup_index: 'bar' }), - })}`; + uri = `${BASE_URI}?${stringify( + { + pattern: 'foo', + params: JSON.stringify({ rollup_index: 'bar' }), + }, + { sort: false } + )}`; ({ body } = await supertest.get(uri).expect(404)); expect(body.message).to.contain('[index_not_found_exception] no such index [bar]'); }); @@ -105,7 +107,7 @@ export default function({ getService }) { pattern: indexName, params: JSON.stringify({ rollup_index: rollupIndex }), }; - const uri = `${BASE_URI}?${querystring.stringify(params)}`; + const uri = `${BASE_URI}?${stringify(params, { sort: false })}`; const { body } = await supertest.get(uri).expect(200); // Verify that the fields for wildcard correspond to our declared mappings diff --git a/x-pack/test/functional/apps/infra/link_to.ts b/x-pack/test/functional/apps/infra/link_to.ts index 639b65ec5eca87..738dc7efd8fd91 100644 --- a/x-pack/test/functional/apps/infra/link_to.ts +++ b/x-pack/test/functional/apps/infra/link_to.ts @@ -22,7 +22,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { state: undefined, }; const expectedSearchString = - "logFilter=(expression:'trace.id:433b4651687e18be2c6c8e3b11f53d09',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1565707203194),streamLive:!f)&sourceId=default"; + "sourceId=default&logPosition=(position:(tiebreaker:0,time:1565707203194),streamLive:!f)&logFilter=(expression:'trace.id:433b4651687e18be2c6c8e3b11f53d09',kind:kuery)"; const expectedRedirectPath = '/logs/stream?'; await pageObjects.common.navigateToActualUrl( diff --git a/x-pack/test/saml_api_integration/apis/security/saml_login.ts b/x-pack/test/saml_api_integration/apis/security/saml_login.ts index 6ede8aadeb5a7b..610850cfb00bbb 100644 --- a/x-pack/test/saml_api_integration/apis/security/saml_login.ts +++ b/x-pack/test/saml_api_integration/apis/security/saml_login.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import querystring from 'querystring'; +import { stringify } from 'query-string'; import url from 'url'; import { delay } from 'bluebird'; import expect from '@kbn/expect'; @@ -443,7 +443,7 @@ export default function({ getService }: FtrProviderContext) { it('should invalidate access token on IdP initiated logout', async () => { const logoutRequest = await createLogoutRequest({ sessionIndex: idpSessionIndex }); const logoutResponse = await supertest - .get(`/api/security/logout?${querystring.stringify(logoutRequest)}`) + .get(`/api/security/logout?${stringify(logoutRequest, { sort: false })}`) .set('Cookie', sessionCookie.cookieString()) .expect(302); @@ -479,7 +479,7 @@ export default function({ getService }: FtrProviderContext) { it('should invalidate access token on IdP initiated logout even if there is no Kibana session', async () => { const logoutRequest = await createLogoutRequest({ sessionIndex: idpSessionIndex }); const logoutResponse = await supertest - .get(`/api/security/logout?${querystring.stringify(logoutRequest)}`) + .get(`/api/security/logout?${stringify(logoutRequest, { sort: false })}`) .expect(302); expect(logoutResponse.headers['set-cookie']).to.be(undefined); diff --git a/x-pack/test/saml_api_integration/fixtures/saml_tools.ts b/x-pack/test/saml_api_integration/fixtures/saml_tools.ts index b7b94b8eeb17ad..bbe0df7ff3a2c9 100644 --- a/x-pack/test/saml_api_integration/fixtures/saml_tools.ts +++ b/x-pack/test/saml_api_integration/fixtures/saml_tools.ts @@ -6,7 +6,7 @@ import crypto from 'crypto'; import fs from 'fs'; -import querystring from 'querystring'; +import { stringify } from 'query-string'; import url from 'url'; import zlib from 'zlib'; import { promisify } from 'util'; @@ -140,7 +140,7 @@ export async function getLogoutRequest({ }; const signer = crypto.createSign('RSA-SHA256'); - signer.update(querystring.stringify(queryStringParameters)); + signer.update(stringify(queryStringParameters, { sort: false })); queryStringParameters.Signature = signer.sign(signingKey.toString(), 'base64'); return queryStringParameters; diff --git a/yarn.lock b/yarn.lock index 491e8ab8cf95d5..be4b185b7b77f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -23769,6 +23769,15 @@ qs@~6.4.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" integrity sha1-E+JtKK1rD/qpExLNO/cI7TUecjM= +query-string@6.10.1: + version "6.10.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.10.1.tgz#30b3505f6fca741d5ae541964d1b3ae9dc2a0de8" + integrity sha512-SHTUV6gDlgMXg/AQUuLpTiBtW/etZ9JT6k6RCtCyqADquApLX0Aq5oK/s5UeTUAWBG50IExjIr587GqfXRfM4A== + dependencies: + decode-uri-component "^0.2.0" + split-on-first "^1.0.0" + strict-uri-encode "^2.0.0" + query-string@^4.1.0, query-string@^4.2.2: version "4.3.4" resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" @@ -23786,11 +23795,6 @@ query-string@^5.0.1: object-assign "^4.1.0" strict-uri-encode "^1.0.0" -querystring-browser@1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/querystring-browser/-/querystring-browser-1.0.4.tgz#f2e35881840a819bc7b1bf597faf0979e6622dc6" - integrity sha1-8uNYgYQKgZvHsb9Zf68JeeZiLcY= - querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -27387,6 +27391,11 @@ spdy@^4.0.1: select-hose "^2.0.0" spdy-transport "^3.0.0" +split-on-first@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" + integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== + split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" @@ -27700,6 +27709,11 @@ strict-uri-encode@^1.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= +strict-uri-encode@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" + integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY= + string-length@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/string-length/-/string-length-1.0.1.tgz#56970fb1c38558e9e70b728bf3de269ac45adfac"