[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<string, any> -> ParsedQuery

* Update src/plugins/console/public/application/containers/editor/legacy/console_editor/editor.tsx

Co-Authored-By: Luke Elmers <lukeelmers@gmail.com>

* add more tests for APM

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: Luke Elmers <lukeelmers@gmail.com>
This commit is contained in:
Alexey Antonov 2020-02-12 19:51:03 +03:00 committed by GitHub
parent b7ba72b835
commit deda49e9f0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
70 changed files with 400 additions and 526 deletions

View file

@ -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': {

View file

@ -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.

View file

@ -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",

View file

@ -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'),

View file

@ -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,

View file

@ -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;
}
/**

View file

@ -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() + ' ';

View file

@ -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();
}

View file

@ -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.<string,boolean|Array>}
*/
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('');
};

View file

@ -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'),
},

View file

@ -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';

View file

@ -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<QueryParams>;
};
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 {

View file

@ -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,

View file

@ -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 });
}

View file

@ -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';

View file

@ -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',
});
});
});

View file

@ -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
);
}
});

View file

@ -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,
};

View file

@ -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<any> = {
...oldLocation,
search: newSearch,

View file

@ -26,6 +26,7 @@ export {
Set,
UiComponent,
UiComponentInstance,
url,
JsonValue,
JsonObject,
JsonArray,

View file

@ -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,

View file

@ -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,
});

View file

@ -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' : '+');
}

View file

@ -17,4 +17,4 @@
* under the License.
*/
export { Get, Set, createGetterSetter } from '../common';
export { Get, Set, createGetterSetter, url } from '../common';

View file

