mirror of
https://github.com/tenrok/axios.git
synced 2026-06-17 19:21:29 +03:00
9d92bcd326
* chore: remove un-needed ghsa in the comments of files * fix: auth header * fix: escape regex chars in cookies.read * fix: read-side merge and descriptors * fix: enable redaction in the .toJson for errors * fix: general IPv4-mapped IPv6 normalization in NO_PROXY * fix: added regression tests for scenarios already covered * chore: remove un-needed comments * fix: harden proxy host detection and error redaction * fix: make form-data header change opt-in * fix: apply suggestions form github review * fix: cubic review * fix: widen the regexs for matches Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> * fix: smaller issue found by cubic * fix: address prototype chain * fix: update as per cubic --------- Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
239 lines
6.2 KiB
JavaScript
239 lines
6.2 KiB
JavaScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import axios from '../../index.js';
|
|
import cookies from '../../lib/helpers/cookies.js';
|
|
|
|
class MockXMLHttpRequest {
|
|
constructor() {
|
|
this.requestHeaders = {};
|
|
this.readyState = 0;
|
|
this.status = 200;
|
|
this.statusText = 'OK';
|
|
this.responseText = '';
|
|
this.timeout = 0;
|
|
this.onreadystatechange = null;
|
|
this.onloadend = null;
|
|
this.onabort = null;
|
|
this.onerror = null;
|
|
this.ontimeout = null;
|
|
this.upload = {
|
|
addEventListener() {},
|
|
};
|
|
}
|
|
|
|
open(method, url, async = true) {
|
|
this.method = method;
|
|
this.url = url;
|
|
this.async = async;
|
|
}
|
|
|
|
setRequestHeader(key, value) {
|
|
this.requestHeaders[key] = value;
|
|
}
|
|
|
|
addEventListener() {}
|
|
|
|
getAllResponseHeaders() {
|
|
return '';
|
|
}
|
|
|
|
send() {
|
|
requests.push(this);
|
|
this.readyState = 4;
|
|
|
|
queueMicrotask(() => {
|
|
if (this.onloadend) {
|
|
this.onloadend();
|
|
} else if (this.onreadystatechange) {
|
|
this.onreadystatechange();
|
|
}
|
|
});
|
|
}
|
|
|
|
abort() {}
|
|
}
|
|
|
|
let requests = [];
|
|
let OriginalXMLHttpRequest;
|
|
|
|
const setXsrfCookie = (value) => {
|
|
document.cookie = `${axios.defaults.xsrfCookieName}=${value}; path=/`;
|
|
};
|
|
|
|
const clearXsrfCookie = () => {
|
|
document.cookie = `${axios.defaults.xsrfCookieName}=; expires=${new Date(
|
|
Date.now() - 86400000
|
|
).toUTCString()}; path=/`;
|
|
};
|
|
|
|
const sendRequest = async (url, config) => {
|
|
const responsePromise = axios(url, config);
|
|
const request = requests.at(-1);
|
|
|
|
expect(request).toBeDefined();
|
|
await responsePromise;
|
|
|
|
return request;
|
|
};
|
|
|
|
describe('xsrf (vitest browser)', () => {
|
|
beforeEach(() => {
|
|
requests = [];
|
|
OriginalXMLHttpRequest = window.XMLHttpRequest;
|
|
window.XMLHttpRequest = MockXMLHttpRequest;
|
|
});
|
|
|
|
afterEach(() => {
|
|
clearXsrfCookie();
|
|
window.XMLHttpRequest = OriginalXMLHttpRequest;
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it('should not set xsrf header if cookie is null', async () => {
|
|
const request = await sendRequest('/foo');
|
|
|
|
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBeUndefined();
|
|
});
|
|
|
|
it('should set xsrf header if cookie is set', async () => {
|
|
setXsrfCookie('12345');
|
|
|
|
const request = await sendRequest('/foo');
|
|
|
|
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBe('12345');
|
|
});
|
|
|
|
it('should not set xsrf header if xsrfCookieName is null', async () => {
|
|
setXsrfCookie('12345');
|
|
|
|
const request = await sendRequest('/foo', {
|
|
xsrfCookieName: null,
|
|
});
|
|
|
|
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBeUndefined();
|
|
});
|
|
|
|
it('should not read cookies at all if xsrfCookieName is null', async () => {
|
|
const readSpy = vi.spyOn(cookies, 'read');
|
|
|
|
await sendRequest('/foo', {
|
|
xsrfCookieName: null,
|
|
});
|
|
|
|
expect(readSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('should not set xsrf header for cross origin', async () => {
|
|
setXsrfCookie('12345');
|
|
|
|
const request = await sendRequest('http://example.com/');
|
|
|
|
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBeUndefined();
|
|
});
|
|
|
|
it('should not set xsrf header for cross origin when using withCredentials', async () => {
|
|
setXsrfCookie('12345');
|
|
|
|
const request = await sendRequest('http://example.com/', {
|
|
withCredentials: true,
|
|
});
|
|
|
|
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBeUndefined();
|
|
});
|
|
|
|
describe('withXSRFToken option', () => {
|
|
it('should set xsrf header for cross origin when withXSRFToken = true', async () => {
|
|
const token = '12345';
|
|
|
|
setXsrfCookie(token);
|
|
|
|
const request = await sendRequest('http://example.com/', {
|
|
withXSRFToken: true,
|
|
});
|
|
|
|
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBe(token);
|
|
});
|
|
|
|
it('should not set xsrf header for the same origin when withXSRFToken = false', async () => {
|
|
const token = '12345';
|
|
|
|
setXsrfCookie(token);
|
|
|
|
const request = await sendRequest('/foo', {
|
|
withXSRFToken: false,
|
|
});
|
|
|
|
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBeUndefined();
|
|
});
|
|
|
|
it('should support function resolver', async () => {
|
|
const token = '12345';
|
|
|
|
setXsrfCookie(token);
|
|
|
|
const request = await sendRequest('/foo', {
|
|
withXSRFToken: (config) => config.userFlag === 'yes',
|
|
userFlag: 'yes',
|
|
});
|
|
|
|
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBe(token);
|
|
});
|
|
});
|
|
|
|
// Non-boolean truthy withXSRFToken must not short-circuit
|
|
// the same-origin check and leak the XSRF token cross-origin.
|
|
describe('non-boolean withXSRFToken', () => {
|
|
afterEach(() => {
|
|
delete Object.prototype.withXSRFToken;
|
|
});
|
|
|
|
const leakCases = [
|
|
['number 1', 1],
|
|
['string "false"', 'false'],
|
|
['empty object', {}],
|
|
['empty array', []],
|
|
];
|
|
|
|
leakCases.forEach(([label, value]) => {
|
|
it(`should not send xsrf header cross-origin when withXSRFToken = ${label}`, async () => {
|
|
setXsrfCookie('12345');
|
|
|
|
const request = await sendRequest('http://example.com/', {
|
|
withXSRFToken: value,
|
|
});
|
|
|
|
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
it('should not send xsrf header cross-origin when Object.prototype.withXSRFToken is polluted', async () => {
|
|
Object.prototype.withXSRFToken = 1;
|
|
setXsrfCookie('12345');
|
|
|
|
const request = await sendRequest('http://example.com/');
|
|
|
|
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBeUndefined();
|
|
});
|
|
|
|
it('should still send xsrf header cross-origin when withXSRFToken === true (strict)', async () => {
|
|
const token = '12345';
|
|
setXsrfCookie(token);
|
|
|
|
const request = await sendRequest('http://example.com/', {
|
|
withXSRFToken: true,
|
|
});
|
|
|
|
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBe(token);
|
|
});
|
|
|
|
it('should still send xsrf header same-origin when withXSRFToken is undefined', async () => {
|
|
const token = '12345';
|
|
setXsrfCookie(token);
|
|
|
|
const request = await sendRequest('/foo');
|
|
|
|
expect(request.requestHeaders[axios.defaults.xsrfHeaderName]).toBe(token);
|
|
});
|
|
});
|
|
});
|