From 39c304d51ab990f2aeeb460a010f91f8e9964e02 Mon Sep 17 00:00:00 2001 From: George He Date: Sun, 7 Apr 2024 16:40:28 +0800 Subject: [PATCH 1/7] fix(pre-request script): avoid encoding tags in parsing request urls --- .../objects/__tests__/certificates.test.ts | 6 - .../src/sdk/objects/__tests__/request.test.ts | 5 - .../sdk/objects/__tests__/response.test.ts | 5 - .../src/sdk/objects/__tests__/urls.test.ts | 91 ++++++++-- packages/insomnia/src/sdk/objects/urls.ts | 155 ++++++++++++------ 5 files changed, 183 insertions(+), 79 deletions(-) diff --git a/packages/insomnia/src/sdk/objects/__tests__/certificates.test.ts b/packages/insomnia/src/sdk/objects/__tests__/certificates.test.ts index 5232f6b4ba0..067db5b4751 100644 --- a/packages/insomnia/src/sdk/objects/__tests__/certificates.test.ts +++ b/packages/insomnia/src/sdk/objects/__tests__/certificates.test.ts @@ -1,15 +1,9 @@ -import url from 'node:url'; - import { describe, expect, it } from '@jest/globals'; import { Certificate } from '../certificates'; -import { setUrlParser } from '../urls'; describe('test Certificate object', () => { it('test methods', () => { - // make URL work in Node.js - setUrlParser(url.URL); - const cert = new Certificate({ name: 'Certificate for example.com', matches: ['https://example.com'], diff --git a/packages/insomnia/src/sdk/objects/__tests__/request.test.ts b/packages/insomnia/src/sdk/objects/__tests__/request.test.ts index 9c8e6abb3d3..413f1f726aa 100644 --- a/packages/insomnia/src/sdk/objects/__tests__/request.test.ts +++ b/packages/insomnia/src/sdk/objects/__tests__/request.test.ts @@ -1,13 +1,8 @@ -import url from 'node:url'; - import { describe, expect, it } from '@jest/globals'; import { Request, RequestBody } from '../request'; -import { setUrlParser } from '../urls'; describe('test request and response objects', () => { - setUrlParser(url.URL); - it('test RequestBody methods', () => { const reqBody = new RequestBody({ mode: 'urlencoded', diff --git a/packages/insomnia/src/sdk/objects/__tests__/response.test.ts b/packages/insomnia/src/sdk/objects/__tests__/response.test.ts index 5a6e7153d04..301954a0bff 100644 --- a/packages/insomnia/src/sdk/objects/__tests__/response.test.ts +++ b/packages/insomnia/src/sdk/objects/__tests__/response.test.ts @@ -1,14 +1,9 @@ -import url from 'node:url'; - import { describe, expect, it } from '@jest/globals'; import { Request } from '../request'; import { Response } from '../response'; -import { setUrlParser } from '../urls'; describe('test request and response objects', () => { - setUrlParser(url.URL); - it('test Response methods', () => { const req = new Request({ url: 'https://hostname.com/path', diff --git a/packages/insomnia/src/sdk/objects/__tests__/urls.test.ts b/packages/insomnia/src/sdk/objects/__tests__/urls.test.ts index d86ccd15d21..1c7a925dba2 100644 --- a/packages/insomnia/src/sdk/objects/__tests__/urls.test.ts +++ b/packages/insomnia/src/sdk/objects/__tests__/urls.test.ts @@ -1,13 +1,9 @@ -import url from 'node:url'; - import { describe, expect, it } from '@jest/globals'; -import { QueryParam, setUrlParser, Url, UrlMatchPattern } from '../urls'; +import { QueryParam, Url, UrlMatchPattern } from '../urls'; import { Variable } from '../variables'; describe('test Url object', () => { - setUrlParser(url.URL); - it('test QueryParam', () => { const queryParam = new QueryParam({ key: 'uname', @@ -56,32 +52,33 @@ describe('test Url object', () => { ], }); - expect(url.getHost()).toEqual('hostvalue.com'); + expect(url.getHost()).toEqual('hostValue.com'); expect(url.getPath()).toEqual('/pathLevel1/pathLevel2'); + expect(url.getQueryString()).toEqual('key1=value1&key2=value2'); expect(url.getPathWithQuery()).toEqual('/pathLevel1/pathLevel2?key1=value1&key2=value2'); - expect(url.getRemote(true)).toEqual('hostvalue.com:777'); - expect(url.getRemote(false)).toEqual('hostvalue.com:777'); // TODO: add more cases + expect(url.getRemote(true)).toEqual('hostValue.com:777'); + expect(url.getRemote(false)).toEqual('hostValue.com:777'); // TODO: add more cases url.removeQueryParams([ new QueryParam({ key: 'key1', value: 'value1' }), ]); expect(url.getQueryString()).toEqual('key2=value2'); - expect(url.toString()).toEqual('https://usernameValue:passwordValue@hostvalue.com:777/pathLevel1/pathLevel2?key2=value2#hashValue'); + expect(url.toString()).toEqual('https://usernameValue:passwordValue@hostValue.com:777/pathLevel1/pathLevel2?key2=value2#hashValue'); - const url2 = new Url('https://usernameValue:passwordValue@hostvalue.com:777/pathLevel1/pathLevel2?key1=value1&key2=value2#hashValue'); - expect(url2.getHost()).toEqual('hostvalue.com'); + const url2 = new Url('https://usernameValue:passwordValue@hostValue.com:777/pathLevel1/pathLevel2?key1=value1&key2=value2#hashValue'); + expect(url2.getHost()).toEqual('hostValue.com'); expect(url2.getPath()).toEqual('/pathLevel1/pathLevel2'); expect(url2.getQueryString()).toEqual('key1=value1&key2=value2'); expect(url2.getPathWithQuery()).toEqual('/pathLevel1/pathLevel2?key1=value1&key2=value2'); - expect(url2.getRemote(true)).toEqual('hostvalue.com:777'); - expect(url2.getRemote(false)).toEqual('hostvalue.com:777'); // TODO: add more cases + expect(url2.getRemote(true)).toEqual('hostValue.com:777'); + expect(url2.getRemote(false)).toEqual('hostValue.com:777'); // TODO: add more cases url2.removeQueryParams([ new QueryParam({ key: 'key1', value: 'value1' }), ]); expect(url2.getQueryString()).toEqual('key2=value2'); - expect(url2.toString()).toEqual('https://usernameValue:passwordValue@hostvalue.com:777/pathLevel1/pathLevel2?key2=value2#hashValue'); + expect(url2.toString()).toEqual('https://usernameValue:passwordValue@hostValue.com:777/pathLevel1/pathLevel2?key2=value2#hashValue'); }); it('test Url static methods', () => { @@ -92,6 +89,72 @@ describe('test Url object', () => { expect(urlObj.toString()).toEqual(urlStr); }); + + const urlParsingTests = [ + { + testName: 'interal url', + url: 'inso/', + }, + { + testName: 'interal url with protocol', + url: 'http://inso/', + }, + { + testName: 'interal url with auth', + url: 'http://name:pwd@inso/', + }, + { + testName: 'interal url with auth without protocol', + url: 'name:pwd@inso/', + }, + { + testName: 'ip address', + url: 'http://127.0.0.1/', + }, + { + testName: 'localhost', + url: 'https://localhost/', + }, + { + testName: 'url with query params', + url: 'localhost/?k=v', + }, + { + testName: 'url with hash', + url: 'localhost/#myHash', + }, + { + testName: 'url with query params and hash', + url: 'localhost/?k=v#myHash', + }, + { + testName: 'url with query params and hash', + url: 'localhost/?k={{ myValue }}', + }, + { + testName: 'url with query params and hash', + url: 'localhost/#My{{ hashValue }}', + }, + { + testName: 'url with path params', + url: 'inso.com/:path1/:path', + }, + { + testName: 'url with tags and path params', + url: '{{ _.baseUrl }}/:path1/:path', + }, + { + testName: 'hybrid of path params and tags', + url: '{{ baseUrl }}/:path_{{ _.pathSuffix }}', + }, + ]; + + urlParsingTests.forEach(testCase => { + it(`parsing url: ${testCase.testName}`, () => { + const urlObj = new Url(testCase.url); + expect(urlObj.toString()).toEqual(testCase.url); + }); + }); }); describe('test Url Match Pattern', () => { diff --git a/packages/insomnia/src/sdk/objects/urls.ts b/packages/insomnia/src/sdk/objects/urls.ts index e0e8a4e5ec8..cc896f98207 100644 --- a/packages/insomnia/src/sdk/objects/urls.ts +++ b/packages/insomnia/src/sdk/objects/urls.ts @@ -1,12 +1,7 @@ import { Property, PropertyBase, PropertyList } from './properties'; import { Variable, VariableList } from './variables'; -// TODO: make it also work with node.js -let UrlParser = URL; let UrlSearchParams = URLSearchParams; -export function setUrlParser(provider: any) { - UrlParser = provider; -} export function setUrlSearchParams(provider: any) { UrlSearchParams = provider; } @@ -143,12 +138,6 @@ export class Url extends PropertyBase { } private setFields(def: UrlOptions | string) { - if (typeof def === 'string') { - def = def.includes('://') ? def : 'http://' + def; - } else if (!def.protocol || def.protocol === '') { - def.protocol = 'http://'; - } - const urlObj = typeof def === 'string' ? Url.parse(def) : def; if (urlObj) { @@ -189,30 +178,98 @@ export class Url extends PropertyBase { } static parse(urlStr: string): UrlOptions | undefined { - // TODO: enable validation - // if (!UrlParser.canParse(urlStr)) { - // console.error(`invalid URL string ${urlStr}`); - // return undefined; - // } - - const url = new UrlParser(urlStr); - const query = Array.from(url.searchParams.entries()) - .map(kv => { - const kvArray = kv as [string, string]; - return { key: kvArray[0], value: kvArray[1] }; - }); + // the URL API (for web) is not leveraged here because the input string could contain tags for interpolation + // which will be encoded, then it would introduce confusion for users in manipulation + + const endOfProto = urlStr.indexOf('://'); + const protocol = endOfProto >= 0 ? urlStr.slice(0, endOfProto + 1) : ''; + + const potentialStartOfAuth = protocol === '' ? 0 : endOfProto + 3; + const endOfAuth = urlStr.indexOf('@', potentialStartOfAuth); + let auth = undefined; + if (endOfAuth >= 0 && potentialStartOfAuth < endOfAuth) { // e.g., '@insomnia.com' will be ignored + const authStr = endOfAuth >= 0 ? urlStr.slice(potentialStartOfAuth, endOfAuth) : ''; + const authParts = authStr?.split(':'); + if (authParts.length < 2) { + throw Error('new Url(): failed to parse auth in url ${urlStr}'); + } + auth = { username: authParts[0], password: authParts[1] }; + } + + const startOfHash = urlStr.indexOf('#'); + const hash = startOfHash >= 0 ? urlStr.slice(startOfHash + 1) : undefined; + + const endOfQuery = startOfHash >= 0 ? startOfHash : urlStr.length; + const startOfQuery = urlStr.lastIndexOf('?', endOfQuery); + const query = new Array<{ key: string; value: string }>(); + if (startOfQuery >= 0) { + const queryStr = urlStr.slice(startOfQuery + 1, endOfQuery); + query.push( + ...queryStr + .split('&') + .map(pairStr => { + const queryParts = pairStr.split('='); + const key = queryParts[0]; + const value = queryParts.length > 1 ? queryParts[1] : ''; + return { key, value }; + }) + ); + } + + const startOfPathname = urlStr.indexOf('/', endOfProto >= 0 ? endOfProto + 3 : 0); + const path = new Array(); + if (startOfPathname >= 0) { + let endOfPathname = urlStr.length; + if (startOfQuery >= 0) { + endOfPathname = startOfQuery; + } else if (startOfHash >= 0) { + endOfPathname = startOfHash; + } + const pathname = urlStr.slice(startOfPathname, endOfPathname); + path.push( + ...pathname.split('/'), + ); + } + + let potentialStartOfHostname = 0; + if (endOfAuth >= 0) { + potentialStartOfHostname = endOfAuth + 1; + } else if (endOfProto >= 0) { + potentialStartOfHostname = endOfProto + 3; + } + let potentialEndOfHostname = urlStr.length; + if (startOfPathname >= 0) { + potentialEndOfHostname = startOfPathname; + } else if (startOfQuery >= 0) { + potentialEndOfHostname = startOfQuery; + } else if (startOfHash >= 0) { + potentialEndOfHostname = startOfHash; + } + const host = new Array(); + let port = undefined; + if (potentialStartOfHostname < potentialEndOfHostname) { + const hostname = urlStr.slice(potentialStartOfHostname, potentialEndOfHostname); + const hostnameParts = hostname.split(':'); + if (hostnameParts.length === 2) { + port = hostnameParts[1]; + } else if (hostnameParts.length > 2) { + throw Error('new Url(): failed to parse hostname in url ${urlStr}'); + } + + host.push( + ... + hostnameParts[0].split('.'), + ); + } return { - auth: url.username !== '' ? { // TODO: make it compatible with RequestAuth - username: url.username, - password: url.password, - } : undefined, - hash: url.hash, - host: url.hostname.split('/'), - path: url.pathname.split('/'), - port: url.port, - protocol: url.protocol, // e.g. https: + auth, + protocol, + host, + port, + path, query, + hash, variables: [], }; } @@ -232,7 +289,7 @@ export class Url extends PropertyBase { } getHost() { - return this.host.join('.').toLowerCase(); + return this.host.join('.'); } getPath(unresolved?: boolean) { @@ -255,7 +312,11 @@ export class Url extends PropertyBase { const params = new UrlSearchParams(); this.query.each(param => params.append(param.key, param.value), {}); - return params.toString(); + const queryParamStrs = this.query.map(pair => { + return pair.value ? `${pair.key}=${pair.value}` : pair.key; + }, {}); + + return queryParamStrs.join('&'); } getRemote(forcePort?: boolean) { @@ -306,20 +367,19 @@ export class Url extends PropertyBase { } toString(forceProtocol?: boolean) { - const protocol = forceProtocol ? - (this.protocol ? this.protocol : 'https:') : - (this.protocol ? this.protocol : ''); + const protocolStr = forceProtocol ? + (this.protocol ? `${this.protocol}//` : 'http://') : + (this.protocol ? `${this.protocol}//` : ''); - const parser = new UrlParser(`${protocol}//` + this.getHost()); - parser.username = this.auth?.username || ''; - parser.password = this.auth?.password || ''; - parser.port = this.port || ''; - parser.pathname = this.getPath(); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - parser.search = this.getQueryString(); - parser.hash = this.hash || ''; + const authStr = this.auth ? `${this.auth.username}:${this.auth.password}@` : ''; + const hostStr = this.getHost(); + const portStr = this.port ? `:${this.port}` : ''; + const pathStr = this.getPath(); + const queryStr = this.getQueryString() ? `?${this.getQueryString()}` : ''; + const hashStr = this.hash ? `#${this.hash}` : ''; - return parser.toString(); + return `${protocolStr}${authStr}${hostStr}${portStr}${pathStr}${queryStr}${hashStr}`; + // return parser.toString(); } update(url: UrlOptions | string) { @@ -561,9 +621,6 @@ export class UrlMatchPatternList extends PropertyList return '_kind' in obj && obj._kind === 'UrlMatchPatternList'; } - // TODO: unsupported yet - // toObject(excludeDisabledopt, nullable, caseSensitiveopt, nullable, multiValueopt, nullable, sanitizeKeysopt) → {Object} - test(urlStr: string) { return this .filter(matchPattern => matchPattern.test(urlStr), {}) From 9a1c1e71f206c0e20d86167f662610ce1078e82c Mon Sep 17 00:00:00 2001 From: George He Date: Mon, 8 Apr 2024 11:29:47 +0800 Subject: [PATCH 2/7] fix: refactor requestBody transforming as functions with tests --- .../src/sdk/objects/__tests__/request.test.ts | 47 ++++++++++- packages/insomnia/src/sdk/objects/insomnia.ts | 26 +------ packages/insomnia/src/sdk/objects/request.ts | 78 ++++++++++++++----- packages/insomnia/src/sdk/objects/urls.ts | 8 +- 4 files changed, 112 insertions(+), 47 deletions(-) diff --git a/packages/insomnia/src/sdk/objects/__tests__/request.test.ts b/packages/insomnia/src/sdk/objects/__tests__/request.test.ts index 413f1f726aa..9eb4d52c9d4 100644 --- a/packages/insomnia/src/sdk/objects/__tests__/request.test.ts +++ b/packages/insomnia/src/sdk/objects/__tests__/request.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from '@jest/globals'; -import { Request, RequestBody } from '../request'; +import { mergeRequestBody, toScriptRequestBody, Request, RequestBody } from '../request'; describe('test request and response objects', () => { it('test RequestBody methods', () => { @@ -67,4 +67,49 @@ describe('test request and response objects', () => { const req2 = req.clone(); expect(req2.toJSON()).toEqual(req.toJSON()); }); + + it('test Request body transforming', () => { + const bodies = [ + { + mimeType: 'text/plain', + text: 'rawContent', + }, + { + mimeType: 'application/octet-stream', + fileName: 'path/to/file', + }, + { + mimeType: 'application/x-www-form-urlencoded', + params: [ + { name: 'k1', value: 'v1' }, + { name: 'k2', value: 'v2' }, + ], + }, + { + mimeType: 'application/json', + text: `{ + query: 'query', + operationName: 'operation', + variables: 'var', + }`, + }, + { + mimeType: 'image/gif', + fileName: '/path/to/image', + }, + { + mimeType: 'multipart/form-data', + params: [ + { name: 'k1', type: 'text', value: 'v1' }, + { name: 'k2', type: 'file', value: '/path/to/image' }, + ], + }, + ]; + + bodies.forEach(body => { + const originalReqBody = body; + const scriptReqBody = new RequestBody(toScriptRequestBody(body)); + expect(mergeRequestBody(scriptReqBody, originalReqBody)).toEqual(originalReqBody); + }); + }); }); diff --git a/packages/insomnia/src/sdk/objects/insomnia.ts b/packages/insomnia/src/sdk/objects/insomnia.ts index d8749200362..8bedd8a9a73 100644 --- a/packages/insomnia/src/sdk/objects/insomnia.ts +++ b/packages/insomnia/src/sdk/objects/insomnia.ts @@ -1,14 +1,14 @@ import { expect } from 'chai'; import { ClientCertificate } from '../../models/client-certificate'; -import { RequestBodyParameter, RequestHeader } from '../../models/request'; +import { RequestHeader } from '../../models/request'; import { Settings } from '../../models/settings'; import { toPreRequestAuth } from './auth'; import { CookieObject } from './cookies'; import { Environment, Variables } from './environments'; import { RequestContext } from './interfaces'; import { unsupportedError } from './properties'; -import { Request as ScriptRequest, RequestBodyOptions, RequestOptions } from './request'; +import { Request as ScriptRequest, RequestOptions, toScriptRequestBody } from './request'; import { Response as ScriptResponse } from './response'; import { sendRequest } from './send-request'; import { test } from './test'; @@ -125,26 +125,6 @@ export function initInsomniaObject( data: iterationData, }); - let reqBodyOpt: RequestBodyOptions = { mode: undefined }; - if (rawObj.request.body.text != null) { - reqBodyOpt = { - mode: 'raw', - raw: rawObj.request.body.text, - }; - } else if (rawObj.request.body.fileName != null && rawObj.request.body.fileName !== '') { - reqBodyOpt = { - mode: 'file', - file: rawObj.request.body.fileName, - }; - } else if (rawObj.request.body.params != null) { - reqBodyOpt = { - mode: 'urlencoded', - urlencoded: rawObj.request.body.params.map( - (param: RequestBodyParameter) => ({ key: param.name, value: param.value }) - ), - }; - } - const certificate = rawObj.clientCertificates != null && rawObj.clientCertificates.length > 0 ? { disabled: false, @@ -203,7 +183,7 @@ export function initInsomniaObject( header: rawObj.request.headers.map( (header: RequestHeader) => ({ key: header.name, value: header.value }) ), - body: reqBodyOpt, + body: toScriptRequestBody(rawObj.request.body), auth: toPreRequestAuth(rawObj.request.authentication), proxy, certificate, diff --git a/packages/insomnia/src/sdk/objects/request.ts b/packages/insomnia/src/sdk/objects/request.ts index 80127824817..481d3364fbf 100644 --- a/packages/insomnia/src/sdk/objects/request.ts +++ b/packages/insomnia/src/sdk/objects/request.ts @@ -1,6 +1,7 @@ import { init as initClientCertificate } from '../../../src/models/client-certificate'; -import { Request as InsomniaRequest, RequestPathParameter } from '../../../src/models/request'; +import { Request as InsomniaRequest, RequestBody as InsomniaRequestBody, RequestPathParameter } from '../..//models/request'; import { ClientCertificate } from '../../models/client-certificate'; +import { RequestBodyParameter } from '../../models/request'; import { Settings } from '../../models/settings'; import { AuthOptions, AuthOptionTypes, fromPreRequestAuth, RequestAuth } from './auth'; import { CertificateOptions } from './certificates'; @@ -20,7 +21,7 @@ export interface RequestBodyOptions { formdata?: { key: string; value: string; type?: string }[]; graphql?: { query: string; operationName: string; variables: object }; raw?: string; - urlencoded?: { key: string; value: string }[]; + urlencoded?: { key: string; value: string; type?: string }[]; options?: object; } @@ -91,7 +92,7 @@ function getClassFields(opts: RequestBodyOptions) { QueryParam, undefined, opts.urlencoded - .map(entry => ({ key: entry.key, value: entry.value })) + .map(entry => ({ key: entry.key, value: entry.value, type: entry.type })) .map(kv => new QueryParam(kv)), ); } @@ -526,13 +527,38 @@ export function mergeClientCertificates( throw Error('Invalid certificate configuration: "cert+key" and "pfx" can not be set at the same time'); } -export function mergeRequests( - originalReq: InsomniaRequest, - updatedReq: Request -): InsomniaRequest { +export function toScriptRequestBody(insomniaReqBody: InsomniaRequestBody): RequestBodyOptions { + let reqBodyOpt: RequestBodyOptions = { mode: undefined }; + + if (insomniaReqBody.text !== undefined) { + reqBodyOpt = { + mode: 'raw', + raw: insomniaReqBody.text, + }; + } else if (insomniaReqBody.fileName !== undefined && insomniaReqBody.fileName !== '') { + reqBodyOpt = { + mode: 'file', + file: insomniaReqBody.fileName, + }; + } else if (insomniaReqBody.params !== undefined) { + reqBodyOpt = { + mode: 'urlencoded', + urlencoded: insomniaReqBody.params.map( + (param: RequestBodyParameter) => ({ key: param.name, value: param.value, type: param.type }) + ), + }; + } + + return reqBodyOpt; +} + +export function mergeRequestBody( + updatedReqBody: RequestBody | undefined, + originalReqBody: InsomniaRequestBody +): InsomniaRequestBody { let mimeType = 'application/octet-stream'; - if (updatedReq.body) { - switch (updatedReq.body.mode) { + if (updatedReqBody) { + switch (updatedReqBody.mode) { case undefined: mimeType = 'application/octet-stream'; break; @@ -554,31 +580,41 @@ export function mergeRequests( mimeType = 'application/json'; break; default: - throw Error(`unknown body mode: ${updatedReq.body.mode}`); + throw Error(`unknown request body mode: ${updatedReqBody.mode}`); } } - if (originalReq.body.mimeType) { - mimeType = originalReq.body.mimeType; + if (originalReqBody.mimeType) { + mimeType = originalReqBody.mimeType; } + return { + mimeType: mimeType, + text: updatedReqBody?.raw, + fileName: updatedReqBody?.file, + params: updatedReqBody?.urlencoded?.map( + (param: { key: string; value: string; type?: string }) => ( + { name: param.key, value: param.value, type: param.type } + ), + {}, + ), + }; +} + +export function mergeRequests( + originalReq: InsomniaRequest, + updatedReq: Request +): InsomniaRequest { const queryParameters = updatedReq.url.query.map( queryParam => ({ name: queryParam.key, value: queryParam.value }) , {}, ); + const updatedReqProperties: Partial = { // url is encoded during parsing phase. Need decode url In order to recognized variables url: decodeURI(typeof updatedReq.url === 'string' ? updatedReq.url : updatedReq.url.toString()), method: updatedReq.method, - body: { - mimeType: mimeType, - text: updatedReq.body?.raw, - fileName: updatedReq.body?.file, - params: updatedReq.body?.urlencoded?.map( - (param: { key: string; value: string }) => ({ name: param.key, value: param.value }), - {}, - ), - }, + body: mergeRequestBody(updatedReq.body, originalReq.body), headers: updatedReq.headers.map( (header: Header) => ({ name: header.key, diff --git a/packages/insomnia/src/sdk/objects/urls.ts b/packages/insomnia/src/sdk/objects/urls.ts index cc896f98207..7701bac724d 100644 --- a/packages/insomnia/src/sdk/objects/urls.ts +++ b/packages/insomnia/src/sdk/objects/urls.ts @@ -16,8 +16,9 @@ export class QueryParam extends Property { key: string; value: string; + type?: string; - constructor(options: { key: string; value: string } | string) { + constructor(options: { key: string; value: string; type?: string } | string) { super(); if (typeof options === 'string') { @@ -25,12 +26,14 @@ export class QueryParam extends Property { const optionsObj = JSON.parse(options); this.key = optionsObj.key; this.value = optionsObj.value; + this.type = optionsObj.type; } catch (e) { throw Error(`invalid QueryParam options ${e}`); } } else if (typeof options === 'object' && ('key' in options) && ('value' in options)) { this.key = options.key; this.value = options.value; + this.type = options.type; } else { throw Error('unknown options for new QueryParam'); } @@ -88,7 +91,7 @@ export class QueryParam extends Property { return params.toString(); } - update(param: string | { key: string; value: string }) { + update(param: string | { key: string; value: string; type?: string }) { if (typeof param === 'string') { const paramObj = QueryParam.parseSingle(param); this.key = typeof paramObj.key === 'string' ? paramObj.key : ''; @@ -96,6 +99,7 @@ export class QueryParam extends Property { } else if ('key' in param && 'value' in param) { this.key = param.key; this.value = param.value; + this.type = param.type; } else { throw Error('the param for update must be: string | { key: string; value: string }'); } From a66e101d8a2ad842a3bffdf379498454b17f9ac3 Mon Sep 17 00:00:00 2001 From: George He Date: Mon, 8 Apr 2024 11:39:36 +0800 Subject: [PATCH 3/7] fix: lint error --- packages/insomnia/src/sdk/objects/__tests__/request.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/insomnia/src/sdk/objects/__tests__/request.test.ts b/packages/insomnia/src/sdk/objects/__tests__/request.test.ts index 9eb4d52c9d4..ac2ae2df52a 100644 --- a/packages/insomnia/src/sdk/objects/__tests__/request.test.ts +++ b/packages/insomnia/src/sdk/objects/__tests__/request.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from '@jest/globals'; -import { mergeRequestBody, toScriptRequestBody, Request, RequestBody } from '../request'; +import { mergeRequestBody, Request, RequestBody, toScriptRequestBody } from '../request'; describe('test request and response objects', () => { it('test RequestBody methods', () => { From c2deac254b64945d57252a6957b15f778341eecd Mon Sep 17 00:00:00 2001 From: George He Date: Mon, 8 Apr 2024 13:46:58 +0800 Subject: [PATCH 4/7] fix: revert url decoding --- packages/insomnia/src/sdk/objects/request.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/insomnia/src/sdk/objects/request.ts b/packages/insomnia/src/sdk/objects/request.ts index 481d3364fbf..681bca9d870 100644 --- a/packages/insomnia/src/sdk/objects/request.ts +++ b/packages/insomnia/src/sdk/objects/request.ts @@ -611,8 +611,7 @@ export function mergeRequests( ); const updatedReqProperties: Partial = { - // url is encoded during parsing phase. Need decode url In order to recognized variables - url: decodeURI(typeof updatedReq.url === 'string' ? updatedReq.url : updatedReq.url.toString()), + url: typeof updatedReq.url === 'string' ? updatedReq.url : updatedReq.url.toString(), method: updatedReq.method, body: mergeRequestBody(updatedReq.body, originalReq.body), headers: updatedReq.headers.map( From a2bc448054c83995bb9f2a03756cb4b19bdd687d Mon Sep 17 00:00:00 2001 From: George He Date: Mon, 8 Apr 2024 14:34:47 +0800 Subject: [PATCH 5/7] fix: query params are duplicated after script execution --- packages/insomnia/src/sdk/objects/request.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/insomnia/src/sdk/objects/request.ts b/packages/insomnia/src/sdk/objects/request.ts index 681bca9d870..a5693b07beb 100644 --- a/packages/insomnia/src/sdk/objects/request.ts +++ b/packages/insomnia/src/sdk/objects/request.ts @@ -609,9 +609,10 @@ export function mergeRequests( , {}, ); + updatedReq.url.query.clear(); const updatedReqProperties: Partial = { - url: typeof updatedReq.url === 'string' ? updatedReq.url : updatedReq.url.toString(), + url: updatedReq.url.toString(), method: updatedReq.method, body: mergeRequestBody(updatedReq.body, originalReq.body), headers: updatedReq.headers.map( From 2934bf3e03801ce3b967ba1091ff8cafd4395e7d Mon Sep 17 00:00:00 2001 From: George He Date: Mon, 8 Apr 2024 14:42:53 +0800 Subject: [PATCH 6/7] fix: query params are duplicated after script execution --- packages/insomnia/src/sdk/objects/request.ts | 9 +-------- packages/insomnia/src/sdk/objects/urls.ts | 2 +- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/packages/insomnia/src/sdk/objects/request.ts b/packages/insomnia/src/sdk/objects/request.ts index a5693b07beb..52bbb380c65 100644 --- a/packages/insomnia/src/sdk/objects/request.ts +++ b/packages/insomnia/src/sdk/objects/request.ts @@ -604,13 +604,6 @@ export function mergeRequests( originalReq: InsomniaRequest, updatedReq: Request ): InsomniaRequest { - const queryParameters = updatedReq.url.query.map( - queryParam => ({ name: queryParam.key, value: queryParam.value }) - , - {}, - ); - updatedReq.url.query.clear(); - const updatedReqProperties: Partial = { url: updatedReq.url.toString(), method: updatedReq.method, @@ -625,7 +618,7 @@ export function mergeRequests( authentication: fromPreRequestAuth(updatedReq.auth), preRequestScript: '', pathParameters: updatedReq.pathParameters, - parameters: queryParameters, + parameters: [], // set empty array as parameters will be part of url field }; return { diff --git a/packages/insomnia/src/sdk/objects/urls.ts b/packages/insomnia/src/sdk/objects/urls.ts index 7701bac724d..66c31df27c2 100644 --- a/packages/insomnia/src/sdk/objects/urls.ts +++ b/packages/insomnia/src/sdk/objects/urls.ts @@ -344,7 +344,7 @@ export class Url extends PropertyBase { this.query = new PropertyList( QueryParam, undefined, - this.query.filter(queryParam => queryParam.key === params, {}) + this.query.filter(queryParam => queryParam.key !== params, {}) ); } else if (params.length > 0) { let toBeRemoved: Set; From da913aefba0b960ce3c7c2e5e71b9a5c2f06e7ce Mon Sep 17 00:00:00 2001 From: George He Date: Mon, 8 Apr 2024 14:56:39 +0800 Subject: [PATCH 7/7] fix: pathParameters property is empty in pre-request script --- packages/insomnia/src/sdk/objects/insomnia.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/insomnia/src/sdk/objects/insomnia.ts b/packages/insomnia/src/sdk/objects/insomnia.ts index 8bedd8a9a73..e215a7a0d97 100644 --- a/packages/insomnia/src/sdk/objects/insomnia.ts +++ b/packages/insomnia/src/sdk/objects/insomnia.ts @@ -187,6 +187,7 @@ export function initInsomniaObject( auth: toPreRequestAuth(rawObj.request.authentication), proxy, certificate, + pathParameters: rawObj.request.pathParameters, }; const request = new ScriptRequest(reqOpt);