From a28d05ee7337ff09c17eee646108b1aadba61a80 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Wed, 18 Mar 2020 08:59:44 -0700 Subject: [PATCH 1/8] chore: refactor all the net specs to be async with better error handling (#22731) --- spec-main/api-net-spec.ts | 2411 ++++++++++++++++------------------ spec-main/extensions-spec.ts | 22 + 2 files changed, 1120 insertions(+), 1313 deletions(-) diff --git a/spec-main/api-net-spec.ts b/spec-main/api-net-spec.ts index 85a7e15cc0910..b923aa9892d0c 100644 --- a/spec-main/api-net-spec.ts +++ b/spec-main/api-net-spec.ts @@ -1,180 +1,199 @@ -import { expect } from 'chai' -import { net, session, ClientRequest, BrowserWindow } from 'electron' -import * as http from 'http' -import * as url from 'url' -import { AddressInfo, Socket } from 'net' -import { emittedOnce } from './events-helpers' +import { expect } from 'chai'; +import { net, session, ClientRequest, BrowserWindow } from 'electron'; +import * as http from 'http'; +import * as url from 'url'; +import { AddressInfo, Socket } from 'net'; +import { emittedOnce } from './events-helpers'; -const kOneKiloByte = 1024 -const kOneMegaByte = kOneKiloByte * kOneKiloByte +const kOneKiloByte = 1024; +const kOneMegaByte = kOneKiloByte * kOneKiloByte; function randomBuffer (size: number, start: number = 0, end: number = 255) { - const range = 1 + end - start - const buffer = Buffer.allocUnsafe(size) + const range = 1 + end - start; + const buffer = Buffer.allocUnsafe(size); for (let i = 0; i < size; ++i) { - buffer[i] = start + Math.floor(Math.random() * range) + buffer[i] = start + Math.floor(Math.random() * range); } - return buffer + return buffer; } function randomString (length: number) { - const buffer = randomBuffer(length, '0'.charCodeAt(0), 'z'.charCodeAt(0)) - return buffer.toString() + const buffer = randomBuffer(length, '0'.charCodeAt(0), 'z'.charCodeAt(0)); + return buffer.toString(); } -const cleanupTasks: (() => void)[] = [] +const cleanupTasks: (() => void)[] = []; function cleanUp () { - cleanupTasks.forEach(t => t()) - cleanupTasks.length = 0 + cleanupTasks.forEach(t => t()); + cleanupTasks.length = 0; } -function respondOnce (fn: http.RequestListener): Promise { +async function getResponse (urlRequest: Electron.ClientRequest) { + return new Promise((resolve, reject) => { + urlRequest.on('error', reject); + urlRequest.on('abort', reject); + urlRequest.on('response', (response) => resolve(response)); + urlRequest.end(); + }); +} + +async function collectStreamBody (response: Electron.IncomingMessage | http.IncomingMessage) { + return (await collectStreamBodyBuffer(response)).toString(); +} + +function collectStreamBodyBuffer (response: Electron.IncomingMessage | http.IncomingMessage) { + return new Promise((resolve, reject) => { + response.on('error', reject); + (response as NodeJS.EventEmitter).on('aborted', reject); + const data: Buffer[] = []; + response.on('data', (chunk) => data.push(chunk)); + response.on('end', (chunk?: Buffer) => { + if (chunk) data.push(chunk); + resolve(Buffer.concat(data)); + }); + }); +} + +function respondNTimes (fn: http.RequestListener, n: number): Promise { return new Promise((resolve) => { const server = http.createServer((request, response) => { - fn(request, response) + fn(request, response); // don't close if a redirect was returned - if (response.statusCode < 300 || response.statusCode >= 399) { server.close() } - }) + n--; + if ((response.statusCode < 300 || response.statusCode >= 399) && n <= 0) { server.close(); } + }); server.listen(0, '127.0.0.1', () => { - resolve(`http://127.0.0.1:${(server.address() as AddressInfo).port}`) - }) - const sockets: Socket[] = [] - server.on('connection', s => sockets.push(s)) + resolve(`http://127.0.0.1:${(server.address() as AddressInfo).port}`); + }); + const sockets: Socket[] = []; + server.on('connection', s => sockets.push(s)); cleanupTasks.push(() => { - server.close() - sockets.forEach(s => s.destroy()) - }) - }) + server.close(); + sockets.forEach(s => s.destroy()); + }); + }); +} + +function respondOnce (fn: http.RequestListener) { + return respondNTimes(fn, 1); } -respondOnce.toRoutes = (routes: Record) => { - return respondOnce((request, response) => { +let routeFailure = false; + +respondNTimes.toRoutes = (routes: Record, n: number) => { + return respondNTimes((request, response) => { if (routes.hasOwnProperty(request.url || '')) { - routes[request.url || ''](request, response) + (async () => { + await Promise.resolve(routes[request.url || ''](request, response)); + })().catch((err) => { + routeFailure = true; + console.error('Route handler failed, this is probably why your test failed', err); + response.statusCode = 500; + response.end(); + }); } else { - response.statusCode = 500 - response.end() - expect.fail(`Unexpected URL: ${request.url}`) + response.statusCode = 500; + response.end(); + expect.fail(`Unexpected URL: ${request.url}`); } - }) -} + }, n); +}; +respondOnce.toRoutes = (routes: Record) => respondNTimes.toRoutes(routes, 1); -respondOnce.toURL = (url: string, fn: http.RequestListener) => { - return respondOnce.toRoutes({ [url]: fn }) -} +respondNTimes.toURL = (url: string, fn: http.RequestListener, n: number) => { + return respondNTimes.toRoutes({ [url]: fn }, n); +}; +respondOnce.toURL = (url: string, fn: http.RequestListener) => respondNTimes.toURL(url, fn, 1); -respondOnce.toSingleURL = (fn: http.RequestListener) => { - const requestUrl = '/requestUrl' - return respondOnce.toURL(requestUrl, fn).then(url => `${url}${requestUrl}`) -} +respondNTimes.toSingleURL = (fn: http.RequestListener, n: number) => { + const requestUrl = '/requestUrl'; + return respondNTimes.toURL(requestUrl, fn, n).then(url => `${url}${requestUrl}`); +}; +respondOnce.toSingleURL = (fn: http.RequestListener) => respondNTimes.toSingleURL(fn, 1); describe('net module', () => { - afterEach(cleanUp) - afterEach(async () => { - await session.defaultSession.clearCache() - }) - describe('HTTP basics', () => { - it('should be able to issue a basic GET request', (done) => { - respondOnce.toSingleURL((request, response) => { - expect(request.method).to.equal('GET') - response.end() - }).then(serverUrl => { - const urlRequest = net.request(serverUrl) - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - response.on('data', () => {}) - response.on('end', () => { - done() - }) - }) - urlRequest.end() - }) - }) - - it('should be able to issue a basic POST request', (done) => { - respondOnce.toSingleURL((request, response) => { - expect(request.method).to.equal('POST') - response.end() - }).then(serverUrl => { - const urlRequest = net.request({ - method: 'POST', - url: serverUrl - }) - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - response.on('data', () => { }) - response.on('end', () => { - done() - }) - }) - urlRequest.end() - }) - }) - - it('should fetch correct data in a GET request', (done) => { - const bodyData = 'Hello World!' - respondOnce.toSingleURL((request, response) => { - expect(request.method).to.equal('GET') - response.end(bodyData) - }).then(serverUrl => { - const urlRequest = net.request(serverUrl) - urlRequest.on('response', (response) => { - let expectedBodyData = '' - expect(response.statusCode).to.equal(200) - response.on('data', (chunk) => { - expectedBodyData += chunk.toString() - }) - response.on('end', () => { - expect(expectedBodyData).to.equal(bodyData) - done() - }) - }) - urlRequest.end() - }) - }) + beforeEach(() => { + routeFailure = false; + }); + afterEach(cleanUp); + afterEach(async function () { + await session.defaultSession.clearCache(); + if (routeFailure && this.test) { + if (!this.test.isFailed()) { + throw new Error('Failing this test due an unhandled error in the respondOnce route handler, check the logs above for the actual error'); + } + } + }); - it('should post the correct data in a POST request', (done) => { - const bodyData = 'Hello World!' - respondOnce.toSingleURL((request, response) => { - let postedBodyData = '' - expect(request.method).to.equal('POST') - request.on('data', (chunk: Buffer) => { - postedBodyData += chunk.toString() - }) - request.on('end', () => { - expect(postedBodyData).to.equal(bodyData) - response.end() - }) - }).then(serverUrl => { - const urlRequest = net.request({ - method: 'POST', - url: serverUrl - }) - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - response.on('data', () => {}) - response.on('end', () => { - done() - }) - }) - urlRequest.write(bodyData) - urlRequest.end() - }) - }) + describe('HTTP basics', () => { + it('should be able to issue a basic GET request', async () => { + const serverUrl = await respondOnce.toSingleURL((request, response) => { + expect(request.method).to.equal('GET'); + response.end(); + }); + const urlRequest = net.request(serverUrl); + const response = await getResponse(urlRequest); + expect(response.statusCode).to.equal(200); + await collectStreamBody(response); + }); + + it('should be able to issue a basic POST request', async () => { + const serverUrl = await respondOnce.toSingleURL((request, response) => { + expect(request.method).to.equal('POST'); + response.end(); + }); + const urlRequest = net.request({ + method: 'POST', + url: serverUrl + }); + const response = await getResponse(urlRequest); + expect(response.statusCode).to.equal(200); + await collectStreamBody(response); + }); + + it('should fetch correct data in a GET request', async () => { + const expectedBodyData = 'Hello World!'; + const serverUrl = await respondOnce.toSingleURL((request, response) => { + expect(request.method).to.equal('GET'); + response.end(expectedBodyData); + }); + const urlRequest = net.request(serverUrl); + const response = await getResponse(urlRequest); + expect(response.statusCode).to.equal(200); + const body = await collectStreamBody(response); + expect(body).to.equal(expectedBodyData); + }); + + it('should post the correct data in a POST request', async () => { + const bodyData = 'Hello World!'; + const serverUrl = await respondOnce.toSingleURL(async (request, response) => { + const postedBodyData = await collectStreamBody(request); + expect(postedBodyData).to.equal(bodyData); + response.end(); + }); + const urlRequest = net.request({ + method: 'POST', + url: serverUrl + }); + urlRequest.write(bodyData); + const response = await getResponse(urlRequest); + expect(response.statusCode).to.equal(200); + }); - it('should support chunked encoding', (done) => { - respondOnce.toSingleURL((request, response) => { - response.statusCode = 200 - response.statusMessage = 'OK' - response.chunkedEncoding = true - expect(request.method).to.equal('POST') - expect(request.headers['transfer-encoding']).to.equal('chunked') - expect(request.headers['content-length']).to.equal(undefined) + it('should support chunked encoding', async () => { + const serverUrl = await respondOnce.toSingleURL((request, response) => { + response.statusCode = 200; + response.statusMessage = 'OK'; + response.chunkedEncoding = true; + expect(request.method).to.equal('POST'); + expect(request.headers['transfer-encoding']).to.equal('chunked'); + expect(request.headers['content-length']).to.equal(undefined); request.on('data', (chunk: Buffer) => { - response.write(chunk) - }) + response.write(chunk); + }); request.on('end', (chunk: Buffer) => { +<<<<<<< HEAD response.end(chunk) }) }).then(serverUrl => { @@ -208,101 +227,100 @@ describe('net module', () => { urlRequest.end() }) }) +======= + response.end(chunk); + }); + }); + const urlRequest = net.request({ + method: 'POST', + url: serverUrl + }); + + let chunkIndex = 0; + const chunkCount = 100; + let sent = Buffer.alloc(0); + + urlRequest.chunkedEncoding = true; + while (chunkIndex < chunkCount) { + chunkIndex += 1; + const chunk = randomBuffer(kOneKiloByte); + sent = Buffer.concat([sent, chunk]); + urlRequest.write(chunk); + } + + const response = await getResponse(urlRequest); + expect(response.statusCode).to.equal(200); + const received = await collectStreamBodyBuffer(response); + expect(sent.equals(received)).to.be.true(); + expect(chunkIndex).to.be.equal(chunkCount); + }); +>>>>>>> 54e6492e2... chore: refactor all the net specs to be async with better error handling (#22731) it('should emit the login event when 401', async () => { - const [user, pass] = ['user', 'pass'] + const [user, pass] = ['user', 'pass']; const serverUrl = await respondOnce.toSingleURL((request, response) => { if (!request.headers.authorization) { - return response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' }).end() + return response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' }).end(); } - response.writeHead(200).end('ok') - }) - let loginAuthInfo: any - await new Promise((resolve, reject) => { - const request = net.request({ method: 'GET', url: serverUrl }) - request.on('response', (response) => { - response.on('error', reject) - response.on('data', () => {}) - response.on('end', () => resolve()) - }) - request.on('login', (authInfo, cb) => { - loginAuthInfo = authInfo - cb(user, pass) - }) - request.on('error', reject) - request.end() - }) - expect(loginAuthInfo.realm).to.equal('Foo') - expect(loginAuthInfo.scheme).to.equal('basic') - }) + response.writeHead(200).end('ok'); + }); + let loginAuthInfo: Electron.AuthInfo; + const request = net.request({ method: 'GET', url: serverUrl }); + request.on('login', (authInfo, cb) => { + loginAuthInfo = authInfo; + cb(user, pass); + }); + const response = await getResponse(request); + expect(response.statusCode).to.equal(200); + expect(loginAuthInfo!.realm).to.equal('Foo'); + expect(loginAuthInfo!.scheme).to.equal('basic'); + }); it('should response when cancelling authentication', async () => { const serverUrl = await respondOnce.toSingleURL((request, response) => { if (!request.headers.authorization) { - response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' }) - response.end('unauthenticated') + response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' }); + response.end('unauthenticated'); } else { - response.writeHead(200).end('ok') + response.writeHead(200).end('ok'); } - }) - expect(await new Promise((resolve, reject) => { - const request = net.request({ method: 'GET', url: serverUrl }) - request.on('response', (response) => { - let data = '' - response.on('error', reject) - response.on('data', (chunk) => { - data += chunk - }) - response.on('end', () => resolve(data)) - }) - request.on('login', (authInfo, cb) => { - cb() - }) - request.on('error', reject) - request.end() - })).to.equal('unauthenticated') - }) + }); + const request = net.request({ method: 'GET', url: serverUrl }); + request.on('login', (authInfo, cb) => { + cb(); + }); + const response = await getResponse(request); + const body = await collectStreamBody(response); + expect(body).to.equal('unauthenticated'); + }); it('should share credentials with WebContents', async () => { - const [user, pass] = ['user', 'pass'] - const serverUrl = await new Promise((resolve) => { - const server = http.createServer((request, response) => { - if (!request.headers.authorization) { - return response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' }).end() - } - return response.writeHead(200).end('ok') - }) - server.listen(0, '127.0.0.1', () => { - resolve(`http://127.0.0.1:${(server.address() as AddressInfo).port}`) - }) - after(() => { server.close() }) - }) - const bw = new BrowserWindow({ show: false }) - const loaded = bw.loadURL(serverUrl) + const [user, pass] = ['user', 'pass']; + const serverUrl = await respondNTimes.toSingleURL((request, response) => { + if (!request.headers.authorization) { + return response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' }).end(); + } + return response.writeHead(200).end('ok'); + }, 2); + const bw = new BrowserWindow({ show: false }); bw.webContents.on('login', (event, details, authInfo, cb) => { - event.preventDefault() - cb(user, pass) - }) - await loaded - bw.close() - await new Promise((resolve, reject) => { - const request = net.request({ method: 'GET', url: serverUrl }) - request.on('response', (response) => { - response.on('error', reject) - response.on('data', () => {}) - response.on('end', () => resolve()) - }) - request.on('login', () => { - // we shouldn't receive a login event, because the credentials should - // be cached. - reject(new Error('unexpected login event')) - }) - request.on('error', reject) - request.end() - }) - }) + event.preventDefault(); + cb(user, pass); + }); + await bw.loadURL(serverUrl); + bw.close(); + const request = net.request({ method: 'GET', url: serverUrl }); + let logInCount = 0; + request.on('login', () => { + logInCount++; + }); + const response = await getResponse(request); + await collectStreamBody(response); + expect(logInCount).to.equal(0, 'should not receive a login event, credentials should be cached'); + }); it('should share proxy credentials with WebContents', async () => { +<<<<<<< HEAD const [user, pass] = ['user', 'pass'] const proxyPort = await new Promise((resolve) => { const server = http.createServer((request, response) => { @@ -320,1281 +338,1048 @@ describe('net module', () => { await customSession.setProxy({ proxyRules: `127.0.0.1:${proxyPort}`, proxyBypassRules: '<-loopback>' } as any) const bw = new BrowserWindow({ show: false, webPreferences: { session: customSession } }) const loaded = bw.loadURL('http://127.0.0.1:9999') +======= + const [user, pass] = ['user', 'pass']; + const proxyUrl = await respondNTimes((request, response) => { + if (!request.headers['proxy-authorization']) { + return response.writeHead(407, { 'Proxy-Authenticate': 'Basic realm="Foo"' }).end(); + } + return response.writeHead(200).end('ok'); + }, 2); + const customSession = session.fromPartition(`net-proxy-test-${Math.random()}`); + await customSession.setProxy({ proxyRules: proxyUrl.replace('http://', ''), proxyBypassRules: '<-loopback>' }); + const bw = new BrowserWindow({ show: false, webPreferences: { session: customSession } }); +>>>>>>> 54e6492e2... chore: refactor all the net specs to be async with better error handling (#22731) bw.webContents.on('login', (event, details, authInfo, cb) => { - event.preventDefault() - cb(user, pass) - }) - await loaded - bw.close() - await new Promise((resolve, reject) => { - const request = net.request({ method: 'GET', url: 'http://127.0.0.1:9999', session: customSession }) - request.on('response', (response) => { - response.on('error', reject) - response.on('data', () => {}) - response.on('end', () => resolve()) - }) - request.on('login', () => { - // we shouldn't receive a login event, because the credentials should - // be cached. - reject(new Error('unexpected login event')) - }) - request.on('error', reject) - request.end() - }) - }) + event.preventDefault(); + cb(user, pass); + }); + await bw.loadURL('http://127.0.0.1:9999'); + bw.close(); + const request = net.request({ method: 'GET', url: 'http://127.0.0.1:9999', session: customSession }); + let logInCount = 0; + request.on('login', () => { + logInCount++; + }); + const response = await getResponse(request); + const body = await collectStreamBody(response); + expect(response.statusCode).to.equal(200); + expect(body).to.equal('ok'); + expect(logInCount).to.equal(0, 'should not receive a login event, credentials should be cached'); + }); it('should upload body when 401', async () => { - const [user, pass] = ['user', 'pass'] + const [user, pass] = ['user', 'pass']; const serverUrl = await respondOnce.toSingleURL((request, response) => { if (!request.headers.authorization) { - return response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' }).end() + return response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' }).end(); } - response.writeHead(200) - request.on('data', (chunk) => { response.write(chunk) }) - request.on('end', () => { - response.end() - }) - }) - const requestData = randomString(kOneKiloByte) - const responseData = await new Promise((resolve, reject) => { - const request = net.request({ method: 'GET', url: serverUrl }) - request.on('response', (response) => { - response.on('error', reject) - let data = '' - response.on('data', (chunk) => { data += chunk.toString() }) - response.on('end', () => resolve(data)) - }) - request.on('login', (authInfo, cb) => { - cb(user, pass) - }) - request.on('error', reject) - request.end(requestData) - }) - expect(responseData).to.equal(requestData) - }) - }) + response.writeHead(200); + request.on('data', (chunk) => response.write(chunk)); + request.on('end', () => response.end()); + }); + const requestData = randomString(kOneKiloByte); + const request = net.request({ method: 'GET', url: serverUrl }); + request.on('login', (authInfo, cb) => { + cb(user, pass); + }); + request.write(requestData); + const response = await getResponse(request); + const responseData = await collectStreamBody(response); + expect(responseData).to.equal(requestData); + }); + }); describe('ClientRequest API', () => { - it('request/response objects should emit expected events', (done) => { - const bodyData = randomString(kOneKiloByte) - respondOnce.toSingleURL((request, response) => { - response.end(bodyData) - }).then(serverUrl => { - let requestResponseEventEmitted = false - let requestFinishEventEmitted = false - let requestCloseEventEmitted = false - let responseDataEventEmitted = false - let responseEndEventEmitted = false - - function maybeDone (done: () => void) { - if (!requestCloseEventEmitted || !responseEndEventEmitted) { - return - } - - expect(requestResponseEventEmitted).to.equal(true) - expect(requestFinishEventEmitted).to.equal(true) - expect(requestCloseEventEmitted).to.equal(true) - expect(responseDataEventEmitted).to.equal(true) - expect(responseEndEventEmitted).to.equal(true) - done() - } - - const urlRequest = net.request(serverUrl) - urlRequest.on('response', (response) => { - requestResponseEventEmitted = true - const statusCode = response.statusCode - expect(statusCode).to.equal(200) - const buffers: Buffer[] = [] - response.on('data', (chunk) => { - buffers.push(chunk) - responseDataEventEmitted = true - }) - response.on('end', () => { - const receivedBodyData = Buffer.concat(buffers) - expect(receivedBodyData.toString()).to.equal(bodyData) - responseEndEventEmitted = true - maybeDone(done) - }) - response.on('error', (error: Error) => { - expect(error).to.be.an('Error') - }) - response.on('aborted', () => { - expect.fail('response aborted') - }) - }) - urlRequest.on('finish', () => { - requestFinishEventEmitted = true - }) - urlRequest.on('error', (error) => { - expect(error).to.be.an('Error') - }) - urlRequest.on('abort', () => { - expect.fail('request aborted') - }) - urlRequest.on('close', () => { - requestCloseEventEmitted = true - maybeDone(done) - }) - urlRequest.end() - }) - }) - - it('should be able to set a custom HTTP request header before first write', (done) => { - const customHeaderName = 'Some-Custom-Header-Name' - const customHeaderValue = 'Some-Customer-Header-Value' - respondOnce.toSingleURL((request, response) => { - expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue) - response.statusCode = 200 - response.statusMessage = 'OK' - response.end() - }).then(serverUrl => { - const urlRequest = net.request(serverUrl) - urlRequest.on('response', (response) => { - const statusCode = response.statusCode - expect(statusCode).to.equal(200) - response.on('data', () => { - }) - response.on('end', () => { - done() - }) - }) - urlRequest.setHeader(customHeaderName, customHeaderValue) - expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) - expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue) - urlRequest.write('') - expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) - expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue) - urlRequest.end() - }) - }) + it('request/response objects should emit expected events', async () => { + const bodyData = randomString(kOneKiloByte); + const serverUrl = await respondOnce.toSingleURL((request, response) => { + response.end(bodyData); + }); + + const urlRequest = net.request(serverUrl); + // request close event + const closePromise = emittedOnce(urlRequest, 'close'); + // request finish event + const finishPromise = emittedOnce(urlRequest, 'close'); + // request "response" event + const response = await getResponse(urlRequest); + response.on('error', (error: Error) => { + expect(error).to.be.an('Error'); + }); + const statusCode = response.statusCode; + expect(statusCode).to.equal(200); + // response data event + // respond end event + const body = await collectStreamBody(response); + expect(body).to.equal(bodyData); + urlRequest.on('error', (error) => { + expect(error).to.be.an('Error'); + }); + await Promise.all([closePromise, finishPromise]); + }); + + it('should be able to set a custom HTTP request header before first write', async () => { + const customHeaderName = 'Some-Custom-Header-Name'; + const customHeaderValue = 'Some-Customer-Header-Value'; + const serverUrl = await respondOnce.toSingleURL((request, response) => { + expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue); + response.statusCode = 200; + response.statusMessage = 'OK'; + response.end(); + }); + const urlRequest = net.request(serverUrl); + urlRequest.setHeader(customHeaderName, customHeaderValue); + expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue); + expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue); + urlRequest.write(''); + expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue); + expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue); + const response = await getResponse(urlRequest); + expect(response.statusCode).to.equal(200); + await collectStreamBody(response); + }); it('should be able to set a non-string object as a header value', async () => { - const customHeaderName = 'Some-Integer-Value' - const customHeaderValue = 900 + const customHeaderName = 'Some-Integer-Value'; + const customHeaderValue = 900; const serverUrl = await respondOnce.toSingleURL((request, response) => { - expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue.toString()) - response.statusCode = 200 - response.statusMessage = 'OK' - response.end() - }) - const urlRequest = net.request(serverUrl) - const complete = new Promise(resolve => { - urlRequest.on('response', (response) => { - resolve(response.statusCode) - response.on('data', () => {}) - response.on('end', () => {}) - }) - }) - urlRequest.setHeader(customHeaderName, customHeaderValue as any) - expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) - expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue) - urlRequest.write('') - expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) - expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue) - urlRequest.end() - expect(await complete).to.equal(200) - }) - - it('should not be able to set a custom HTTP request header after first write', (done) => { - const customHeaderName = 'Some-Custom-Header-Name' - const customHeaderValue = 'Some-Customer-Header-Value' - respondOnce.toSingleURL((request, response) => { - expect(request.headers[customHeaderName.toLowerCase()]).to.equal(undefined) - response.statusCode = 200 - response.statusMessage = 'OK' - response.end() - }).then(serverUrl => { - const urlRequest = net.request(serverUrl) - urlRequest.on('response', (response) => { - const statusCode = response.statusCode - expect(statusCode).to.equal(200) - response.on('data', () => {}) - response.on('end', () => { - done() - }) - }) - urlRequest.write('') - expect(() => { - urlRequest.setHeader(customHeaderName, customHeaderValue) - }).to.throw() - expect(urlRequest.getHeader(customHeaderName)).to.equal(undefined) - urlRequest.end() - }) - }) - - it('should be able to remove a custom HTTP request header before first write', (done) => { - const customHeaderName = 'Some-Custom-Header-Name' - const customHeaderValue = 'Some-Customer-Header-Value' - respondOnce.toSingleURL((request, response) => { - expect(request.headers[customHeaderName.toLowerCase()]).to.equal(undefined) - response.statusCode = 200 - response.statusMessage = 'OK' - response.end() - }).then(serverUrl => { - const urlRequest = net.request(serverUrl) - urlRequest.on('response', (response) => { - const statusCode = response.statusCode - expect(statusCode).to.equal(200) - response.on('data', () => {}) - response.on('end', () => { - done() - }) - }) - urlRequest.setHeader(customHeaderName, customHeaderValue) - expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) - urlRequest.removeHeader(customHeaderName) - expect(urlRequest.getHeader(customHeaderName)).to.equal(undefined) - urlRequest.write('') - urlRequest.end() - }) - }) - - it('should not be able to remove a custom HTTP request header after first write', (done) => { - const customHeaderName = 'Some-Custom-Header-Name' - const customHeaderValue = 'Some-Customer-Header-Value' - respondOnce.toSingleURL((request, response) => { - expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue) - response.statusCode = 200 - response.statusMessage = 'OK' - response.end() - }).then(serverUrl => { - const urlRequest = net.request(serverUrl) - urlRequest.on('response', (response) => { - const statusCode = response.statusCode - expect(statusCode).to.equal(200) - response.on('data', () => {}) - response.on('end', () => { - done() - }) - }) - urlRequest.setHeader(customHeaderName, customHeaderValue) - expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) - urlRequest.write('') - expect(() => { - urlRequest.removeHeader(customHeaderName) - }).to.throw() - expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue) - urlRequest.end() - }) - }) - - it('should be able to set cookie header line', (done) => { - const cookieHeaderName = 'Cookie' - const cookieHeaderValue = 'test=12345' - const customSession = session.fromPartition('test-cookie-header') - respondOnce.toSingleURL((request, response) => { - expect(request.headers[cookieHeaderName.toLowerCase()]).to.equal(cookieHeaderValue) - response.statusCode = 200 - response.statusMessage = 'OK' - response.end() - }).then(serverUrl => { - customSession.cookies.set({ - url: `${serverUrl}`, - name: 'test', - value: '11111', - expirationDate: 0 - }).then(() => { // resolved - const urlRequest = net.request({ - method: 'GET', - url: serverUrl, - session: customSession - }) - urlRequest.on('response', (response) => { - const statusCode = response.statusCode - expect(statusCode).to.equal(200) - response.on('data', () => {}) - response.on('end', () => { - done() - }) - }) - urlRequest.setHeader(cookieHeaderName, cookieHeaderValue) - expect(urlRequest.getHeader(cookieHeaderName)).to.equal(cookieHeaderValue) - urlRequest.end() - }, (error) => { - done(error) - }) - }) - }) - - it('should be able to receive cookies', (done) => { - const cookie = ['cookie1', 'cookie2'] - respondOnce.toSingleURL((request, response) => { - response.statusCode = 200 - response.statusMessage = 'OK' - response.setHeader('set-cookie', cookie) - response.end() - }).then(serverUrl => { - const urlRequest = net.request(serverUrl) - urlRequest.on('response', (response) => { - expect(response.headers['set-cookie']).to.have.same.members(cookie) - done() - }) - urlRequest.end() - }) - }) + expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue.toString()); + response.statusCode = 200; + response.statusMessage = 'OK'; + response.end(); + }); + + const urlRequest = net.request(serverUrl); + urlRequest.setHeader(customHeaderName, customHeaderValue as any); + expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue); + expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue); + urlRequest.write(''); + expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue); + expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue); + const response = await getResponse(urlRequest); + expect(response.statusCode).to.equal(200); + await collectStreamBody(response); + }); + + it('should not be able to set a custom HTTP request header after first write', async () => { + const customHeaderName = 'Some-Custom-Header-Name'; + const customHeaderValue = 'Some-Customer-Header-Value'; + const serverUrl = await respondOnce.toSingleURL((request, response) => { + expect(request.headers[customHeaderName.toLowerCase()]).to.equal(undefined); + response.statusCode = 200; + response.statusMessage = 'OK'; + response.end(); + }); + const urlRequest = net.request(serverUrl); + urlRequest.write(''); + expect(() => { + urlRequest.setHeader(customHeaderName, customHeaderValue); + }).to.throw(); + expect(urlRequest.getHeader(customHeaderName)).to.equal(undefined); + const response = await getResponse(urlRequest); + expect(response.statusCode).to.equal(200); + await collectStreamBody(response); + }); + + it('should be able to remove a custom HTTP request header before first write', async () => { + const customHeaderName = 'Some-Custom-Header-Name'; + const customHeaderValue = 'Some-Customer-Header-Value'; + const serverUrl = await respondOnce.toSingleURL((request, response) => { + expect(request.headers[customHeaderName.toLowerCase()]).to.equal(undefined); + response.statusCode = 200; + response.statusMessage = 'OK'; + response.end(); + }); + const urlRequest = net.request(serverUrl); + urlRequest.setHeader(customHeaderName, customHeaderValue); + expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue); + urlRequest.removeHeader(customHeaderName); + expect(urlRequest.getHeader(customHeaderName)).to.equal(undefined); + urlRequest.write(''); + const response = await getResponse(urlRequest); + expect(response.statusCode).to.equal(200); + await collectStreamBody(response); + }); + + it('should not be able to remove a custom HTTP request header after first write', async () => { + const customHeaderName = 'Some-Custom-Header-Name'; + const customHeaderValue = 'Some-Customer-Header-Value'; + const serverUrl = await respondOnce.toSingleURL((request, response) => { + expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue); + response.statusCode = 200; + response.statusMessage = 'OK'; + response.end(); + }); + const urlRequest = net.request(serverUrl); + urlRequest.setHeader(customHeaderName, customHeaderValue); + expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue); + urlRequest.write(''); + expect(() => { + urlRequest.removeHeader(customHeaderName); + }).to.throw(); + expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue); + const response = await getResponse(urlRequest); + expect(response.statusCode).to.equal(200); + await collectStreamBody(response); + }); + + it('should be able to set cookie header line', async () => { + const cookieHeaderName = 'Cookie'; + const cookieHeaderValue = 'test=12345'; + const customSession = session.fromPartition('test-cookie-header'); + const serverUrl = await respondOnce.toSingleURL((request, response) => { + expect(request.headers[cookieHeaderName.toLowerCase()]).to.equal(cookieHeaderValue); + response.statusCode = 200; + response.statusMessage = 'OK'; + response.end(); + }); + await customSession.cookies.set({ + url: `${serverUrl}`, + name: 'test', + value: '11111', + expirationDate: 0 + }); + const urlRequest = net.request({ + method: 'GET', + url: serverUrl, + session: customSession + }); + urlRequest.setHeader(cookieHeaderName, cookieHeaderValue); + expect(urlRequest.getHeader(cookieHeaderName)).to.equal(cookieHeaderValue); + const response = await getResponse(urlRequest); + expect(response.statusCode).to.equal(200); + await collectStreamBody(response); + }); + + it('should be able to receive cookies', async () => { + const cookie = ['cookie1', 'cookie2']; + const serverUrl = await respondOnce.toSingleURL((request, response) => { + response.statusCode = 200; + response.statusMessage = 'OK'; + response.setHeader('set-cookie', cookie); + response.end(); + }); + const urlRequest = net.request(serverUrl); + const response = await getResponse(urlRequest); + expect(response.headers['set-cookie']).to.have.same.members(cookie); + }); it('should be able to abort an HTTP request before first write', async () => { const serverUrl = await respondOnce.toSingleURL((request, response) => { - response.end() - expect.fail('Unexpected request event') - }) + response.end(); + expect.fail('Unexpected request event'); + }); - const urlRequest = net.request(serverUrl) + const urlRequest = net.request(serverUrl); urlRequest.on('response', () => { - expect.fail('unexpected response event') - }) - const aborted = emittedOnce(urlRequest, 'abort') - urlRequest.abort() - urlRequest.write('') - urlRequest.end() - await aborted - }) - - it('it should be able to abort an HTTP request before request end', (done) => { - let requestReceivedByServer = false - let urlRequest: ClientRequest | null = null - respondOnce.toSingleURL(() => { - requestReceivedByServer = true - urlRequest!.abort() - }).then(serverUrl => { - let requestAbortEventEmitted = false + expect.fail('unexpected response event'); + }); + const aborted = emittedOnce(urlRequest, 'abort'); + urlRequest.abort(); + urlRequest.write(''); + urlRequest.end(); + await aborted; + }); + + it('it should be able to abort an HTTP request before request end', async () => { + let requestReceivedByServer = false; + let urlRequest: ClientRequest | null = null; + const serverUrl = await respondOnce.toSingleURL(() => { + requestReceivedByServer = true; + urlRequest!.abort(); + }); + let requestAbortEventEmitted = false; - urlRequest = net.request(serverUrl) - urlRequest.on('response', () => { - expect.fail('Unexpected response event') - }) - urlRequest.on('finish', () => { - expect.fail('Unexpected finish event') - }) - urlRequest.on('error', () => { - expect.fail('Unexpected error event') - }) - urlRequest.on('abort', () => { - requestAbortEventEmitted = true - }) - urlRequest.on('close', () => { - expect(requestReceivedByServer).to.equal(true) - expect(requestAbortEventEmitted).to.equal(true) - done() - }) + urlRequest = net.request(serverUrl); + urlRequest.on('response', () => { + expect.fail('Unexpected response event'); + }); + urlRequest.on('finish', () => { + expect.fail('Unexpected finish event'); + }); + urlRequest.on('error', () => { + expect.fail('Unexpected error event'); + }); + urlRequest.on('abort', () => { + requestAbortEventEmitted = true; + }); - urlRequest.chunkedEncoding = true - urlRequest.write(randomString(kOneKiloByte)) - }) - }) + await emittedOnce(urlRequest, 'close', () => { + urlRequest!.chunkedEncoding = true; + urlRequest!.write(randomString(kOneKiloByte)); + }); + expect(requestReceivedByServer).to.equal(true); + expect(requestAbortEventEmitted).to.equal(true); + }); it('it should be able to abort an HTTP request after request end and before response', async () => { - let requestReceivedByServer = false - let urlRequest: ClientRequest | null = null + let requestReceivedByServer = false; + let urlRequest: ClientRequest | null = null; const serverUrl = await respondOnce.toSingleURL((request, response) => { - requestReceivedByServer = true - urlRequest!.abort() + requestReceivedByServer = true; + urlRequest!.abort(); process.nextTick(() => { - response.statusCode = 200 - response.statusMessage = 'OK' - response.end() - }) - }) - let requestFinishEventEmitted = false - - urlRequest = net.request(serverUrl) + response.statusCode = 200; + response.statusMessage = 'OK'; + response.end(); + }); + }); + let requestFinishEventEmitted = false; + + urlRequest = net.request(serverUrl); urlRequest.on('response', () => { - expect.fail('Unexpected response event') - }) + expect.fail('Unexpected response event'); + }); urlRequest.on('finish', () => { - requestFinishEventEmitted = true - }) + requestFinishEventEmitted = true; + }); urlRequest.on('error', () => { - expect.fail('Unexpected error event') - }) - urlRequest.end(randomString(kOneKiloByte)) - await emittedOnce(urlRequest, 'abort') - expect(requestFinishEventEmitted).to.equal(true) - expect(requestReceivedByServer).to.equal(true) - }) + expect.fail('Unexpected error event'); + }); + urlRequest.end(randomString(kOneKiloByte)); + await emittedOnce(urlRequest, 'abort'); + expect(requestFinishEventEmitted).to.equal(true); + expect(requestReceivedByServer).to.equal(true); + }); it('it should be able to abort an HTTP request after response start', async () => { - let requestReceivedByServer = false + let requestReceivedByServer = false; const serverUrl = await respondOnce.toSingleURL((request, response) => { - requestReceivedByServer = true - response.statusCode = 200 - response.statusMessage = 'OK' - response.write(randomString(kOneKiloByte)) - }) - let requestFinishEventEmitted = false - let requestResponseEventEmitted = false - let responseCloseEventEmitted = false - - const urlRequest = net.request(serverUrl) + requestReceivedByServer = true; + response.statusCode = 200; + response.statusMessage = 'OK'; + response.write(randomString(kOneKiloByte)); + }); + let requestFinishEventEmitted = false; + let requestResponseEventEmitted = false; + let responseCloseEventEmitted = false; + + const urlRequest = net.request(serverUrl); urlRequest.on('response', (response) => { - requestResponseEventEmitted = true - const statusCode = response.statusCode - expect(statusCode).to.equal(200) - response.on('data', () => {}) + requestResponseEventEmitted = true; + const statusCode = response.statusCode; + expect(statusCode).to.equal(200); + response.on('data', () => {}); response.on('end', () => { - expect.fail('Unexpected end event') - }) + expect.fail('Unexpected end event'); + }); response.on('error', () => { - expect.fail('Unexpected error event') - }) + expect.fail('Unexpected error event'); + }); response.on('close' as any, () => { - responseCloseEventEmitted = true - }) - urlRequest.abort() - }) + responseCloseEventEmitted = true; + }); + urlRequest.abort(); + }); urlRequest.on('finish', () => { - requestFinishEventEmitted = true - }) + requestFinishEventEmitted = true; + }); urlRequest.on('error', () => { - expect.fail('Unexpected error event') - }) - urlRequest.end(randomString(kOneKiloByte)) - await emittedOnce(urlRequest, 'abort') - expect(requestFinishEventEmitted).to.be.true('request should emit "finish" event') - expect(requestReceivedByServer).to.be.true('request should be received by the server') - expect(requestResponseEventEmitted).to.be.true('"response" event should be emitted') - expect(responseCloseEventEmitted).to.be.true('response should emit "close" event') - }) + expect.fail('Unexpected error event'); + }); + urlRequest.end(randomString(kOneKiloByte)); + await emittedOnce(urlRequest, 'abort'); + expect(requestFinishEventEmitted).to.be.true('request should emit "finish" event'); + expect(requestReceivedByServer).to.be.true('request should be received by the server'); + expect(requestResponseEventEmitted).to.be.true('"response" event should be emitted'); + expect(responseCloseEventEmitted).to.be.true('response should emit "close" event'); + }); it('abort event should be emitted at most once', async () => { - let requestReceivedByServer = false - let urlRequest: ClientRequest | null = null + let requestReceivedByServer = false; + let urlRequest: ClientRequest | null = null; const serverUrl = await respondOnce.toSingleURL(() => { - requestReceivedByServer = true - urlRequest!.abort() - urlRequest!.abort() - }) - let requestFinishEventEmitted = false - let abortsEmitted = 0 - - urlRequest = net.request(serverUrl) + requestReceivedByServer = true; + urlRequest!.abort(); + urlRequest!.abort(); + }); + let requestFinishEventEmitted = false; + let abortsEmitted = 0; + + urlRequest = net.request(serverUrl); urlRequest.on('response', () => { - expect.fail('Unexpected response event') - }) + expect.fail('Unexpected response event'); + }); urlRequest.on('finish', () => { - requestFinishEventEmitted = true - }) + requestFinishEventEmitted = true; + }); urlRequest.on('error', () => { - expect.fail('Unexpected error event') - }) + expect.fail('Unexpected error event'); + }); urlRequest.on('abort', () => { - abortsEmitted++ - }) - urlRequest.end(randomString(kOneKiloByte)) - await emittedOnce(urlRequest, 'abort') - expect(requestFinishEventEmitted).to.be.true('request should emit "finish" event') - expect(requestReceivedByServer).to.be.true('request should be received by server') - expect(abortsEmitted).to.equal(1, 'request should emit exactly 1 "abort" event') - }) + abortsEmitted++; + }); + urlRequest.end(randomString(kOneKiloByte)); + await emittedOnce(urlRequest, 'abort'); + expect(requestFinishEventEmitted).to.be.true('request should emit "finish" event'); + expect(requestReceivedByServer).to.be.true('request should be received by server'); + expect(abortsEmitted).to.equal(1, 'request should emit exactly 1 "abort" event'); + }); it('should allow to read response body from non-2xx response', async () => { - const bodyData = randomString(kOneKiloByte) + const bodyData = randomString(kOneKiloByte); const serverUrl = await respondOnce.toSingleURL((request, response) => { - response.statusCode = 404 - response.end(bodyData) - }) - - let requestResponseEventEmitted = false - let responseDataEventEmitted = false - - const urlRequest = net.request(serverUrl) + response.statusCode = 404; + response.end(bodyData); + }); + + const urlRequest = net.request(serverUrl); + const bodyCheckPromise = getResponse(urlRequest).then(r => { + expect(r.statusCode).to.equal(404); + return r; + }).then(collectStreamBody).then(receivedBodyData => { + expect(receivedBodyData.toString()).to.equal(bodyData); + }); const eventHandlers = Promise.all([ - emittedOnce(urlRequest, 'response') - .then(async (params: any[]) => { - const response: Electron.IncomingMessage = params[0] - requestResponseEventEmitted = true - const statusCode = response.statusCode - expect(statusCode).to.equal(404) - const buffers: Buffer[] = [] - response.on('data', (chunk) => { - buffers.push(chunk) - responseDataEventEmitted = true - }) - await new Promise((resolve, reject) => { - response.on('error', () => { - reject(new Error('error emitted')) - }) - emittedOnce(response, 'end') - .then(() => { - const receivedBodyData = Buffer.concat(buffers) - expect(receivedBodyData.toString()).to.equal(bodyData) - }) - .then(resolve) - .catch(reject) - }) - }), + bodyCheckPromise, emittedOnce(urlRequest, 'close') - ]) + ]); - urlRequest.end() + urlRequest.end(); - await eventHandlers - - expect(requestResponseEventEmitted).to.equal(true) - expect(responseDataEventEmitted).to.equal(true) - }) + await eventHandlers; + }); describe('webRequest', () => { afterEach(() => { - session.defaultSession.webRequest.onBeforeRequest(null) - }) + session.defaultSession.webRequest.onBeforeRequest(null); + }); it('Should throw when invalid filters are passed', () => { expect(() => { session.defaultSession.webRequest.onBeforeRequest( { urls: ['*://www.googleapis.com'] }, - (details, callback) => { callback({ cancel: false }) } - ) - }).to.throw('Invalid url pattern *://www.googleapis.com: Empty path.') + (details, callback) => { callback({ cancel: false }); } + ); + }).to.throw('Invalid url pattern *://www.googleapis.com: Empty path.'); expect(() => { session.defaultSession.webRequest.onBeforeRequest( { urls: [ '*://www.googleapis.com/', '*://blahblah.dev' ] }, - (details, callback) => { callback({ cancel: false }) } - ) - }).to.throw('Invalid url pattern *://blahblah.dev: Empty path.') - }) + (details, callback) => { callback({ cancel: false }); } + ); + }).to.throw('Invalid url pattern *://blahblah.dev: Empty path.'); + }); it('Should not throw when valid filters are passed', () => { expect(() => { session.defaultSession.webRequest.onBeforeRequest( { urls: ['*://www.googleapis.com/'] }, - (details, callback) => { callback({ cancel: false }) } - ) - }).to.not.throw() - }) - - it('Requests should be intercepted by webRequest module', (done) => { - const requestUrl = '/requestUrl' - const redirectUrl = '/redirectUrl' - let requestIsRedirected = false - respondOnce.toURL(redirectUrl, (request, response) => { - requestIsRedirected = true - response.end() - }).then(serverUrl => { - let requestIsIntercepted = false - session.defaultSession.webRequest.onBeforeRequest( - (details, callback) => { - if (details.url === `${serverUrl}${requestUrl}`) { - requestIsIntercepted = true - // Disabled due to false positive in StandardJS - // eslint-disable-next-line standard/no-callback-literal - callback({ - redirectURL: `${serverUrl}${redirectUrl}` - }) - } else { - callback({ - cancel: false - }) - } - }) - - const urlRequest = net.request(`${serverUrl}${requestUrl}`) - - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - response.on('data', () => {}) - response.on('end', () => { - expect(requestIsRedirected).to.be.true('The server should receive a request to the forward URL') - expect(requestIsIntercepted).to.be.true('The request should be intercepted by the webRequest module') - done() - }) - }) - urlRequest.end() - }) - }) - - it('should to able to create and intercept a request using a custom session object', (done) => { - const requestUrl = '/requestUrl' - const redirectUrl = '/redirectUrl' - const customPartitionName = 'custom-partition' - let requestIsRedirected = false - respondOnce.toURL(redirectUrl, (request, response) => { - requestIsRedirected = true - response.end() - }).then(serverUrl => { - session.defaultSession.webRequest.onBeforeRequest(() => { - expect.fail('Request should not be intercepted by the default session') - }) - - const customSession = session.fromPartition(customPartitionName, { cache: false }) - let requestIsIntercepted = false - customSession.webRequest.onBeforeRequest((details, callback) => { + (details, callback) => { callback({ cancel: false }); } + ); + }).to.not.throw(); + }); + + it('Requests should be intercepted by webRequest module', async () => { + const requestUrl = '/requestUrl'; + const redirectUrl = '/redirectUrl'; + let requestIsRedirected = false; + const serverUrl = await respondOnce.toURL(redirectUrl, (request, response) => { + requestIsRedirected = true; + response.end(); + }); + let requestIsIntercepted = false; + session.defaultSession.webRequest.onBeforeRequest( + (details, callback) => { if (details.url === `${serverUrl}${requestUrl}`) { - requestIsIntercepted = true + requestIsIntercepted = true; // Disabled due to false positive in StandardJS // eslint-disable-next-line standard/no-callback-literal callback({ redirectURL: `${serverUrl}${redirectUrl}` - }) + }); } else { callback({ cancel: false - }) + }); } - }) - - const urlRequest = net.request({ - url: `${serverUrl}${requestUrl}`, - session: customSession - }) - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - response.on('data', () => {}) - response.on('end', () => { - expect(requestIsRedirected).to.be.true('The server should receive a request to the forward URL') - expect(requestIsIntercepted).to.be.true('The request should be intercepted by the webRequest module') - done() - }) - }) - urlRequest.end() - }) - }) + }); - it('should to able to create and intercept a request using a custom partition name', (done) => { - const requestUrl = '/requestUrl' - const redirectUrl = '/redirectUrl' - const customPartitionName = 'custom-partition' - let requestIsRedirected = false - respondOnce.toURL(redirectUrl, (request, response) => { - requestIsRedirected = true - response.end() - }).then(serverUrl => { - session.defaultSession.webRequest.onBeforeRequest(() => { - expect.fail('Request should not be intercepted by the default session') - }) + const urlRequest = net.request(`${serverUrl}${requestUrl}`); + const response = await getResponse(urlRequest); + + expect(response.statusCode).to.equal(200); + await collectStreamBody(response); + expect(requestIsRedirected).to.be.true('The server should receive a request to the forward URL'); + expect(requestIsIntercepted).to.be.true('The request should be intercepted by the webRequest module'); + }); + + it('should to able to create and intercept a request using a custom session object', async () => { + const requestUrl = '/requestUrl'; + const redirectUrl = '/redirectUrl'; + const customPartitionName = 'custom-partition'; + let requestIsRedirected = false; + const serverUrl = await respondOnce.toURL(redirectUrl, (request, response) => { + requestIsRedirected = true; + response.end(); + }); + session.defaultSession.webRequest.onBeforeRequest(() => { + expect.fail('Request should not be intercepted by the default session'); + }); + + const customSession = session.fromPartition(customPartitionName, { cache: false }); + let requestIsIntercepted = false; + customSession.webRequest.onBeforeRequest((details, callback) => { + if (details.url === `${serverUrl}${requestUrl}`) { + requestIsIntercepted = true; + // Disabled due to false positive in StandardJS + // eslint-disable-next-line standard/no-callback-literal + callback({ + redirectURL: `${serverUrl}${redirectUrl}` + }); + } else { + callback({ + cancel: false + }); + } + }); - const customSession = session.fromPartition(customPartitionName, { cache: false }) - let requestIsIntercepted = false - customSession.webRequest.onBeforeRequest((details, callback) => { - if (details.url === `${serverUrl}${requestUrl}`) { - requestIsIntercepted = true - // Disabled due to false positive in StandardJS - // eslint-disable-next-line standard/no-callback-literal - callback({ - redirectURL: `${serverUrl}${redirectUrl}` - }) - } else { - callback({ - cancel: false - }) - } - }) + const urlRequest = net.request({ + url: `${serverUrl}${requestUrl}`, + session: customSession + }); + const response = await getResponse(urlRequest); + expect(response.statusCode).to.equal(200); + await collectStreamBody(response); + expect(requestIsRedirected).to.be.true('The server should receive a request to the forward URL'); + expect(requestIsIntercepted).to.be.true('The request should be intercepted by the webRequest module'); + }); + + it('should to able to create and intercept a request using a custom partition name', async () => { + const requestUrl = '/requestUrl'; + const redirectUrl = '/redirectUrl'; + const customPartitionName = 'custom-partition'; + let requestIsRedirected = false; + const serverUrl = await respondOnce.toURL(redirectUrl, (request, response) => { + requestIsRedirected = true; + response.end(); + }); + session.defaultSession.webRequest.onBeforeRequest(() => { + expect.fail('Request should not be intercepted by the default session'); + }); + + const customSession = session.fromPartition(customPartitionName, { cache: false }); + let requestIsIntercepted = false; + customSession.webRequest.onBeforeRequest((details, callback) => { + if (details.url === `${serverUrl}${requestUrl}`) { + requestIsIntercepted = true; + // Disabled due to false positive in StandardJS + // eslint-disable-next-line standard/no-callback-literal + callback({ + redirectURL: `${serverUrl}${redirectUrl}` + }); + } else { + callback({ + cancel: false + }); + } + }); - const urlRequest = net.request({ - url: `${serverUrl}${requestUrl}`, - partition: customPartitionName - }) - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - response.on('data', () => {}) - response.on('end', () => { - expect(requestIsRedirected).to.be.true('The server should receive a request to the forward URL') - expect(requestIsIntercepted).to.be.true('The request should be intercepted by the webRequest module') - done() - }) - }) - urlRequest.end() - }) - }) - }) + const urlRequest = net.request({ + url: `${serverUrl}${requestUrl}`, + partition: customPartitionName + }); + const response = await getResponse(urlRequest); + expect(response.statusCode).to.equal(200); + await collectStreamBody(response); + expect(requestIsRedirected).to.be.true('The server should receive a request to the forward URL'); + expect(requestIsIntercepted).to.be.true('The request should be intercepted by the webRequest module'); + }); + }); it('should throw when calling getHeader without a name', () => { expect(() => { - (net.request({ url: 'https://test' }).getHeader as any)() - }).to.throw(/`name` is required for getHeader\(name\)/) + (net.request({ url: 'https://test' }).getHeader as any)(); + }).to.throw(/`name` is required for getHeader\(name\)/); expect(() => { - net.request({ url: 'https://test' }).getHeader(null as any) - }).to.throw(/`name` is required for getHeader\(name\)/) - }) + net.request({ url: 'https://test' }).getHeader(null as any); + }).to.throw(/`name` is required for getHeader\(name\)/); + }); it('should throw when calling removeHeader without a name', () => { expect(() => { - (net.request({ url: 'https://test' }).removeHeader as any)() - }).to.throw(/`name` is required for removeHeader\(name\)/) + (net.request({ url: 'https://test' }).removeHeader as any)(); + }).to.throw(/`name` is required for removeHeader\(name\)/); expect(() => { - net.request({ url: 'https://test' }).removeHeader(null as any) - }).to.throw(/`name` is required for removeHeader\(name\)/) - }) + net.request({ url: 'https://test' }).removeHeader(null as any); + }).to.throw(/`name` is required for removeHeader\(name\)/); + }); - it('should follow redirect when no redirect handler is provided', (done) => { - const requestUrl = '/302' - respondOnce.toRoutes({ + it('should follow redirect when no redirect handler is provided', async () => { + const requestUrl = '/302'; + const serverUrl = await respondOnce.toRoutes({ '/302': (request, response) => { - response.statusCode = 302 - response.setHeader('Location', '/200') - response.end() + response.statusCode = 302; + response.setHeader('Location', '/200'); + response.end(); }, '/200': (request, response) => { - response.statusCode = 200 - response.end() + response.statusCode = 200; + response.end(); } - }).then(serverUrl => { - const urlRequest = net.request({ - url: `${serverUrl}${requestUrl}` - }) - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - done() - }) - urlRequest.end() - }) - }) + }); + const urlRequest = net.request({ + url: `${serverUrl}${requestUrl}` + }); + const response = await getResponse(urlRequest); + expect(response.statusCode).to.equal(200); + }); - it('should follow redirect chain when no redirect handler is provided', (done) => { - respondOnce.toRoutes({ + it('should follow redirect chain when no redirect handler is provided', async () => { + const serverUrl = await respondOnce.toRoutes({ '/redirectChain': (request, response) => { - response.statusCode = 302 - response.setHeader('Location', '/302') - response.end() + response.statusCode = 302; + response.setHeader('Location', '/302'); + response.end(); }, '/302': (request, response) => { - response.statusCode = 302 - response.setHeader('Location', '/200') - response.end() + response.statusCode = 302; + response.setHeader('Location', '/200'); + response.end(); }, '/200': (request, response) => { - response.statusCode = 200 - response.end() + response.statusCode = 200; + response.end(); } - }).then(serverUrl => { - const urlRequest = net.request({ - url: `${serverUrl}/redirectChain` - }) - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - done() - }) - urlRequest.end() - }) - }) + }); + const urlRequest = net.request({ + url: `${serverUrl}/redirectChain` + }); + const response = await getResponse(urlRequest); + expect(response.statusCode).to.equal(200); + }); it('should not follow redirect when request is canceled in redirect handler', async () => { const serverUrl = await respondOnce.toSingleURL((request, response) => { - response.statusCode = 302 - response.setHeader('Location', '/200') - response.end() - }) + response.statusCode = 302; + response.setHeader('Location', '/200'); + response.end(); + }); const urlRequest = net.request({ url: serverUrl - }) - urlRequest.end() - urlRequest.on('redirect', () => { urlRequest.abort() }) - urlRequest.on('error', () => {}) - await emittedOnce(urlRequest, 'abort') - }) + }); + urlRequest.end(); + urlRequest.on('redirect', () => { urlRequest.abort(); }); + urlRequest.on('error', () => {}); + await emittedOnce(urlRequest, 'abort'); + }); it('should not follow redirect when mode is error', async () => { const serverUrl = await respondOnce.toSingleURL((request, response) => { - response.statusCode = 302 - response.setHeader('Location', '/200') - response.end() - }) + response.statusCode = 302; + response.setHeader('Location', '/200'); + response.end(); + }); const urlRequest = net.request({ url: serverUrl, redirect: 'error' - }) - urlRequest.end() - await emittedOnce(urlRequest, 'error') - }) + }); + urlRequest.end(); + await emittedOnce(urlRequest, 'error'); + }); it('should follow redirect when handler calls callback', async () => { const serverUrl = await respondOnce.toRoutes({ '/redirectChain': (request, response) => { - response.statusCode = 302 - response.setHeader('Location', '/302') - response.end() + response.statusCode = 302; + response.setHeader('Location', '/302'); + response.end(); }, '/302': (request, response) => { - response.statusCode = 302 - response.setHeader('Location', '/200') - response.end() + response.statusCode = 302; + response.setHeader('Location', '/200'); + response.end(); }, '/200': (request, response) => { - response.statusCode = 200 - response.end() + response.statusCode = 200; + response.end(); } - }) - const urlRequest = net.request({ url: `${serverUrl}/redirectChain`, redirect: 'manual' }) - const redirects: string[] = [] + }); + const urlRequest = net.request({ url: `${serverUrl}/redirectChain`, redirect: 'manual' }); + const redirects: string[] = []; urlRequest.on('redirect', (status, method, url) => { - redirects.push(url) - urlRequest.followRedirect() - }) - urlRequest.end() - const [response] = await emittedOnce(urlRequest, 'response') - expect(response.statusCode).to.equal(200) + redirects.push(url); + urlRequest.followRedirect(); + }); + const response = await getResponse(urlRequest); + expect(response.statusCode).to.equal(200); expect(redirects).to.deep.equal([ `${serverUrl}/302`, `${serverUrl}/200` - ]) - }) + ]); + }); it('should throw if given an invalid session option', () => { expect(() => { net.request({ url: 'https://foo', session: 1 as any - }) - }).to.throw('`session` should be an instance of the Session class') - }) + }); + }).to.throw('`session` should be an instance of the Session class'); + }); it('should throw if given an invalid partition option', () => { expect(() => { net.request({ url: 'https://foo', partition: 1 as any - }) - }).to.throw('`partition` should be a string') - }) - - it('should be able to create a request with options', (done) => { - const customHeaderName = 'Some-Custom-Header-Name' - const customHeaderValue = 'Some-Customer-Header-Value' - respondOnce.toURL('/', (request, response) => { - try { - expect(request.method).to.equal('GET') - expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue) - } catch (e) { - return done(e) - } - response.statusCode = 200 - response.statusMessage = 'OK' - response.end() - }).then(serverUrlUnparsed => { - const serverUrl = url.parse(serverUrlUnparsed) - const options = { - port: serverUrl.port ? parseInt(serverUrl.port, 10) : undefined, - hostname: '127.0.0.1', - headers: { [customHeaderName]: customHeaderValue } - } - const urlRequest = net.request(options) - urlRequest.on('response', (response) => { - expect(response.statusCode).to.be.equal(200) - response.on('data', () => {}) - response.on('end', () => { - done() - }) - }) - urlRequest.end() - }) - }) - - it('should be able to pipe a readable stream into a net request', (done) => { - const bodyData = randomString(kOneMegaByte) - let netRequestReceived = false - let netRequestEnded = false - - Promise.all([ + }); + }).to.throw('`partition` should be a string'); + }); + + it('should be able to create a request with options', async () => { + const customHeaderName = 'Some-Custom-Header-Name'; + const customHeaderValue = 'Some-Customer-Header-Value'; + const serverUrlUnparsed = await respondOnce.toURL('/', (request, response) => { + expect(request.method).to.equal('GET'); + expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue); + response.statusCode = 200; + response.statusMessage = 'OK'; + response.end(); + }); + const serverUrl = url.parse(serverUrlUnparsed); + const options = { + port: serverUrl.port ? parseInt(serverUrl.port, 10) : undefined, + hostname: '127.0.0.1', + headers: { [customHeaderName]: customHeaderValue } + }; + const urlRequest = net.request(options); + const response = await getResponse(urlRequest); + expect(response.statusCode).to.be.equal(200); + await collectStreamBody(response); + }); + + it('should be able to pipe a readable stream into a net request', async () => { + const bodyData = randomString(kOneMegaByte); + let netRequestReceived = false; + let netRequestEnded = false; + + const [nodeServerUrl, netServerUrl] = await Promise.all([ respondOnce.toSingleURL((request, response) => response.end(bodyData)), respondOnce.toSingleURL((request, response) => { - netRequestReceived = true - let receivedBodyData = '' + netRequestReceived = true; + let receivedBodyData = ''; request.on('data', (chunk) => { - receivedBodyData += chunk.toString() - }) + receivedBodyData += chunk.toString(); + }); request.on('end', (chunk: Buffer | undefined) => { - netRequestEnded = true + netRequestEnded = true; if (chunk) { - receivedBodyData += chunk.toString() + receivedBodyData += chunk.toString(); } - expect(receivedBodyData).to.be.equal(bodyData) - response.end() - }) - }) - ]).then(([nodeServerUrl, netServerUrl]) => { - const nodeRequest = http.request(nodeServerUrl) - nodeRequest.on('response', (nodeResponse) => { - const netRequest = net.request(netServerUrl) - netRequest.on('response', (netResponse) => { - expect(netResponse.statusCode).to.equal(200) - netResponse.on('data', () => {}) - netResponse.on('end', () => { - expect(netRequestReceived).to.be.true('net request received') - expect(netRequestEnded).to.be.true('net request ended') - done() - }) - }) - nodeResponse.pipe(netRequest) + expect(receivedBodyData).to.be.equal(bodyData); + response.end(); + }); }) - nodeRequest.end() - }) - }) + ]); + const nodeRequest = http.request(nodeServerUrl); + const nodeResponse = await getResponse(nodeRequest as any) as any as http.ServerResponse; + const netRequest = net.request(netServerUrl); + const responsePromise = emittedOnce(netRequest, 'response'); + // TODO(@MarshallOfSound) - FIXME with #22730 + nodeResponse.pipe(netRequest as any); + const [netResponse] = await responsePromise; + expect(netResponse.statusCode).to.equal(200); + await collectStreamBody(netResponse); + expect(netRequestReceived).to.be.true('net request received'); + expect(netRequestEnded).to.be.true('net request ended'); + }); it('should report upload progress', async () => { const serverUrl = await respondOnce.toSingleURL((request, response) => { - response.end() - }) - const netRequest = net.request({ url: serverUrl, method: 'POST' }) - expect(netRequest.getUploadProgress()).to.deep.equal({ active: false }) - netRequest.end(Buffer.from('hello')) - const [position, total] = await emittedOnce(netRequest, 'upload-progress') - expect(netRequest.getUploadProgress()).to.deep.equal({ active: true, started: true, current: position, total }) - }) + response.end(); + }); + const netRequest = net.request({ url: serverUrl, method: 'POST' }); + expect(netRequest.getUploadProgress()).to.deep.equal({ active: false }); + netRequest.end(Buffer.from('hello')); + const [position, total] = await emittedOnce(netRequest, 'upload-progress'); + expect(netRequest.getUploadProgress()).to.deep.equal({ active: true, started: true, current: position, total }); + }); it('should emit error event on server socket destroy', async () => { const serverUrl = await respondOnce.toSingleURL((request) => { - request.socket.destroy() - }) - const urlRequest = net.request(serverUrl) - urlRequest.end() - const [error] = await emittedOnce(urlRequest, 'error') - expect(error.message).to.equal('net::ERR_EMPTY_RESPONSE') - }) + request.socket.destroy(); + }); + const urlRequest = net.request(serverUrl); + urlRequest.end(); + const [error] = await emittedOnce(urlRequest, 'error'); + expect(error.message).to.equal('net::ERR_EMPTY_RESPONSE'); + }); it('should emit error event on server request destroy', async () => { const serverUrl = await respondOnce.toSingleURL((request, response) => { - request.destroy() - response.end() - }) - const urlRequest = net.request(serverUrl) - urlRequest.end(randomBuffer(kOneMegaByte)) - const [error] = await emittedOnce(urlRequest, 'error') - expect(error.message).to.be.oneOf(['net::ERR_CONNECTION_RESET', 'net::ERR_CONNECTION_ABORTED']) - }) + request.destroy(); + response.end(); + }); + const urlRequest = net.request(serverUrl); + urlRequest.end(randomBuffer(kOneMegaByte)); + const [error] = await emittedOnce(urlRequest, 'error'); + expect(error.message).to.be.oneOf(['net::ERR_CONNECTION_RESET', 'net::ERR_CONNECTION_ABORTED']); + }); it('should not emit any event after close', async () => { const serverUrl = await respondOnce.toSingleURL((request, response) => { - response.end() - }) + response.end(); + }); - const urlRequest = net.request(serverUrl) - urlRequest.end() + const urlRequest = net.request(serverUrl); + urlRequest.end(); - await emittedOnce(urlRequest, 'close') + await emittedOnce(urlRequest, 'close'); await new Promise((resolve, reject) => { ['finish', 'abort', 'close', 'error'].forEach(evName => { urlRequest.on(evName as any, () => { - reject(new Error(`Unexpected ${evName} event`)) - }) - }) - setTimeout(resolve, 50) - }) - }) - }) + reject(new Error(`Unexpected ${evName} event`)); + }); + }); + setTimeout(resolve, 50); + }); + }); + }); describe('IncomingMessage API', () => { it('response object should implement the IncomingMessage API', async () => { - const customHeaderName = 'Some-Custom-Header-Name' - const customHeaderValue = 'Some-Customer-Header-Value' + const customHeaderName = 'Some-Custom-Header-Name'; + const customHeaderValue = 'Some-Customer-Header-Value'; const serverUrl = await respondOnce.toSingleURL((request, response) => { - response.statusCode = 200 - response.statusMessage = 'OK' - response.setHeader(customHeaderName, customHeaderValue) - response.end() - }) + response.statusCode = 200; + response.statusMessage = 'OK'; + response.setHeader(customHeaderName, customHeaderValue); + response.end(); + }); - const urlRequest = net.request(serverUrl) - urlRequest.end() - const [response] = await emittedOnce(urlRequest, 'response') + const urlRequest = net.request(serverUrl); + const response = await getResponse(urlRequest); - expect(response.statusCode).to.equal(200) - expect(response.statusMessage).to.equal('OK') + expect(response.statusCode).to.equal(200); + expect(response.statusMessage).to.equal('OK'); - const headers = response.headers - expect(headers).to.be.an('object') - const headerValue = headers[customHeaderName.toLowerCase()] - expect(headerValue).to.equal(customHeaderValue) + const headers = response.headers; + expect(headers).to.be.an('object'); + const headerValue = headers[customHeaderName.toLowerCase()]; + expect(headerValue).to.equal(customHeaderValue); - const httpVersion = response.httpVersion - expect(httpVersion).to.be.a('string').and.to.have.lengthOf.at.least(1) + const httpVersion = response.httpVersion; + expect(httpVersion).to.be.a('string').and.to.have.lengthOf.at.least(1); - const httpVersionMajor = response.httpVersionMajor - expect(httpVersionMajor).to.be.a('number').and.to.be.at.least(1) + const httpVersionMajor = response.httpVersionMajor; + expect(httpVersionMajor).to.be.a('number').and.to.be.at.least(1); - const httpVersionMinor = response.httpVersionMinor - expect(httpVersionMinor).to.be.a('number').and.to.be.at.least(0) + const httpVersionMinor = response.httpVersionMinor; + expect(httpVersionMinor).to.be.a('number').and.to.be.at.least(0); - response.on('data', () => {}) - await emittedOnce(response, 'end') - }) + await collectStreamBody(response); + }); it('should discard duplicate headers', async () => { - const includedHeader = 'max-forwards' - const discardableHeader = 'Max-Forwards' + const includedHeader = 'max-forwards'; + const discardableHeader = 'Max-Forwards'; - const includedHeaderValue = 'max-fwds-val' - const discardableHeaderValue = 'max-fwds-val-two' + const includedHeaderValue = 'max-fwds-val'; + const discardableHeaderValue = 'max-fwds-val-two'; const serverUrl = await respondOnce.toSingleURL((request, response) => { - response.statusCode = 200 - response.statusMessage = 'OK' - response.setHeader(discardableHeader, discardableHeaderValue) - response.setHeader(includedHeader, includedHeaderValue) - response.end() - }) - const urlRequest = net.request(serverUrl) - urlRequest.end() - - const [response] = await emittedOnce(urlRequest, 'response') - expect(response.statusCode).to.equal(200) - expect(response.statusMessage).to.equal('OK') - - const headers = response.headers - expect(headers).to.be.an('object') - - expect(headers).to.have.property(includedHeader) - expect(headers).to.not.have.property(discardableHeader) - expect(headers[includedHeader]).to.equal(includedHeaderValue) - - response.on('data', () => {}) - await emittedOnce(response, 'end') - }) + response.statusCode = 200; + response.statusMessage = 'OK'; + response.setHeader(discardableHeader, discardableHeaderValue); + response.setHeader(includedHeader, includedHeaderValue); + response.end(); + }); + const urlRequest = net.request(serverUrl); + const response = await getResponse(urlRequest); + expect(response.statusCode).to.equal(200); + expect(response.statusMessage).to.equal('OK'); + + const headers = response.headers; + expect(headers).to.be.an('object'); + + expect(headers).to.have.property(includedHeader); + expect(headers).to.not.have.property(discardableHeader); + expect(headers[includedHeader]).to.equal(includedHeaderValue); + + await collectStreamBody(response); + }); it('should join repeated non-discardable value with ,', async () => { const serverUrl = await respondOnce.toSingleURL((request, response) => { - response.statusCode = 200 - response.statusMessage = 'OK' - response.setHeader('referrer-policy', ['first-text', 'second-text']) - response.end() - }) - const urlRequest = net.request(serverUrl) - urlRequest.end() - - const [response] = await emittedOnce(urlRequest, 'response') - expect(response.statusCode).to.equal(200) - expect(response.statusMessage).to.equal('OK') - - const headers = response.headers - expect(headers).to.be.an('object') - expect(headers).to.have.property('referrer-policy') - expect(headers['referrer-policy']).to.equal('first-text, second-text') - - response.on('data', () => {}) - await emittedOnce(response, 'end') - }) + response.statusCode = 200; + response.statusMessage = 'OK'; + response.setHeader('referrer-policy', ['first-text', 'second-text']); + response.end(); + }); + const urlRequest = net.request(serverUrl); + const response = await getResponse(urlRequest); + expect(response.statusCode).to.equal(200); + expect(response.statusMessage).to.equal('OK'); + + const headers = response.headers; + expect(headers).to.be.an('object'); + expect(headers).to.have.property('referrer-policy'); + expect(headers['referrer-policy']).to.equal('first-text, second-text'); + + await collectStreamBody(response); + }); it('should be able to pipe a net response into a writable stream', async () => { - const bodyData = randomString(kOneKiloByte) + const bodyData = randomString(kOneKiloByte); + let nodeRequestProcessed = false; const [netServerUrl, nodeServerUrl] = await Promise.all([ respondOnce.toSingleURL((request, response) => response.end(bodyData)), - respondOnce.toSingleURL((request, response) => { - let receivedBodyData = '' - request.on('data', (chunk) => { - receivedBodyData += chunk.toString() - }) - request.on('end', (chunk: Buffer | undefined) => { - if (chunk) { - receivedBodyData += chunk.toString() - } - expect(receivedBodyData).to.be.equal(bodyData) - response.end() - }) - }) - ]) - const netRequest = net.request(netServerUrl) - netRequest.end() - await new Promise((resolve) => { - netRequest.on('response', (netResponse) => { - const serverUrl = url.parse(nodeServerUrl) - const nodeOptions = { - method: 'POST', - path: serverUrl.path, - port: serverUrl.port - } - const nodeRequest = http.request(nodeOptions, res => { - res.on('data', () => {}) - res.on('end', () => { - resolve() - }) - }); - // TODO: IncomingMessage should properly extend ReadableStream in the - // docs - (netResponse as any).pipe(nodeRequest) - }) - netRequest.end() - }) - }) - }) + respondOnce.toSingleURL(async (request, response) => { + const receivedBodyData = await collectStreamBody(request); + expect(receivedBodyData).to.be.equal(bodyData); + nodeRequestProcessed = true; + response.end(); + }) + ]); + const netRequest = net.request(netServerUrl); + const netResponse = await getResponse(netRequest); + const serverUrl = url.parse(nodeServerUrl); + const nodeOptions = { + method: 'POST', + path: serverUrl.path, + port: serverUrl.port + }; + const nodeRequest = http.request(nodeOptions); + const nodeResponsePromise = emittedOnce(nodeRequest, 'response'); + // TODO(@MarshallOfSound) - FIXME with #22730 + (netResponse as any).pipe(nodeRequest); + const [nodeResponse] = await nodeResponsePromise; + netRequest.end(); + await collectStreamBody(nodeResponse); + expect(nodeRequestProcessed).to.equal(true); + }); + }); describe('Stability and performance', () => { it('should free unreferenced, never-started request objects without crash', (done) => { - net.request('https://test') + net.request('https://test'); process.nextTick(() => { - const v8Util = process.electronBinding('v8_util') - v8Util.requestGarbageCollectionForTesting() - done() - }) - }) - - it('should collect on-going requests without crash', (done) => { - let finishResponse: (() => void) | null = null - respondOnce.toSingleURL((request, response) => { - response.write(randomString(kOneKiloByte)) + const v8Util = process.electronBinding('v8_util'); + v8Util.requestGarbageCollectionForTesting(); + done(); + }); + }); + + it('should collect on-going requests without crash', async () => { + let finishResponse: (() => void) | null = null; + const serverUrl = await respondOnce.toSingleURL((request, response) => { + response.write(randomString(kOneKiloByte)); finishResponse = () => { - response.write(randomString(kOneKiloByte)) - response.end() - } - }).then(serverUrl => { - const urlRequest = net.request(serverUrl) - urlRequest.on('response', (response) => { - response.on('data', () => { }) - response.on('end', () => { - done() - }) - process.nextTick(() => { - // Trigger a garbage collection. - const v8Util = process.electronBinding('v8_util') - v8Util.requestGarbageCollectionForTesting() - finishResponse!() - }) - }) - urlRequest.end() - }) - }) - - it('should collect unreferenced, ended requests without crash', (done) => { - respondOnce.toSingleURL((request, response) => { - response.end() - }).then(serverUrl => { - const urlRequest = net.request(serverUrl) - urlRequest.on('response', (response) => { - response.on('data', () => {}) - response.on('end', () => { done() }) - }) + response.write(randomString(kOneKiloByte)); + response.end(); + }; + }); + const urlRequest = net.request(serverUrl); + const response = await getResponse(urlRequest); + process.nextTick(() => { + // Trigger a garbage collection. + const v8Util = process.electronBinding('v8_util'); + v8Util.requestGarbageCollectionForTesting(); + finishResponse!(); + }); + await collectStreamBody(response); + }); + + it('should collect unreferenced, ended requests without crash', async () => { + const serverUrl = await respondOnce.toSingleURL((request, response) => { + response.end(); + }); + const urlRequest = net.request(serverUrl); + process.nextTick(() => { + const v8Util = process.electronBinding('v8_util'); + v8Util.requestGarbageCollectionForTesting(); + }); + const response = await getResponse(urlRequest); + await collectStreamBody(response); + }); + + it('should finish sending data when urlRequest is unreferenced', async () => { + const serverUrl = await respondOnce.toSingleURL(async (request, response) => { + const received = await collectStreamBodyBuffer(request); + expect(received.length).to.equal(kOneMegaByte); + response.end(); + }); + const urlRequest = net.request(serverUrl); + urlRequest.on('close', () => { process.nextTick(() => { - const v8Util = process.electronBinding('v8_util') - v8Util.requestGarbageCollectionForTesting() - }) - urlRequest.end() - }) - }) - - it('should finish sending data when urlRequest is unreferenced', (done) => { - respondOnce.toSingleURL((request, response) => { - let received = Buffer.alloc(0) - request.on('data', (data) => { - received = Buffer.concat([received, data]) - }) - request.on('end', () => { - response.end() - expect(received.length).to.equal(kOneMegaByte) - done() - }) - }).then(serverUrl => { - const urlRequest = net.request(serverUrl) - urlRequest.on('response', (response) => { - response.on('data', () => {}) - response.on('end', () => {}) - }) - urlRequest.on('close', () => { - process.nextTick(() => { - const v8Util = process.electronBinding('v8_util') - v8Util.requestGarbageCollectionForTesting() - }) - }) - urlRequest.end(randomBuffer(kOneMegaByte)) - }) - }) - - it('should finish sending data when urlRequest is unreferenced for chunked encoding', (done) => { - respondOnce.toSingleURL((request, response) => { - let received = Buffer.alloc(0) - request.on('data', (data) => { - received = Buffer.concat([received, data]) - }) - request.on('end', () => { - response.end() - expect(received.length).to.equal(kOneMegaByte) - done() - }) - }).then(serverUrl => { - const urlRequest = net.request(serverUrl) - urlRequest.on('response', (response) => { - response.on('data', () => {}) - response.on('end', () => {}) - }) - urlRequest.on('close', () => { - process.nextTick(() => { - const v8Util = process.electronBinding('v8_util') - v8Util.requestGarbageCollectionForTesting() - }) - }) - urlRequest.chunkedEncoding = true - urlRequest.end(randomBuffer(kOneMegaByte)) - }) - }) - - it('should finish sending data when urlRequest is unreferenced before close event for chunked encoding', (done) => { - respondOnce.toSingleURL((request, response) => { - let received = Buffer.alloc(0) - request.on('data', (data) => { - received = Buffer.concat([received, data]) - }) - request.on('end', () => { - response.end() - expect(received.length).to.equal(kOneMegaByte) - done() - }) - }).then(serverUrl => { - const urlRequest = net.request(serverUrl) - urlRequest.on('response', (response) => { - response.on('data', () => {}) - response.on('end', () => {}) - }) - urlRequest.chunkedEncoding = true - urlRequest.end(randomBuffer(kOneMegaByte)) - const v8Util = process.electronBinding('v8_util') - v8Util.requestGarbageCollectionForTesting() - }) - }) - - it('should finish sending data when urlRequest is unreferenced', (done) => { - respondOnce.toSingleURL((request, response) => { - let received = Buffer.alloc(0) - request.on('data', (data) => { - received = Buffer.concat([received, data]) - }) - request.on('end', () => { - response.end() - expect(received.length).to.equal(kOneMegaByte) - done() - }) - }).then(serverUrl => { - const urlRequest = net.request(serverUrl) - urlRequest.on('response', (response) => { - response.on('data', () => {}) - response.on('end', () => {}) - }) - urlRequest.on('close', () => { - process.nextTick(() => { - const v8Util = process.electronBinding('v8_util') - v8Util.requestGarbageCollectionForTesting() - }) - }) - urlRequest.end(randomBuffer(kOneMegaByte)) - }) - }) - - it('should finish sending data when urlRequest is unreferenced for chunked encoding', (done) => { - respondOnce.toSingleURL((request, response) => { - let received = Buffer.alloc(0) - request.on('data', (data) => { - received = Buffer.concat([received, data]) - }) - request.on('end', () => { - response.end() - expect(received.length).to.equal(kOneMegaByte) - done() - }) - }).then(serverUrl => { - const urlRequest = net.request(serverUrl) - urlRequest.on('response', (response) => { - response.on('data', () => {}) - response.on('end', () => {}) - }) - urlRequest.on('close', () => { - process.nextTick(() => { - const v8Util = process.electronBinding('v8_util') - v8Util.requestGarbageCollectionForTesting() - }) - }) - urlRequest.chunkedEncoding = true - urlRequest.end(randomBuffer(kOneMegaByte)) - }) - }) - }) -}) + const v8Util = process.electronBinding('v8_util'); + v8Util.requestGarbageCollectionForTesting(); + }); + }); + urlRequest.write(randomBuffer(kOneMegaByte)); + const response = await getResponse(urlRequest); + await collectStreamBody(response); + }); + + it('should finish sending data when urlRequest is unreferenced for chunked encoding', async () => { + const serverUrl = await respondOnce.toSingleURL(async (request, response) => { + const received = await collectStreamBodyBuffer(request); + response.end(); + expect(received.length).to.equal(kOneMegaByte); + }); + const urlRequest = net.request(serverUrl); + urlRequest.chunkedEncoding = true; + urlRequest.write(randomBuffer(kOneMegaByte)); + const response = await getResponse(urlRequest); + await collectStreamBody(response); + process.nextTick(() => { + const v8Util = process.electronBinding('v8_util'); + v8Util.requestGarbageCollectionForTesting(); + }); + }); + + it('should finish sending data when urlRequest is unreferenced before close event for chunked encoding', async () => { + const serverUrl = await respondOnce.toSingleURL(async (request, response) => { + const received = await collectStreamBodyBuffer(request); + response.end(); + expect(received.length).to.equal(kOneMegaByte); + }); + const urlRequest = net.request(serverUrl); + urlRequest.chunkedEncoding = true; + urlRequest.write(randomBuffer(kOneMegaByte)); + const v8Util = process.electronBinding('v8_util'); + v8Util.requestGarbageCollectionForTesting(); + await collectStreamBody(await getResponse(urlRequest)); + }); + + it('should finish sending data when urlRequest is unreferenced', async () => { + const serverUrl = await respondOnce.toSingleURL(async (request, response) => { + const received = await collectStreamBodyBuffer(request); + response.end(); + expect(received.length).to.equal(kOneMegaByte); + }); + const urlRequest = net.request(serverUrl); + urlRequest.on('close', () => { + process.nextTick(() => { + const v8Util = process.electronBinding('v8_util'); + v8Util.requestGarbageCollectionForTesting(); + }); + }); + urlRequest.write(randomBuffer(kOneMegaByte)); + await collectStreamBody(await getResponse(urlRequest)); + }); + + it('should finish sending data when urlRequest is unreferenced for chunked encoding', async () => { + const serverUrl = await respondOnce.toSingleURL(async (request, response) => { + const received = await collectStreamBodyBuffer(request); + response.end(); + expect(received.length).to.equal(kOneMegaByte); + }); + const urlRequest = net.request(serverUrl); + urlRequest.on('close', () => { + process.nextTick(() => { + const v8Util = process.electronBinding('v8_util'); + v8Util.requestGarbageCollectionForTesting(); + }); + }); + urlRequest.chunkedEncoding = true; + urlRequest.write(randomBuffer(kOneMegaByte)); + await collectStreamBody(await getResponse(urlRequest)); + }); + }); +}); diff --git a/spec-main/extensions-spec.ts b/spec-main/extensions-spec.ts index f18ec31f658fa..00cb5666762f1 100644 --- a/spec-main/extensions-spec.ts +++ b/spec-main/extensions-spec.ts @@ -31,12 +31,34 @@ ifdescribe(process.electronBinding('features').isExtensionsEnabled())('chrome ex // extension in an in-memory session results in it being installed in the // default session. const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); +<<<<<<< HEAD (customSession as any).loadChromeExtension(path.join(fixtures, 'extensions', 'red-bg')) const w = new BrowserWindow({show: false, webPreferences: {session: customSession}}) await w.loadURL(url) const bg = await w.webContents.executeJavaScript('document.documentElement.style.backgroundColor') expect(bg).to.equal('red') }) +======= + await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); + const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } }); + await w.loadURL(url); + const bg = await w.webContents.executeJavaScript('document.documentElement.style.backgroundColor'); + expect(bg).to.equal('red'); + }); + + it('serializes a loaded extension', async () => { + const extensionPath = path.join(fixtures, 'extensions', 'red-bg'); + const manifest = JSON.parse(fs.readFileSync(path.join(extensionPath, 'manifest.json'), 'utf-8')); + const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); + const extension = await customSession.loadExtension(extensionPath); + expect(extension.id).to.be.a('string'); + expect(extension.name).to.be.a('string'); + expect(extension.path).to.be.a('string'); + expect(extension.version).to.be.a('string'); + expect(extension.url).to.be.a('string'); + expect(extension.manifest).to.deep.equal(manifest); + }); +>>>>>>> e766031f3... chore: refactor all the net specs to be async with better error handling (#22731) it('confines an extension to the session it was loaded in', async () => { const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); From a70a13e88d9b298c5667f5ba3f2583e88e39ec26 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Wed, 18 Mar 2020 16:35:23 -0700 Subject: [PATCH 2/8] chore: fix net specs when rerunning locally (#22745) --- spec-main/api-net-spec.ts | 62 +++------------------------------------ 1 file changed, 4 insertions(+), 58 deletions(-) diff --git a/spec-main/api-net-spec.ts b/spec-main/api-net-spec.ts index b923aa9892d0c..6cb510807d6be 100644 --- a/spec-main/api-net-spec.ts +++ b/spec-main/api-net-spec.ts @@ -60,8 +60,10 @@ function respondNTimes (fn: http.RequestListener, n: number): Promise { const server = http.createServer((request, response) => { fn(request, response); // don't close if a redirect was returned - n--; - if ((response.statusCode < 300 || response.statusCode >= 399) && n <= 0) { server.close(); } + if ((response.statusCode < 300 || response.statusCode >= 399) && n <= 0) { + n--; + server.close(); + } }); server.listen(0, '127.0.0.1', () => { resolve(`http://127.0.0.1:${(server.address() as AddressInfo).port}`); @@ -193,41 +195,6 @@ describe('net module', () => { response.write(chunk); }); request.on('end', (chunk: Buffer) => { -<<<<<<< HEAD - response.end(chunk) - }) - }).then(serverUrl => { - const urlRequest = net.request({ - method: 'POST', - url: serverUrl - }) - - let chunkIndex = 0 - const chunkCount = 100 - let sent = Buffer.alloc(0) - let received = Buffer.alloc(0) - urlRequest.on('response', (response) => { - expect(response.statusCode).to.equal(200) - response.on('data', (chunk) => { - received = Buffer.concat([received, chunk]) - }) - response.on('end', () => { - expect(sent.equals(received)).to.be.true('sent equals received') - expect(chunkIndex).to.be.equal(chunkCount) - done() - }) - }) - urlRequest.chunkedEncoding = true - while (chunkIndex < chunkCount) { - chunkIndex += 1 - const chunk = randomBuffer(kOneKiloByte) - sent = Buffer.concat([sent, chunk]) - urlRequest.write(chunk) - } - urlRequest.end() - }) - }) -======= response.end(chunk); }); }); @@ -254,7 +221,6 @@ describe('net module', () => { expect(sent.equals(received)).to.be.true(); expect(chunkIndex).to.be.equal(chunkCount); }); ->>>>>>> 54e6492e2... chore: refactor all the net specs to be async with better error handling (#22731) it('should emit the login event when 401', async () => { const [user, pass] = ['user', 'pass']; @@ -320,25 +286,6 @@ describe('net module', () => { }); it('should share proxy credentials with WebContents', async () => { -<<<<<<< HEAD - const [user, pass] = ['user', 'pass'] - const proxyPort = await new Promise((resolve) => { - const server = http.createServer((request, response) => { - if (!request.headers['proxy-authorization']) { - return response.writeHead(407, { 'Proxy-Authenticate': 'Basic realm="Foo"' }).end() - } - return response.writeHead(200).end('ok') - }) - server.listen(0, '127.0.0.1', () => { - resolve((server.address() as AddressInfo).port) - }) - after(() => { server.close() }) - }) - const customSession = session.fromPartition(`net-proxy-test-${Math.random()}`) - await customSession.setProxy({ proxyRules: `127.0.0.1:${proxyPort}`, proxyBypassRules: '<-loopback>' } as any) - const bw = new BrowserWindow({ show: false, webPreferences: { session: customSession } }) - const loaded = bw.loadURL('http://127.0.0.1:9999') -======= const [user, pass] = ['user', 'pass']; const proxyUrl = await respondNTimes((request, response) => { if (!request.headers['proxy-authorization']) { @@ -349,7 +296,6 @@ describe('net module', () => { const customSession = session.fromPartition(`net-proxy-test-${Math.random()}`); await customSession.setProxy({ proxyRules: proxyUrl.replace('http://', ''), proxyBypassRules: '<-loopback>' }); const bw = new BrowserWindow({ show: false, webPreferences: { session: customSession } }); ->>>>>>> 54e6492e2... chore: refactor all the net specs to be async with better error handling (#22731) bw.webContents.on('login', (event, details, authInfo, cb) => { event.preventDefault(); cb(user, pass); From 0a09184d73afb9577ed95b4eb5d5ac04ce4d03fa Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 20 Mar 2020 15:56:02 -0700 Subject: [PATCH 3/8] feat: add support for net requests to use the session cookie store (#22704) --- docs/api/client-request.md | 3 + lib/browser/api/net.js | 3 +- shell/browser/api/atom_api_url_loader.cc | 7 ++ spec-main/api-net-spec.ts | 125 +++++++++++++++++++++++ 4 files changed, 137 insertions(+), 1 deletion(-) diff --git a/docs/api/client-request.md b/docs/api/client-request.md index 262b24ab30d56..fa260696e47bf 100644 --- a/docs/api/client-request.md +++ b/docs/api/client-request.md @@ -22,6 +22,9 @@ which the request is associated. with which the request is associated. Defaults to the empty string. The `session` option prevails on `partition`. Thus if a `session` is explicitly specified, `partition` is ignored. + * `useSessionCookies` Boolean (optional) - Whether to send cookies with this + request from the provided session. This will make the `net` request's + cookie behavior match a `fetch` request. Default is `false`. * `protocol` String (optional) - The protocol scheme in the form 'scheme:'. Currently supported values are 'http:' or 'https:'. Defaults to 'http:'. * `host` String (optional) - The server host provided as a concatenation of diff --git a/lib/browser/api/net.js b/lib/browser/api/net.js index 9e20b0dc3311c..f5adadd9c7c07 100644 --- a/lib/browser/api/net.js +++ b/lib/browser/api/net.js @@ -234,7 +234,8 @@ function parseOptions (options) { method: method, url: urlStr, redirectPolicy, - extraHeaders: options.headers || {} + extraHeaders: options.headers || {}, + useSessionCookies: options.useSessionCookies || false }; for (const [name, value] of Object.entries(urlLoaderOptions.extraHeaders)) { if (!_isValidHeaderName(name)) { diff --git a/shell/browser/api/atom_api_url_loader.cc b/shell/browser/api/atom_api_url_loader.cc index fd0a73958b811..b14efd3451730 100644 --- a/shell/browser/api/atom_api_url_loader.cc +++ b/shell/browser/api/atom_api_url_loader.cc @@ -19,6 +19,7 @@ #include "mojo/public/cpp/system/data_pipe_producer.h" #include "native_mate/dictionary.h" #include "native_mate/handle.h" +#include "net/base/load_flags.h" #include "services/network/public/cpp/resource_request.h" #include "services/network/public/cpp/simple_url_loader.h" #include "services/network/public/mojom/chunked_data_pipe_getter.mojom.h" @@ -359,6 +360,12 @@ mate::WrappableBase* SimpleURLLoaderWrapper::New(mate::Arguments* args) { } } + bool use_session_cookies = false; + opts.Get("useSessionCookies", &use_session_cookies); + if (!use_session_cookies) { + request->load_flags |= net::LOAD_DO_NOT_SEND_COOKIES; + } + // Chromium filters headers using browser rules, while for net module we have // every header passed. request->report_raw_headers = true; diff --git a/spec-main/api-net-spec.ts b/spec-main/api-net-spec.ts index 6cb510807d6be..0855aaef6ac69 100644 --- a/spec-main/api-net-spec.ts +++ b/spec-main/api-net-spec.ts @@ -511,6 +511,131 @@ describe('net module', () => { expect(response.headers['set-cookie']).to.have.same.members(cookie); }); + it('should not use the sessions cookie store by default', async () => { + const serverUrl = await respondOnce.toSingleURL((request, response) => { + response.statusCode = 200; + response.statusMessage = 'OK'; + response.setHeader('x-cookie', `${request.headers.cookie!}`); + response.end(); + }); + const sess = session.fromPartition('cookie-tests-1'); + const cookieVal = `${Date.now()}`; + await sess.cookies.set({ + url: serverUrl, + name: 'wild_cookie', + value: cookieVal + }); + const urlRequest = net.request({ + url: serverUrl, + session: sess + }); + const response = await getResponse(urlRequest); + expect(response.headers['x-cookie']).to.equal(`undefined`); + }); + + it('should be able to use the sessions cookie store', async () => { + const serverUrl = await respondOnce.toSingleURL((request, response) => { + response.statusCode = 200; + response.statusMessage = 'OK'; + response.setHeader('x-cookie', request.headers.cookie!); + response.end(); + }); + const sess = session.fromPartition('cookie-tests-2'); + const cookieVal = `${Date.now()}`; + await sess.cookies.set({ + url: serverUrl, + name: 'wild_cookie', + value: cookieVal + }); + const urlRequest = net.request({ + url: serverUrl, + session: sess, + useSessionCookies: true + }); + const response = await getResponse(urlRequest); + expect(response.headers['x-cookie']).to.equal(`wild_cookie=${cookieVal}`); + }); + + it('should be able to use the sessions cookie store with set-cookie', async () => { + const serverUrl = await respondOnce.toSingleURL((request, response) => { + response.statusCode = 200; + response.statusMessage = 'OK'; + response.setHeader('set-cookie', 'foo=bar'); + response.end(); + }); + const sess = session.fromPartition('cookie-tests-3'); + let cookies = await sess.cookies.get({}); + expect(cookies).to.have.lengthOf(0); + const urlRequest = net.request({ + url: serverUrl, + session: sess, + useSessionCookies: true + }); + await collectStreamBody(await getResponse(urlRequest)); + cookies = await sess.cookies.get({}); + expect(cookies).to.have.lengthOf(1); + expect(cookies[0]).to.deep.equal({ + name: 'foo', + value: 'bar', + domain: '127.0.0.1', + hostOnly: true, + path: '/', + secure: false, + httpOnly: false, + session: true + }); + }); + + it('should be able to use the sessions cookie store safely across redirects', async () => { + const serverUrl = await respondOnce.toSingleURL(async (request, response) => { + response.statusCode = 302; + response.statusMessage = 'Moved'; + const newUrl = await respondOnce.toSingleURL((req, res) => { + res.statusCode = 200; + res.statusMessage = 'OK'; + res.setHeader('x-cookie', req.headers.cookie!); + res.end(); + }); + response.setHeader('x-cookie', request.headers.cookie!); + response.setHeader('location', newUrl.replace('127.0.0.1', 'localhost')); + response.end(); + }); + const sess = session.fromPartition('cookie-tests-4'); + const cookie127Val = `${Date.now()}-127`; + const cookieLocalVal = `${Date.now()}-local`; + const localhostUrl = serverUrl.replace('127.0.0.1', 'localhost'); + expect(localhostUrl).to.not.equal(serverUrl); + await Promise.all([ + sess.cookies.set({ + url: serverUrl, + name: 'wild_cookie', + value: cookie127Val + }), sess.cookies.set({ + url: localhostUrl, + name: 'wild_cookie', + value: cookieLocalVal + }) + ]); + const urlRequest = net.request({ + url: serverUrl, + session: sess, + useSessionCookies: true + }); + urlRequest.on('redirect', (status, method, url, headers) => { + // The initial redirect response should have received the 127 value here + expect(headers['x-cookie'][0]).to.equal(`wild_cookie=${cookie127Val}`); + urlRequest.followRedirect(); + }); + const response = await getResponse(urlRequest); + // We expect the server to have received the localhost value here + // The original request was to a 127.0.0.1 URL + // That request would have the cookie127Val cookie attached + // The request is then redirect to a localhost URL (different site) + // Because we are using the session cookie store it should do the safe / secure thing + // and attach the cookies for the new target domain + expect(response.headers['x-cookie']).to.equal(`wild_cookie=${cookieLocalVal}`); + }); + it('should be able to abort an HTTP request before first write', async () => { const serverUrl = await respondOnce.toSingleURL((request, response) => { response.end(); From 671f0d66b62ccd3dcdb0892c79094a8dcbcc0fe9 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Fri, 20 Mar 2020 16:39:03 -0700 Subject: [PATCH 4/8] fix: allow net requests to use Same-Site cookies (#22788) --- shell/browser/api/atom_api_url_loader.cc | 1 + spec-main/api-net-spec.ts | 42 ++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/shell/browser/api/atom_api_url_loader.cc b/shell/browser/api/atom_api_url_loader.cc index b14efd3451730..49495a6162901 100644 --- a/shell/browser/api/atom_api_url_loader.cc +++ b/shell/browser/api/atom_api_url_loader.cc @@ -346,6 +346,7 @@ mate::WrappableBase* SimpleURLLoaderWrapper::New(mate::Arguments* args) { return nullptr; } auto request = std::make_unique(); + request->attach_same_site_cookies = true; opts.Get("method", &request->method); opts.Get("url", &request->url); std::map extra_headers; diff --git a/spec-main/api-net-spec.ts b/spec-main/api-net-spec.ts index 0855aaef6ac69..c7e1ce3ea3148 100644 --- a/spec-main/api-net-spec.ts +++ b/spec-main/api-net-spec.ts @@ -586,6 +586,48 @@ describe('net module', () => { }); }); + ['Lax', 'Strict'].forEach((mode) => { + it(`should be able to use the sessions cookie store with same-site ${mode} cookies`, async () => { + const serverUrl = await respondNTimes.toSingleURL((request, response) => { + response.statusCode = 200; + response.statusMessage = 'OK'; + response.setHeader('set-cookie', `same=site; SameSite=${mode}`); + response.setHeader('x-cookie', `${request.headers.cookie}`); + response.end(); + }, 2); + const sess = session.fromPartition(`cookie-tests-same-site-${mode}`); + let cookies = await sess.cookies.get({}); + expect(cookies).to.have.lengthOf(0); + const urlRequest = net.request({ + url: serverUrl, + session: sess, + useSessionCookies: true + }); + const response = await getResponse(urlRequest); + expect(response.headers['x-cookie']).to.equal('undefined'); + await collectStreamBody(response); + cookies = await sess.cookies.get({}); + expect(cookies).to.have.lengthOf(1); + expect(cookies[0]).to.deep.equal({ + name: 'same', + value: 'site', + domain: '127.0.0.1', + hostOnly: true, + path: '/', + secure: false, + httpOnly: false, + session: true + }); + const urlRequest2 = net.request({ + url: serverUrl, + session: sess, + useSessionCookies: true + }); + const response2 = await getResponse(urlRequest2); + expect(response2.headers['x-cookie']).to.equal('same=site'); + }); + }); + it('should be able to use the sessions cookie store safely across redirects', async () => { const serverUrl = await respondOnce.toSingleURL(async (request, response) => { response.statusCode = 302; From 03c685346c88dd1f2f8050ef685dabf2a3f14130 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 23 Mar 2020 10:17:44 -0700 Subject: [PATCH 5/8] build: fix merge conflict --- spec-main/extensions-spec.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/spec-main/extensions-spec.ts b/spec-main/extensions-spec.ts index 00cb5666762f1..4978a57331778 100644 --- a/spec-main/extensions-spec.ts +++ b/spec-main/extensions-spec.ts @@ -31,14 +31,6 @@ ifdescribe(process.electronBinding('features').isExtensionsEnabled())('chrome ex // extension in an in-memory session results in it being installed in the // default session. const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); -<<<<<<< HEAD - (customSession as any).loadChromeExtension(path.join(fixtures, 'extensions', 'red-bg')) - const w = new BrowserWindow({show: false, webPreferences: {session: customSession}}) - await w.loadURL(url) - const bg = await w.webContents.executeJavaScript('document.documentElement.style.backgroundColor') - expect(bg).to.equal('red') - }) -======= await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } }); await w.loadURL(url); @@ -58,7 +50,6 @@ ifdescribe(process.electronBinding('features').isExtensionsEnabled())('chrome ex expect(extension.url).to.be.a('string'); expect(extension.manifest).to.deep.equal(manifest); }); ->>>>>>> e766031f3... chore: refactor all the net specs to be async with better error handling (#22731) it('confines an extension to the session it was loaded in', async () => { const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); From 368a9edc23f6e9e0abf967543a78d49b353320c2 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 23 Mar 2020 10:52:42 -0700 Subject: [PATCH 6/8] Update extensions-spec.ts --- spec-main/extensions-spec.ts | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/spec-main/extensions-spec.ts b/spec-main/extensions-spec.ts index 4978a57331778..f325f138625b2 100644 --- a/spec-main/extensions-spec.ts +++ b/spec-main/extensions-spec.ts @@ -31,26 +31,13 @@ ifdescribe(process.electronBinding('features').isExtensionsEnabled())('chrome ex // extension in an in-memory session results in it being installed in the // default session. const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); - await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg')); - const w = new BrowserWindow({ show: false, webPreferences: { session: customSession } }); + (customSession as any).loadChromeExtension(path.join(fixtures, 'extensions', 'red-bg')); + const w = new BrowserWindow({show: false, webPreferences: {session: customSession}}) await w.loadURL(url); const bg = await w.webContents.executeJavaScript('document.documentElement.style.backgroundColor'); expect(bg).to.equal('red'); }); - it('serializes a loaded extension', async () => { - const extensionPath = path.join(fixtures, 'extensions', 'red-bg'); - const manifest = JSON.parse(fs.readFileSync(path.join(extensionPath, 'manifest.json'), 'utf-8')); - const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); - const extension = await customSession.loadExtension(extensionPath); - expect(extension.id).to.be.a('string'); - expect(extension.name).to.be.a('string'); - expect(extension.path).to.be.a('string'); - expect(extension.version).to.be.a('string'); - expect(extension.url).to.be.a('string'); - expect(extension.manifest).to.deep.equal(manifest); - }); - it('confines an extension to the session it was loaded in', async () => { const customSession = session.fromPartition(`persist:${require('uuid').v4()}`); (customSession as any).loadChromeExtension(path.join(fixtures, 'extensions', 'red-bg')) From c3e8a47121d1dfadeb7011f5fc835505e8a605f9 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 23 Mar 2020 11:20:40 -0700 Subject: [PATCH 7/8] chore: fix tsc compilation --- spec-main/api-net-spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec-main/api-net-spec.ts b/spec-main/api-net-spec.ts index c7e1ce3ea3148..f87c21d6dd1f7 100644 --- a/spec-main/api-net-spec.ts +++ b/spec-main/api-net-spec.ts @@ -218,7 +218,7 @@ describe('net module', () => { const response = await getResponse(urlRequest); expect(response.statusCode).to.equal(200); const received = await collectStreamBodyBuffer(response); - expect(sent.equals(received)).to.be.true(); + expect(sent.equals(received)).to.be.true('sent equals received'); expect(chunkIndex).to.be.equal(chunkCount); }); @@ -294,7 +294,7 @@ describe('net module', () => { return response.writeHead(200).end('ok'); }, 2); const customSession = session.fromPartition(`net-proxy-test-${Math.random()}`); - await customSession.setProxy({ proxyRules: proxyUrl.replace('http://', ''), proxyBypassRules: '<-loopback>' }); + await customSession.setProxy({ proxyRules: proxyUrl.replace('http://', ''), proxyBypassRules: '<-loopback>' } as any); const bw = new BrowserWindow({ show: false, webPreferences: { session: customSession } }); bw.webContents.on('login', (event, details, authInfo, cb) => { event.preventDefault(); From 74c4f0b6172be5056f67ecbd285a0c6559a93224 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Mon, 23 Mar 2020 13:37:30 -0700 Subject: [PATCH 8/8] spec: set-cookie will not work until Electron 8 for samesite net module requests --- spec-main/api-net-spec.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/spec-main/api-net-spec.ts b/spec-main/api-net-spec.ts index f87c21d6dd1f7..b483b3cca2d35 100644 --- a/spec-main/api-net-spec.ts +++ b/spec-main/api-net-spec.ts @@ -598,14 +598,9 @@ describe('net module', () => { const sess = session.fromPartition(`cookie-tests-same-site-${mode}`); let cookies = await sess.cookies.get({}); expect(cookies).to.have.lengthOf(0); - const urlRequest = net.request({ - url: serverUrl, - session: sess, - useSessionCookies: true - }); - const response = await getResponse(urlRequest); - expect(response.headers['x-cookie']).to.equal('undefined'); - await collectStreamBody(response); + const bw = new BrowserWindow({ show: false, webPreferences: { session: sess } }); + await bw.loadURL(serverUrl); + bw.close(); cookies = await sess.cookies.get({}); expect(cookies).to.have.lengthOf(1); expect(cookies[0]).to.deep.equal({