diff --git a/packages/insomnia/src/common/har.ts b/packages/insomnia/src/common/har.ts index 5324a7fec9..1e7bc0292e 100644 --- a/packages/insomnia/src/common/har.ts +++ b/packages/insomnia/src/common/har.ts @@ -1,7 +1,6 @@ import type * as Har from 'har-format'; -import { Cookie as ToughCookie } from 'tough-cookie'; -import type { BaseModel, Environment, Request, RequestGroup, Response, Workspace } from '~/insomnia-data'; +import type { BaseModel, Cookie, Environment, Request, RequestGroup, Response, Workspace } from '~/insomnia-data'; import { models, services } from '~/insomnia-data'; import { applyRequestHooks } from '~/network/network-adapter'; @@ -10,7 +9,6 @@ import type { RenderedRequest } from '../templating/types'; import { parseGraphQLReqeustBody } from '../utils/graph-ql'; import { smartEncodeUrl } from '../utils/url/querystring'; import { getAppVersion } from './constants'; -import { jarFromCookies } from './cookies'; import { database } from './database'; import { filterHeaders, getSetCookieHeaders, hasAuthHeader } from './misc'; import { getRenderedRequestAndContext } from './render'; @@ -207,7 +205,7 @@ export async function exportHarResponse(response?: Response) { status: response.statusCode, statusText: response.statusMessage, httpVersion: 'HTTP/1.1', - cookies: getResponseCookies(response), + cookies: await getResponseCookies(response), headers: getResponseHeaders(response), content: await getResponseContent(response), redirectURL: '', @@ -299,7 +297,7 @@ export async function exportHarWithRenderedRequest(renderedRequest: RenderedRequ method: renderedRequest.method, url, httpVersion: 'HTTP/1.1', - cookies: getRequestCookies(renderedRequest), + cookies: await getRequestCookies(renderedRequest), headers: getRequestHeaders(renderedRequest), queryString: getRequestQueryString(renderedRequest), postData: await getRequestPostData(renderedRequest), @@ -309,36 +307,56 @@ export async function exportHarWithRenderedRequest(renderedRequest: RenderedRequ return harRequest; } -function getRequestCookies(renderedRequest: RenderedRequest) { - // filter out invalid cookies to avoid getCookiesSync complaining +async function getRequestCookies(renderedRequest: RenderedRequest): Promise { + if (!renderedRequest.url) { + return []; + } + if (typeof window !== 'undefined' && window.main?.cookies) { + const domainCookies = await window.main.cookies.getCookiesForUrl({ + cookies: renderedRequest.cookieJar.cookies, + url: renderedRequest.url, + }); + return domainCookies.map(mapCookieToHar); + } + // Fallback for non-renderer contexts (tests, plugin window) + const { jarFromCookies } = await import('./cookies'); const jar = jarFromCookies(renderedRequest.cookieJar.cookies); - const domainCookies = renderedRequest.url ? jar.getCookiesSync(renderedRequest.url) : []; - const harCookies: Har.Cookie[] = domainCookies.map(mapCookie); - return harCookies; + const domainCookies = jar.getCookiesSync(renderedRequest.url); + return domainCookies.map(c => mapCookieToHar(c.toJSON() as Cookie)); } -export function getResponseCookiesFromHeaders(headers: Har.Cookie[]) { - return getSetCookieHeaders(headers).reduce((accumulator, harCookie) => { - let cookie: null | undefined | ToughCookie = null; - +export async function getResponseCookiesFromHeaders(headers: Har.Cookie[]): Promise { + const setCookieHeaders = getSetCookieHeaders(headers); + if (typeof window !== 'undefined' && window.main?.cookies) { + const results: Har.Cookie[] = []; + for (const harCookie of setCookieHeaders) { + const cookie = await window.main.cookies.parse(harCookie.value || ''); + if (cookie) { + results.push(mapCookieToHar(cookie)); + } + } + return results; + } + // Fallback for non-renderer contexts (tests, plugin window) + const { Cookie: ToughCookie } = await import('tough-cookie'); + return setCookieHeaders.reduce((accumulator, harCookie) => { + let cookie = null; try { cookie = ToughCookie.parse(harCookie.value || '', { loose: true }); } catch {} - - if (cookie === null || cookie === undefined) { + if (!cookie) { return accumulator; } - - return [...accumulator, mapCookie(cookie)]; + return [...accumulator, mapCookieToHar(cookie.toJSON() as Cookie)]; }, [] as Har.Cookie[]); } -function getResponseCookies(response: Response) { +async function getResponseCookies(response: Response): Promise { const headers = response.headers.filter(Boolean); return getResponseCookiesFromHeaders(headers); } -function mapCookie(cookie: ToughCookie) { +function mapCookieToHar(cookie: Cookie): Har.Cookie { const harCookie: Har.Cookie = { name: cookie.key, value: cookie.value, diff --git a/packages/insomnia/src/entry.preload.ts b/packages/insomnia/src/entry.preload.ts index 5e1e474ce9..6f03ccefae 100644 --- a/packages/insomnia/src/entry.preload.ts +++ b/packages/insomnia/src/entry.preload.ts @@ -132,6 +132,7 @@ const cookies: CookiesBridgeAPI = { fromJSON: cookie => invokeWithNormalizedError('cookies.fromJSON', cookie), parse: cookie => invokeWithNormalizedError('cookies.parse', cookie), toString: cookie => invokeWithNormalizedError('cookies.toString', cookie), + getCookiesForUrl: args => invokeWithNormalizedError('cookies.getCookiesForUrl', args), }; const secretStorage: secretStorageBridgeAPI = { diff --git a/packages/insomnia/src/main/ipc/cookies.ts b/packages/insomnia/src/main/ipc/cookies.ts index 94a2067988..11eb4f7ca9 100644 --- a/packages/insomnia/src/main/ipc/cookies.ts +++ b/packages/insomnia/src/main/ipc/cookies.ts @@ -1,4 +1,4 @@ -import { Cookie as ToughCookie } from 'tough-cookie'; +import { Cookie as ToughCookie, CookieJar } from 'tough-cookie'; import type { Cookie } from '~/insomnia-data'; @@ -26,10 +26,26 @@ const cookieToString = (cookie: CookieInput) => { return value; }; +const getCookiesForUrl = (cookies: Cookie[], url: string): Cookie[] => { + try { + const sanitized = cookies.map(c => ({ + ...c, + expires: c.expires === null || c.expires === undefined ? 'Infinity' : c.expires, + })); + const jar = CookieJar.fromJSON(JSON.stringify({ cookies: sanitized })); + jar.rejectPublicSuffixes = false; + jar.looseMode = true; + return jar.getCookiesSync(url).map(c => c.toJSON() as Cookie); + } catch { + return []; + } +}; + export interface CookiesBridgeAPI { fromJSON: (cookie: CookieInput) => Promise; parse: (cookie: string) => Promise; toString: (cookie: CookieInput) => Promise; + getCookiesForUrl: (args: { cookies: Cookie[]; url: string }) => Promise; } export function registerCookieHandlers() { @@ -42,4 +58,7 @@ export function registerCookieHandlers() { ipcMainHandle('cookies.toString', (_, cookie: CookieInput) => { return cookieToString(cookie); }); + ipcMainHandle('cookies.getCookiesForUrl', (_, { cookies, url }: { cookies: Cookie[]; url: string }) => { + return getCookiesForUrl(cookies, url); + }); } diff --git a/packages/insomnia/src/main/ipc/electron.ts b/packages/insomnia/src/main/ipc/electron.ts index c841d2edfa..c5cfbf3d76 100644 --- a/packages/insomnia/src/main/ipc/electron.ts +++ b/packages/insomnia/src/main/ipc/electron.ts @@ -27,6 +27,7 @@ export type HandleChannels = | 'curl.open' | 'curl.readyState' | 'cookies.fromJSON' + | 'cookies.getCookiesForUrl' | 'cookies.parse' | 'cookies.toString' | 'createPlugin' diff --git a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mock-server.mock-route.$mockRouteId.tsx b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mock-server.mock-route.$mockRouteId.tsx index 7f86d90199..40a97fb048 100644 --- a/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mock-server.mock-route.$mockRouteId.tsx +++ b/packages/insomnia/src/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mock-server.mock-route.$mockRouteId.tsx @@ -100,22 +100,25 @@ export const mockRouteToHar = ({ body: string; }): Promise => { const validHeaders = headersArray.filter(({ name }) => !!name); - return import('~/common/har').then(({ getResponseCookiesFromHeaders }) => ({ - status: +statusCode, - statusText: statusText || RESPONSE_CODE_REASONS[+statusCode] || '', - httpVersion: 'HTTP/1.1', - headers: validHeaders, - cookies: getResponseCookiesFromHeaders(validHeaders), - content: { - size: Buffer.byteLength(body), - mimeType, - text: body, - compression: 0, - }, - headersSize: -1, - bodySize: -1, - redirectURL: '', - })); + return (async () => { + const { getResponseCookiesFromHeaders } = await import('~/common/har'); + return { + status: +statusCode, + statusText: statusText || RESPONSE_CODE_REASONS[+statusCode] || '', + httpVersion: 'HTTP/1.1', + headers: validHeaders, + cookies: await getResponseCookiesFromHeaders(validHeaders), + content: { + size: Buffer.byteLength(body), + mimeType, + text: body, + compression: 0, + }, + headersSize: -1, + bodySize: -1, + redirectURL: '', + }; + })(); }; export const useMockRoutePatcher = () => {