@ -17,16 +17,16 @@
* under the License.
*/
import { parse } from 'query-string';
import fn from './quandl';
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';
const parseURL = require('url').parse;
const tlConfig = require('./fixtures/tl_config')();
function parseUrlParams(url) {
return parseQueryString(parseURL(url).query);
return parse(parseURL(url).query, { sort: false });
}
jest.mock('node-fetch', () =>

View file

@ -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({

View file

@ -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<string, any>) {
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 = {

View file

@ -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<URLState extends object> 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<URLState extends object> 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,

View file

@ -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) {

View file

@ -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,

View file

@ -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;

View file

@ -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;
}
/**

View file

@ -43,7 +43,6 @@ export interface CanvasStartDeps {
__LEGACY: {
absoluteToParsedUrl: (url: string, basePath: string) => any;
formatMsg: any;
QueryString: any;
storage: typeof Storage;
trackSubUrlForApp: Chrome['trackSubUrlForApp'];
};

View file

@ -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 });
}

View file

@ -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}`;
};

View file

@ -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,

View file

@ -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 = <State extends {}>(
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 = <State extends {}>(
export const mapStateToRisonAppLocation = <State extends {}>(
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}`,

View file

@ -19,7 +19,7 @@ describe('RedirectToLogs component', () => {
expect(component).toMatchInlineSnapshot(`
<Redirect
to="/logs/stream?logFilter=(expression:'',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1550671089404),streamLive:!f)&sourceId=default"
to="/logs/stream?sourceId=default&logPosition=(position:(tiebreaker:0,time:1550671089404),streamLive:!f)&logFilter=(expression:'',kind:kuery)"
/>
`);
});
@ -33,7 +33,7 @@ describe('RedirectToLogs component', () => {
expect(component).toMatchInlineSnapshot(`
<Redirect
to="/logs/stream?logFilter=(expression:'FILTER_FIELD:FILTER_VALUE',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1550671089404),streamLive:!f)&sourceId=default"
to="/logs/stream?sourceId=default&logPosition=(position:(tiebreaker:0,time:1550671089404),streamLive:!f)&logFilter=(expression:'FILTER_FIELD:FILTER_VALUE',kind:kuery)"
/>
`);
});
@ -45,7 +45,7 @@ describe('RedirectToLogs component', () => {
expect(component).toMatchInlineSnapshot(`
<Redirect
to="/logs/stream?logFilter=(expression:'',kind:kuery)&sourceId=SOME-OTHER-SOURCE"
to="/logs/stream?sourceId=SOME-OTHER-SOURCE&logFilter=(expression:'',kind:kuery)"
/>
`);
});

View file

@ -35,7 +35,7 @@ describe('RedirectToNodeLogs component', () => {
expect(component).toMatchInlineSnapshot(`
<Redirect
to="/logs?logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)&sourceId=default"
to="/logs?sourceId=default&logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)"
/>
`);
});
@ -47,7 +47,7 @@ describe('RedirectToNodeLogs component', () => {
expect(component).toMatchInlineSnapshot(`
<Redirect
to="/logs?logFilter=(expression:'CONTAINER_FIELD:%20CONTAINER_ID',kind:kuery)&sourceId=default"
to="/logs?sourceId=default&logFilter=(expression:'CONTAINER_FIELD:%20CONTAINER_ID',kind:kuery)"
/>
`);
});
@ -59,7 +59,7 @@ describe('RedirectToNodeLogs component', () => {
expect(component).toMatchInlineSnapshot(`
<Redirect
to="/logs?logFilter=(expression:'POD_FIELD:%20POD_ID',kind:kuery)&sourceId=default"
to="/logs?sourceId=default&logFilter=(expression:'POD_FIELD:%20POD_ID',kind:kuery)"
/>
`);
});
@ -73,7 +73,7 @@ describe('RedirectToNodeLogs component', () => {
expect(component).toMatchInlineSnapshot(`
<Redirect
to="/logs?logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1550671089404),streamLive:!f)&sourceId=default"
to="/logs?sourceId=default&logPosition=(position:(tiebreaker:0,time:1550671089404),streamLive:!f)&logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)"
/>
`);
});
@ -89,7 +89,7 @@ describe('RedirectToNodeLogs component', () => {
expect(component).toMatchInlineSnapshot(`
<Redirect
to="/logs?logFilter=(expression:'(HOST_FIELD:%20HOST_NAME)%20and%20(FILTER_FIELD:FILTER_VALUE)',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1550671089404),streamLive:!f)&sourceId=default"
to="/logs?sourceId=default&logPosition=(position:(tiebreaker:0,time:1550671089404),streamLive:!f)&logFilter=(expression:'(HOST_FIELD:%20HOST_NAME)%20and%20(FILTER_FIELD:FILTER_VALUE)',kind:kuery)"
/>
`);
});
@ -103,7 +103,7 @@ describe('RedirectToNodeLogs component', () => {
expect(component).toMatchInlineSnapshot(`
<Redirect
to="/logs?logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)&sourceId=SOME-OTHER-SOURCE"
to="/logs?sourceId=SOME-OTHER-SOURCE&logFilter=(expression:'HOST_FIELD:%20HOST_NAME',kind:kuery)"
/>
`);
});

View file

@ -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: 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<string, any> = parse(queryString, { sort: false });
const queryParam = parsedQueryString[key];
return Array.isArray(queryParam) ? queryParam[0] : queryParam;
};
@ -153,13 +155,17 @@ export const replaceStateKeyInQueryString = <UrlState extends any>(
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 => {

View file

@ -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 = <State>({
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 = <UrlState extends any>(
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 => {

View file

@ -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<PageProps> = ({ location, deps }) => {
const { context } = useResolver('', undefined, deps.config, basicResolvers(deps));
const { _g } = queryString.parse(location.search);
const { _g }: Record<string, any> = parse(location.search, { sort: false });
let globalState: any = null;
try {
globalState = decode(_g);

View file

@ -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<PageProps> = ({ location, deps }) => {
const { index, savedSearchId } = queryString.parse(location.search);
const { index, savedSearchId }: Record<string, any> = parse(location.search, { sort: false });
const { context } = useResolver(index, savedSearchId, deps.config, {
checkBasicLicense,
loadIndexPatterns: () => loadIndexPatterns(deps.indexPatterns),

View file

@ -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<PageProps> = ({ location, deps }) => {
const { index, savedSearchId } = queryString.parse(location.search);
const { index, savedSearchId }: Record<string, any> = parse(location.search, { sort: false });
const { context } = useResolver(index, savedSearchId, deps.config, basicResolvers(deps));
return (
<PageLoader context={context}>

View file

@ -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<PageProps> = ({ location, deps }) => {
const { id, index, savedSearchId } = queryString.parse(location.search);
const { id, index, savedSearchId }: Record<string, any> = 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<PageProps> = ({ location, deps }) => {
};
const CheckViewOrCreateWrapper: FC<PageProps> = ({ location, deps }) => {
const { id: moduleId, index: indexPatternId } = queryString.parse(location.search);
const { id: moduleId, index: indexPatternId }: Record<string, any> = parse(location.search, {
sort: false,
});
// the single resolver checkViewOrCreateJobs redirects only. so will always reject
useResolver(undefined, undefined, deps.config, {
checkViewOrCreateJobs: () => checkViewOrCreateJobs(moduleId, indexPatternId),

View file

@ -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<WizardPageProps> = ({ location, jobType, deps }) => {
const { index, savedSearchId } = queryString.parse(location.search);
const { index, savedSearchId }: Record<string, any> = parse(location.search, { sort: false });
const { context, results } = useResolver(index, savedSearchId, deps.config, {
...basicResolvers(deps),
privileges: checkCreateJobsPrivilege,

View file

@ -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';

View file

@ -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<any> {
const urlState: Dictionary<any> = {};
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<any>;
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<any>, 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) {

View file

@ -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 });
}

View file

@ -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' : '+');
}

View file

@ -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,
};

View file

@ -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}`;
};

View file

@ -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<MlHostConditionalProps>(({
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<QueryStringType>;
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 <Redirect to={`/${SiemPageName.hosts}?${reEncoded}`} />;
}}
/>
@ -47,14 +53,19 @@ export const MlHostConditionalContainer = React.memo<MlHostConditionalProps>(({
params: { hostName },
},
}) => {
const queryStringDecoded: QueryStringType = QueryString.decode(
location.search.substring(1)
);
const queryStringDecoded = parse(location.search.substring(1), {
sort: false,
}) as Required<QueryStringType>;
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 (
<Redirect to={`/${SiemPageName.hosts}/${HostsTableType.anomalies}?${reEncoded}`} />
);
@ -65,12 +76,20 @@ export const MlHostConditionalContainer = React.memo<MlHostConditionalProps>(({
hosts,
queryStringDecoded.query || ''
);
const reEncoded = QueryString.encode(queryStringDecoded);
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
sort: false,
encode: false,
});
return (
<Redirect to={`/${SiemPageName.hosts}/${HostsTableType.anomalies}?${reEncoded}`} />
);
} else {
const reEncoded = QueryString.encode(queryStringDecoded);
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
sort: false,
encode: false,
});
return (
<Redirect
to={`/${SiemPageName.hosts}/${hostName}/${HostsTableType.anomalies}?${reEncoded}`}

View file

@ -4,15 +4,17 @@
* 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, getMultipleEntities, multipleEntities } from './entity_helpers';
import { SiemPageName } from '../../../pages/home/types';
import { url as urlUtils } from '../../../../../../../../src/plugins/kibana_utils/public';
interface QueryStringType {
'?_g': string;
query: string | null;
@ -28,13 +30,19 @@ export const MlNetworkConditionalContainer = React.memo<MlNetworkConditionalProp
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<QueryStringType>;
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 <Redirect to={`/${SiemPageName.network}?${reEncoded}`} />;
}}
/>
@ -46,14 +54,20 @@ export const MlNetworkConditionalContainer = React.memo<MlNetworkConditionalProp
params: { ip },
},
}) => {
const queryStringDecoded: QueryStringType = QueryString.decode(
location.search.substring(1)
);
const queryStringDecoded = parse(location.search.substring(1), {
sort: false,
}) as Required<QueryStringType>;
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 <Redirect to={`/${SiemPageName.network}?${reEncoded}`} />;
} else if (multipleEntities(ip)) {
const ips: string[] = getMultipleEntities(ip);
@ -62,10 +76,16 @@ export const MlNetworkConditionalContainer = React.memo<MlNetworkConditionalProp
ips,
queryStringDecoded.query || ''
);
const reEncoded = QueryString.encode(queryStringDecoded);
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
sort: false,
encode: false,
});
return <Redirect to={`/${SiemPageName.network}?${reEncoded}`} />;
} else {
const reEncoded = QueryString.encode(queryStringDecoded);
const reEncoded = stringify(urlUtils.encodeQuery(queryStringDecoded), {
sort: false,
encode: false,
});
return <Redirect to={`/${SiemPageName.network}/ip/${ip}?${reEncoded}`} />;
}
}}

View file

@ -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 = <T>(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 = <T>(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 = (

View file

@ -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: '',
});
});

View file

@ -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<RouteComponentProps<MatchPara
const [filteredPolicy, setFilteredPolicy] = useState<string | undefined>(undefined);
useEffect(() => {
if (search) {
const parsedParams = parse(search.replace(/^\?/, ''));
const parsedParams = parse(search.replace(/^\?/, ''), { sort: false });
const { repository, policy } = parsedParams;
if (policy && policy !== filteredPolicy) {

View file

@ -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<RouteComponentProps> = ({
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}`);
}
};

View file

@ -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 }
),
});
};

View file

@ -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<UptimeUrlParams>, ignoreEmpty
}
});
}
return `?${qs.stringify(params)}`;
return `?${stringify(params, { sort: false })}`;
};

View file

@ -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 => {

View file

@ -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<GetPingHistogramParams, HistogramResult>
...(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);

View file

@ -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",

View file

@ -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);

View file

@ -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 | {};
}
/**

View file

@ -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

View file

@ -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(

View file

@ -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);

View file

@ -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;

View file

@ -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"