api-net-spec.ts 91 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344
  1. import { expect } from 'chai';
  2. import * as dns from 'node:dns';
  3. import { net, session, ClientRequest, BrowserWindow, ClientRequestConstructorOptions, protocol } from 'electron/main';
  4. import * as http from 'node:http';
  5. import * as url from 'node:url';
  6. import * as path from 'node:path';
  7. import { Socket } from 'node:net';
  8. import { defer, listen } from './lib/spec-helpers';
  9. import { once } from 'node:events';
  10. import { setTimeout } from 'node:timers/promises';
  11. // See https://github.com/nodejs/node/issues/40702.
  12. dns.setDefaultResultOrder('ipv4first');
  13. const kOneKiloByte = 1024;
  14. const kOneMegaByte = kOneKiloByte * kOneKiloByte;
  15. function randomBuffer (size: number, start: number = 0, end: number = 255) {
  16. const range = 1 + end - start;
  17. const buffer = Buffer.allocUnsafe(size);
  18. for (let i = 0; i < size; ++i) {
  19. buffer[i] = start + Math.floor(Math.random() * range);
  20. }
  21. return buffer;
  22. }
  23. function randomString (length: number) {
  24. const buffer = randomBuffer(length, '0'.charCodeAt(0), 'z'.charCodeAt(0));
  25. return buffer.toString();
  26. }
  27. async function getResponse (urlRequest: Electron.ClientRequest) {
  28. return new Promise<Electron.IncomingMessage>((resolve, reject) => {
  29. urlRequest.on('error', reject);
  30. urlRequest.on('abort', reject);
  31. urlRequest.on('response', (response) => resolve(response));
  32. urlRequest.end();
  33. });
  34. }
  35. async function collectStreamBody (response: Electron.IncomingMessage | http.IncomingMessage) {
  36. return (await collectStreamBodyBuffer(response)).toString();
  37. }
  38. function collectStreamBodyBuffer (response: Electron.IncomingMessage | http.IncomingMessage) {
  39. return new Promise<Buffer>((resolve, reject) => {
  40. response.on('error', reject);
  41. (response as NodeJS.EventEmitter).on('aborted', reject);
  42. const data: Buffer[] = [];
  43. response.on('data', (chunk) => data.push(chunk));
  44. response.on('end', (chunk?: Buffer) => {
  45. if (chunk) data.push(chunk);
  46. resolve(Buffer.concat(data));
  47. });
  48. });
  49. }
  50. async function respondNTimes (fn: http.RequestListener, n: number): Promise<string> {
  51. const server = http.createServer((request, response) => {
  52. fn(request, response);
  53. // don't close if a redirect was returned
  54. if ((response.statusCode < 300 || response.statusCode >= 399) && n <= 0) {
  55. n--;
  56. server.close();
  57. }
  58. });
  59. const sockets: Socket[] = [];
  60. server.on('connection', s => sockets.push(s));
  61. defer(() => {
  62. server.close();
  63. for (const socket of sockets) {
  64. socket.destroy();
  65. }
  66. });
  67. return (await listen(server)).url;
  68. }
  69. function respondOnce (fn: http.RequestListener) {
  70. return respondNTimes(fn, 1);
  71. }
  72. let routeFailure = false;
  73. respondNTimes.toRoutes = (routes: Record<string, http.RequestListener>, n: number) => {
  74. return respondNTimes((request, response) => {
  75. if (Object.hasOwn(routes, request.url || '')) {
  76. (async () => {
  77. await Promise.resolve(routes[request.url || ''](request, response));
  78. })().catch((err) => {
  79. routeFailure = true;
  80. console.error('Route handler failed, this is probably why your test failed', err);
  81. response.statusCode = 500;
  82. response.end();
  83. });
  84. } else {
  85. response.statusCode = 500;
  86. response.end();
  87. expect.fail(`Unexpected URL: ${request.url}`);
  88. }
  89. }, n);
  90. };
  91. respondOnce.toRoutes = (routes: Record<string, http.RequestListener>) => respondNTimes.toRoutes(routes, 1);
  92. respondNTimes.toURL = (url: string, fn: http.RequestListener, n: number) => {
  93. return respondNTimes.toRoutes({ [url]: fn }, n);
  94. };
  95. respondOnce.toURL = (url: string, fn: http.RequestListener) => respondNTimes.toURL(url, fn, 1);
  96. respondNTimes.toSingleURL = (fn: http.RequestListener, n: number) => {
  97. const requestUrl = '/requestUrl';
  98. return respondNTimes.toURL(requestUrl, fn, n).then(url => `${url}${requestUrl}`);
  99. };
  100. respondOnce.toSingleURL = (fn: http.RequestListener) => respondNTimes.toSingleURL(fn, 1);
  101. describe('net module', () => {
  102. beforeEach(() => {
  103. routeFailure = false;
  104. });
  105. afterEach(async function () {
  106. await session.defaultSession.clearCache();
  107. if (routeFailure && this.test) {
  108. if (!this.test.isFailed()) {
  109. throw new Error('Failing this test due an unhandled error in the respondOnce route handler, check the logs above for the actual error');
  110. }
  111. }
  112. });
  113. describe('HTTP basics', () => {
  114. it('should be able to issue a basic GET request', async () => {
  115. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  116. expect(request.method).to.equal('GET');
  117. response.end();
  118. });
  119. const urlRequest = net.request(serverUrl);
  120. const response = await getResponse(urlRequest);
  121. expect(response.statusCode).to.equal(200);
  122. await collectStreamBody(response);
  123. });
  124. it('should be able to issue a basic POST request', async () => {
  125. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  126. expect(request.method).to.equal('POST');
  127. response.end();
  128. });
  129. const urlRequest = net.request({
  130. method: 'POST',
  131. url: serverUrl
  132. });
  133. const response = await getResponse(urlRequest);
  134. expect(response.statusCode).to.equal(200);
  135. await collectStreamBody(response);
  136. });
  137. it('should fetch correct data in a GET request', async () => {
  138. const expectedBodyData = 'Hello World!';
  139. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  140. expect(request.method).to.equal('GET');
  141. response.end(expectedBodyData);
  142. });
  143. const urlRequest = net.request(serverUrl);
  144. const response = await getResponse(urlRequest);
  145. expect(response.statusCode).to.equal(200);
  146. const body = await collectStreamBody(response);
  147. expect(body).to.equal(expectedBodyData);
  148. });
  149. it('should post the correct data in a POST request', async () => {
  150. const bodyData = 'Hello World!';
  151. let postedBodyData: string = '';
  152. const serverUrl = await respondOnce.toSingleURL(async (request, response) => {
  153. postedBodyData = await collectStreamBody(request);
  154. response.end();
  155. });
  156. const urlRequest = net.request({
  157. method: 'POST',
  158. url: serverUrl
  159. });
  160. urlRequest.write(bodyData);
  161. const response = await getResponse(urlRequest);
  162. expect(response.statusCode).to.equal(200);
  163. expect(postedBodyData).to.equal(bodyData);
  164. });
  165. it('a 307 redirected POST request preserves the body', async () => {
  166. const bodyData = 'Hello World!';
  167. let postedBodyData: string = '';
  168. let methodAfterRedirect: string | undefined;
  169. const serverUrl = await respondNTimes.toRoutes({
  170. '/redirect': (req, res) => {
  171. res.statusCode = 307;
  172. res.setHeader('location', serverUrl);
  173. return res.end();
  174. },
  175. '/': async (req, res) => {
  176. methodAfterRedirect = req.method;
  177. postedBodyData = await collectStreamBody(req);
  178. res.end();
  179. }
  180. }, 2);
  181. const urlRequest = net.request({
  182. method: 'POST',
  183. url: serverUrl + '/redirect'
  184. });
  185. urlRequest.write(bodyData);
  186. const response = await getResponse(urlRequest);
  187. expect(response.statusCode).to.equal(200);
  188. await collectStreamBody(response);
  189. expect(methodAfterRedirect).to.equal('POST');
  190. expect(postedBodyData).to.equal(bodyData);
  191. });
  192. it('a 302 redirected POST request DOES NOT preserve the body', async () => {
  193. const bodyData = 'Hello World!';
  194. let postedBodyData: string = '';
  195. let methodAfterRedirect: string | undefined;
  196. const serverUrl = await respondNTimes.toRoutes({
  197. '/redirect': (req, res) => {
  198. res.statusCode = 302;
  199. res.setHeader('location', serverUrl);
  200. return res.end();
  201. },
  202. '/': async (req, res) => {
  203. methodAfterRedirect = req.method;
  204. postedBodyData = await collectStreamBody(req);
  205. res.end();
  206. }
  207. }, 2);
  208. const urlRequest = net.request({
  209. method: 'POST',
  210. url: serverUrl + '/redirect'
  211. });
  212. urlRequest.write(bodyData);
  213. const response = await getResponse(urlRequest);
  214. expect(response.statusCode).to.equal(200);
  215. await collectStreamBody(response);
  216. expect(methodAfterRedirect).to.equal('GET');
  217. expect(postedBodyData).to.equal('');
  218. });
  219. it('should support chunked encoding', async () => {
  220. let receivedRequest: http.IncomingMessage = null as any;
  221. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  222. response.statusCode = 200;
  223. response.statusMessage = 'OK';
  224. response.chunkedEncoding = true;
  225. receivedRequest = request;
  226. request.on('data', (chunk: Buffer) => {
  227. response.write(chunk);
  228. });
  229. request.on('end', (chunk: Buffer) => {
  230. response.end(chunk);
  231. });
  232. });
  233. const urlRequest = net.request({
  234. method: 'POST',
  235. url: serverUrl
  236. });
  237. let chunkIndex = 0;
  238. const chunkCount = 100;
  239. let sent = Buffer.alloc(0);
  240. urlRequest.chunkedEncoding = true;
  241. while (chunkIndex < chunkCount) {
  242. chunkIndex += 1;
  243. const chunk = randomBuffer(kOneKiloByte);
  244. sent = Buffer.concat([sent, chunk]);
  245. urlRequest.write(chunk);
  246. }
  247. const response = await getResponse(urlRequest);
  248. expect(receivedRequest.method).to.equal('POST');
  249. expect(receivedRequest.headers['transfer-encoding']).to.equal('chunked');
  250. expect(receivedRequest.headers['content-length']).to.equal(undefined);
  251. expect(response.statusCode).to.equal(200);
  252. const received = await collectStreamBodyBuffer(response);
  253. expect(sent.equals(received)).to.be.true();
  254. expect(chunkIndex).to.be.equal(chunkCount);
  255. });
  256. for (const extraOptions of [{}, { credentials: 'include' }, { useSessionCookies: false, credentials: 'include' }] as ClientRequestConstructorOptions[]) {
  257. describe(`authentication when ${JSON.stringify(extraOptions)}`, () => {
  258. it('should emit the login event when 401', async () => {
  259. const [user, pass] = ['user', 'pass'];
  260. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  261. if (!request.headers.authorization) {
  262. return response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' }).end();
  263. }
  264. response.writeHead(200).end('ok');
  265. });
  266. let loginAuthInfo: Electron.AuthInfo;
  267. const request = net.request({ method: 'GET', url: serverUrl, ...extraOptions });
  268. request.on('login', (authInfo, cb) => {
  269. loginAuthInfo = authInfo;
  270. cb(user, pass);
  271. });
  272. const response = await getResponse(request);
  273. expect(response.statusCode).to.equal(200);
  274. expect(loginAuthInfo!.realm).to.equal('Foo');
  275. expect(loginAuthInfo!.scheme).to.equal('basic');
  276. });
  277. it('should receive 401 response when cancelling authentication', async () => {
  278. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  279. if (!request.headers.authorization) {
  280. response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' });
  281. response.end('unauthenticated');
  282. } else {
  283. response.writeHead(200).end('ok');
  284. }
  285. });
  286. const request = net.request({ method: 'GET', url: serverUrl, ...extraOptions });
  287. request.on('login', (authInfo, cb) => {
  288. cb();
  289. });
  290. const response = await getResponse(request);
  291. const body = await collectStreamBody(response);
  292. expect(response.statusCode).to.equal(401);
  293. expect(body).to.equal('unauthenticated');
  294. });
  295. it('should share credentials with WebContents', async () => {
  296. const [user, pass] = ['user', 'pass'];
  297. const serverUrl = await respondNTimes.toSingleURL((request, response) => {
  298. if (!request.headers.authorization) {
  299. return response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' }).end();
  300. }
  301. return response.writeHead(200).end('ok');
  302. }, 2);
  303. const bw = new BrowserWindow({ show: false });
  304. bw.webContents.on('login', (event, details, authInfo, cb) => {
  305. event.preventDefault();
  306. cb(user, pass);
  307. });
  308. await bw.loadURL(serverUrl);
  309. bw.close();
  310. const request = net.request({ method: 'GET', url: serverUrl, ...extraOptions });
  311. let logInCount = 0;
  312. request.on('login', () => {
  313. logInCount++;
  314. });
  315. const response = await getResponse(request);
  316. await collectStreamBody(response);
  317. expect(logInCount).to.equal(0, 'should not receive a login event, credentials should be cached');
  318. });
  319. it('should share proxy credentials with WebContents', async () => {
  320. const [user, pass] = ['user', 'pass'];
  321. const proxyUrl = await respondNTimes((request, response) => {
  322. if (!request.headers['proxy-authorization']) {
  323. return response.writeHead(407, { 'Proxy-Authenticate': 'Basic realm="Foo"' }).end();
  324. }
  325. return response.writeHead(200).end('ok');
  326. }, 2);
  327. const customSession = session.fromPartition(`net-proxy-test-${Math.random()}`);
  328. await customSession.setProxy({ proxyRules: proxyUrl.replace('http://', ''), proxyBypassRules: '<-loopback>' });
  329. const bw = new BrowserWindow({ show: false, webPreferences: { session: customSession } });
  330. bw.webContents.on('login', (event, details, authInfo, cb) => {
  331. event.preventDefault();
  332. cb(user, pass);
  333. });
  334. await bw.loadURL('http://127.0.0.1:9999');
  335. bw.close();
  336. const request = net.request({ method: 'GET', url: 'http://127.0.0.1:9999', session: customSession, ...extraOptions });
  337. let logInCount = 0;
  338. request.on('login', () => {
  339. logInCount++;
  340. });
  341. const response = await getResponse(request);
  342. const body = await collectStreamBody(response);
  343. expect(response.statusCode).to.equal(200);
  344. expect(body).to.equal('ok');
  345. expect(logInCount).to.equal(0, 'should not receive a login event, credentials should be cached');
  346. });
  347. it('should upload body when 401', async () => {
  348. const [user, pass] = ['user', 'pass'];
  349. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  350. if (!request.headers.authorization) {
  351. return response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' }).end();
  352. }
  353. response.writeHead(200);
  354. request.on('data', (chunk) => response.write(chunk));
  355. request.on('end', () => response.end());
  356. });
  357. const requestData = randomString(kOneKiloByte);
  358. const request = net.request({ method: 'GET', url: serverUrl, ...extraOptions });
  359. request.on('login', (authInfo, cb) => {
  360. cb(user, pass);
  361. });
  362. request.write(requestData);
  363. const response = await getResponse(request);
  364. const responseData = await collectStreamBody(response);
  365. expect(responseData).to.equal(requestData);
  366. });
  367. });
  368. }
  369. describe('authentication when {"credentials":"omit"}', () => {
  370. it('should not emit the login event when 401', async () => {
  371. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  372. if (!request.headers.authorization) {
  373. return response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' }).end();
  374. }
  375. response.writeHead(200).end('ok');
  376. });
  377. const request = net.request({ method: 'GET', url: serverUrl, credentials: 'omit' });
  378. request.on('login', () => {
  379. expect.fail('unexpected login event');
  380. });
  381. const response = await getResponse(request);
  382. expect(response.statusCode).to.equal(401);
  383. expect(response.headers['www-authenticate']).to.equal('Basic realm="Foo"');
  384. });
  385. it('should not share credentials with WebContents', async () => {
  386. const [user, pass] = ['user', 'pass'];
  387. const serverUrl = await respondNTimes.toSingleURL((request, response) => {
  388. if (!request.headers.authorization) {
  389. return response.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Foo"' }).end();
  390. }
  391. return response.writeHead(200).end('ok');
  392. }, 2);
  393. const bw = new BrowserWindow({ show: false });
  394. bw.webContents.on('login', (event, details, authInfo, cb) => {
  395. event.preventDefault();
  396. cb(user, pass);
  397. });
  398. await bw.loadURL(serverUrl);
  399. bw.close();
  400. const request = net.request({ method: 'GET', url: serverUrl, credentials: 'omit' });
  401. request.on('login', () => {
  402. expect.fail();
  403. });
  404. const response = await getResponse(request);
  405. expect(response.statusCode).to.equal(401);
  406. expect(response.headers['www-authenticate']).to.equal('Basic realm="Foo"');
  407. });
  408. it('should share proxy credentials with WebContents', async () => {
  409. const [user, pass] = ['user', 'pass'];
  410. const proxyUrl = await respondNTimes((request, response) => {
  411. if (!request.headers['proxy-authorization']) {
  412. return response.writeHead(407, { 'Proxy-Authenticate': 'Basic realm="Foo"' }).end();
  413. }
  414. return response.writeHead(200).end('ok');
  415. }, 2);
  416. const customSession = session.fromPartition(`net-proxy-test-${Math.random()}`);
  417. await customSession.setProxy({ proxyRules: proxyUrl.replace('http://', ''), proxyBypassRules: '<-loopback>' });
  418. const bw = new BrowserWindow({ show: false, webPreferences: { session: customSession } });
  419. bw.webContents.on('login', (event, details, authInfo, cb) => {
  420. event.preventDefault();
  421. cb(user, pass);
  422. });
  423. await bw.loadURL('http://127.0.0.1:9999');
  424. bw.close();
  425. const request = net.request({ method: 'GET', url: 'http://127.0.0.1:9999', session: customSession, credentials: 'omit' });
  426. request.on('login', () => {
  427. expect.fail();
  428. });
  429. const response = await getResponse(request);
  430. const body = await collectStreamBody(response);
  431. expect(response.statusCode).to.equal(200);
  432. expect(body).to.equal('ok');
  433. });
  434. });
  435. });
  436. describe('ClientRequest API', () => {
  437. it('request/response objects should emit expected events', async () => {
  438. const bodyData = randomString(kOneKiloByte);
  439. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  440. response.end(bodyData);
  441. });
  442. const urlRequest = net.request(serverUrl);
  443. // request close event
  444. const closePromise = once(urlRequest, 'close');
  445. // request finish event
  446. const finishPromise = once(urlRequest, 'close');
  447. // request "response" event
  448. const response = await getResponse(urlRequest);
  449. response.on('error', (error: Error) => {
  450. expect(error).to.be.an('Error');
  451. });
  452. const statusCode = response.statusCode;
  453. expect(statusCode).to.equal(200);
  454. // response data event
  455. // respond end event
  456. const body = await collectStreamBody(response);
  457. expect(body).to.equal(bodyData);
  458. urlRequest.on('error', (error) => {
  459. expect(error).to.be.an('Error');
  460. });
  461. await Promise.all([closePromise, finishPromise]);
  462. });
  463. it('should be able to set a custom HTTP request header before first write', async () => {
  464. const customHeaderName = 'Some-Custom-Header-Name';
  465. const customHeaderValue = 'Some-Customer-Header-Value';
  466. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  467. expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue);
  468. response.statusCode = 200;
  469. response.statusMessage = 'OK';
  470. response.end();
  471. });
  472. const urlRequest = net.request(serverUrl);
  473. urlRequest.setHeader(customHeaderName, customHeaderValue);
  474. expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue);
  475. expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue);
  476. urlRequest.write('');
  477. expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue);
  478. expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue);
  479. const response = await getResponse(urlRequest);
  480. expect(response.statusCode).to.equal(200);
  481. await collectStreamBody(response);
  482. });
  483. it('should be able to set a non-string object as a header value', async () => {
  484. const customHeaderName = 'Some-Integer-Value';
  485. const customHeaderValue = 900;
  486. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  487. expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue.toString());
  488. response.statusCode = 200;
  489. response.statusMessage = 'OK';
  490. response.end();
  491. });
  492. const urlRequest = net.request(serverUrl);
  493. urlRequest.setHeader(customHeaderName, customHeaderValue as any);
  494. expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue);
  495. expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue);
  496. urlRequest.write('');
  497. expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue);
  498. expect(urlRequest.getHeader(customHeaderName.toLowerCase())).to.equal(customHeaderValue);
  499. const response = await getResponse(urlRequest);
  500. expect(response.statusCode).to.equal(200);
  501. await collectStreamBody(response);
  502. });
  503. it('should not change the case of header name', async () => {
  504. const customHeaderName = 'X-Header-Name';
  505. const customHeaderValue = 'value';
  506. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  507. expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue.toString());
  508. expect(request.rawHeaders.includes(customHeaderName)).to.equal(true);
  509. response.statusCode = 200;
  510. response.statusMessage = 'OK';
  511. response.end();
  512. });
  513. const urlRequest = net.request(serverUrl);
  514. urlRequest.setHeader(customHeaderName, customHeaderValue);
  515. expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue);
  516. urlRequest.write('');
  517. const response = await getResponse(urlRequest);
  518. expect(response.statusCode).to.equal(200);
  519. await collectStreamBody(response);
  520. });
  521. it('should not be able to set a custom HTTP request header after first write', async () => {
  522. const customHeaderName = 'Some-Custom-Header-Name';
  523. const customHeaderValue = 'Some-Customer-Header-Value';
  524. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  525. expect(request.headers[customHeaderName.toLowerCase()]).to.equal(undefined);
  526. response.statusCode = 200;
  527. response.statusMessage = 'OK';
  528. response.end();
  529. });
  530. const urlRequest = net.request(serverUrl);
  531. urlRequest.write('');
  532. expect(() => {
  533. urlRequest.setHeader(customHeaderName, customHeaderValue);
  534. }).to.throw();
  535. expect(urlRequest.getHeader(customHeaderName)).to.equal(undefined);
  536. const response = await getResponse(urlRequest);
  537. expect(response.statusCode).to.equal(200);
  538. await collectStreamBody(response);
  539. });
  540. it('should be able to remove a custom HTTP request header before first write', async () => {
  541. const customHeaderName = 'Some-Custom-Header-Name';
  542. const customHeaderValue = 'Some-Customer-Header-Value';
  543. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  544. expect(request.headers[customHeaderName.toLowerCase()]).to.equal(undefined);
  545. response.statusCode = 200;
  546. response.statusMessage = 'OK';
  547. response.end();
  548. });
  549. const urlRequest = net.request(serverUrl);
  550. urlRequest.setHeader(customHeaderName, customHeaderValue);
  551. expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue);
  552. urlRequest.removeHeader(customHeaderName);
  553. expect(urlRequest.getHeader(customHeaderName)).to.equal(undefined);
  554. urlRequest.write('');
  555. const response = await getResponse(urlRequest);
  556. expect(response.statusCode).to.equal(200);
  557. await collectStreamBody(response);
  558. });
  559. it('should not be able to remove a custom HTTP request header after first write', async () => {
  560. const customHeaderName = 'Some-Custom-Header-Name';
  561. const customHeaderValue = 'Some-Customer-Header-Value';
  562. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  563. expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue);
  564. response.statusCode = 200;
  565. response.statusMessage = 'OK';
  566. response.end();
  567. });
  568. const urlRequest = net.request(serverUrl);
  569. urlRequest.setHeader(customHeaderName, customHeaderValue);
  570. expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue);
  571. urlRequest.write('');
  572. expect(() => {
  573. urlRequest.removeHeader(customHeaderName);
  574. }).to.throw();
  575. expect(urlRequest.getHeader(customHeaderName)).to.equal(customHeaderValue);
  576. const response = await getResponse(urlRequest);
  577. expect(response.statusCode).to.equal(200);
  578. await collectStreamBody(response);
  579. });
  580. it('should keep the order of headers', async () => {
  581. const customHeaderNameA = 'X-Header-100';
  582. const customHeaderNameB = 'X-Header-200';
  583. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  584. const headerNames = Array.from(Object.keys(request.headers));
  585. const headerAIndex = headerNames.indexOf(customHeaderNameA.toLowerCase());
  586. const headerBIndex = headerNames.indexOf(customHeaderNameB.toLowerCase());
  587. expect(headerBIndex).to.be.below(headerAIndex);
  588. response.statusCode = 200;
  589. response.statusMessage = 'OK';
  590. response.end();
  591. });
  592. const urlRequest = net.request(serverUrl);
  593. urlRequest.setHeader(customHeaderNameB, 'b');
  594. urlRequest.setHeader(customHeaderNameA, 'a');
  595. const response = await getResponse(urlRequest);
  596. expect(response.statusCode).to.equal(200);
  597. await collectStreamBody(response);
  598. });
  599. it('should be able to set cookie header line', async () => {
  600. const cookieHeaderName = 'Cookie';
  601. const cookieHeaderValue = 'test=12345';
  602. const customSession = session.fromPartition(`test-cookie-header-${Math.random()}`);
  603. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  604. expect(request.headers[cookieHeaderName.toLowerCase()]).to.equal(cookieHeaderValue);
  605. response.statusCode = 200;
  606. response.statusMessage = 'OK';
  607. response.end();
  608. });
  609. await customSession.cookies.set({
  610. url: `${serverUrl}`,
  611. name: 'test',
  612. value: '11111',
  613. expirationDate: 0
  614. });
  615. const urlRequest = net.request({
  616. method: 'GET',
  617. url: serverUrl,
  618. session: customSession
  619. });
  620. urlRequest.setHeader(cookieHeaderName, cookieHeaderValue);
  621. expect(urlRequest.getHeader(cookieHeaderName)).to.equal(cookieHeaderValue);
  622. const response = await getResponse(urlRequest);
  623. expect(response.statusCode).to.equal(200);
  624. await collectStreamBody(response);
  625. });
  626. it('should be able to receive cookies', async () => {
  627. const cookie = ['cookie1', 'cookie2'];
  628. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  629. response.statusCode = 200;
  630. response.statusMessage = 'OK';
  631. response.setHeader('set-cookie', cookie);
  632. response.end();
  633. });
  634. const urlRequest = net.request(serverUrl);
  635. const response = await getResponse(urlRequest);
  636. expect(response.headers['set-cookie']).to.have.same.members(cookie);
  637. });
  638. it('should be able to receive content-type', async () => {
  639. const contentType = 'mime/test; charset=test';
  640. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  641. response.statusCode = 200;
  642. response.statusMessage = 'OK';
  643. response.setHeader('content-type', contentType);
  644. response.end();
  645. });
  646. const urlRequest = net.request(serverUrl);
  647. const response = await getResponse(urlRequest);
  648. expect(response.headers['content-type']).to.equal(contentType);
  649. });
  650. it('should not use the sessions cookie store by default', async () => {
  651. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  652. response.statusCode = 200;
  653. response.statusMessage = 'OK';
  654. response.setHeader('x-cookie', `${request.headers.cookie!}`);
  655. response.end();
  656. });
  657. const sess = session.fromPartition(`cookie-tests-${Math.random()}`);
  658. const cookieVal = `${Date.now()}`;
  659. await sess.cookies.set({
  660. url: serverUrl,
  661. name: 'wild_cookie',
  662. value: cookieVal
  663. });
  664. const urlRequest = net.request({
  665. url: serverUrl,
  666. session: sess
  667. });
  668. const response = await getResponse(urlRequest);
  669. expect(response.headers['x-cookie']).to.equal('undefined');
  670. });
  671. for (const extraOptions of [{ useSessionCookies: true }, { credentials: 'include' }] as ClientRequestConstructorOptions[]) {
  672. describe(`when ${JSON.stringify(extraOptions)}`, () => {
  673. it('should be able to use the sessions cookie store', async () => {
  674. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  675. response.statusCode = 200;
  676. response.statusMessage = 'OK';
  677. response.setHeader('x-cookie', request.headers.cookie!);
  678. response.end();
  679. });
  680. const sess = session.fromPartition(`cookie-tests-${Math.random()}`);
  681. const cookieVal = `${Date.now()}`;
  682. await sess.cookies.set({
  683. url: serverUrl,
  684. name: 'wild_cookie',
  685. value: cookieVal
  686. });
  687. const urlRequest = net.request({
  688. url: serverUrl,
  689. session: sess,
  690. ...extraOptions
  691. });
  692. const response = await getResponse(urlRequest);
  693. expect(response.headers['x-cookie']).to.equal(`wild_cookie=${cookieVal}`);
  694. });
  695. it('should be able to use the sessions cookie store with set-cookie', async () => {
  696. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  697. response.statusCode = 200;
  698. response.statusMessage = 'OK';
  699. response.setHeader('set-cookie', 'foo=bar');
  700. response.end();
  701. });
  702. const sess = session.fromPartition(`cookie-tests-${Math.random()}`);
  703. let cookies = await sess.cookies.get({});
  704. expect(cookies).to.have.lengthOf(0);
  705. const urlRequest = net.request({
  706. url: serverUrl,
  707. session: sess,
  708. ...extraOptions
  709. });
  710. await collectStreamBody(await getResponse(urlRequest));
  711. cookies = await sess.cookies.get({});
  712. expect(cookies).to.have.lengthOf(1);
  713. expect(cookies[0]).to.deep.equal({
  714. name: 'foo',
  715. value: 'bar',
  716. domain: '127.0.0.1',
  717. hostOnly: true,
  718. path: '/',
  719. secure: false,
  720. httpOnly: false,
  721. session: true,
  722. sameSite: 'unspecified'
  723. });
  724. });
  725. for (const mode of ['Lax', 'Strict']) {
  726. it(`should be able to use the sessions cookie store with same-site ${mode} cookies`, async () => {
  727. const serverUrl = await respondNTimes.toSingleURL((request, response) => {
  728. response.statusCode = 200;
  729. response.statusMessage = 'OK';
  730. response.setHeader('set-cookie', `same=site; SameSite=${mode}`);
  731. response.setHeader('x-cookie', `${request.headers.cookie}`);
  732. response.end();
  733. }, 2);
  734. const sess = session.fromPartition(`cookie-tests-${Math.random()}`);
  735. let cookies = await sess.cookies.get({});
  736. expect(cookies).to.have.lengthOf(0);
  737. const urlRequest = net.request({
  738. url: serverUrl,
  739. session: sess,
  740. ...extraOptions
  741. });
  742. const response = await getResponse(urlRequest);
  743. expect(response.headers['x-cookie']).to.equal('undefined');
  744. await collectStreamBody(response);
  745. cookies = await sess.cookies.get({});
  746. expect(cookies).to.have.lengthOf(1);
  747. expect(cookies[0]).to.deep.equal({
  748. name: 'same',
  749. value: 'site',
  750. domain: '127.0.0.1',
  751. hostOnly: true,
  752. path: '/',
  753. secure: false,
  754. httpOnly: false,
  755. session: true,
  756. sameSite: mode.toLowerCase()
  757. });
  758. const urlRequest2 = net.request({
  759. url: serverUrl,
  760. session: sess,
  761. ...extraOptions
  762. });
  763. const response2 = await getResponse(urlRequest2);
  764. expect(response2.headers['x-cookie']).to.equal('same=site');
  765. });
  766. }
  767. it('should be able to use the sessions cookie store safely across redirects', async () => {
  768. const serverUrl = await respondOnce.toSingleURL(async (request, response) => {
  769. response.statusCode = 302;
  770. response.statusMessage = 'Moved';
  771. const newUrl = await respondOnce.toSingleURL((req, res) => {
  772. res.statusCode = 200;
  773. res.statusMessage = 'OK';
  774. res.setHeader('x-cookie', req.headers.cookie!);
  775. res.end();
  776. });
  777. response.setHeader('x-cookie', request.headers.cookie!);
  778. response.setHeader('location', newUrl.replace('127.0.0.1', 'localhost'));
  779. response.end();
  780. });
  781. const sess = session.fromPartition(`cookie-tests-${Math.random()}`);
  782. const cookie127Val = `${Date.now()}-127`;
  783. const cookieLocalVal = `${Date.now()}-local`;
  784. const localhostUrl = serverUrl.replace('127.0.0.1', 'localhost');
  785. expect(localhostUrl).to.not.equal(serverUrl);
  786. // cookies with lax or strict same-site settings will not
  787. // persist after redirects. no_restriction must be used
  788. await Promise.all([
  789. sess.cookies.set({
  790. url: serverUrl,
  791. name: 'wild_cookie',
  792. sameSite: 'no_restriction',
  793. value: cookie127Val
  794. }), sess.cookies.set({
  795. url: localhostUrl,
  796. name: 'wild_cookie',
  797. sameSite: 'no_restriction',
  798. value: cookieLocalVal
  799. })
  800. ]);
  801. const urlRequest = net.request({
  802. url: serverUrl,
  803. session: sess,
  804. ...extraOptions
  805. });
  806. urlRequest.on('redirect', (status, method, url, headers) => {
  807. // The initial redirect response should have received the 127 value here
  808. expect(headers['x-cookie'][0]).to.equal(`wild_cookie=${cookie127Val}`);
  809. urlRequest.followRedirect();
  810. });
  811. const response = await getResponse(urlRequest);
  812. // We expect the server to have received the localhost value here
  813. // The original request was to a 127.0.0.1 URL
  814. // That request would have the cookie127Val cookie attached
  815. // The request is then redirect to a localhost URL (different site)
  816. // Because we are using the session cookie store it should do the safe / secure thing
  817. // and attach the cookies for the new target domain
  818. expect(response.headers['x-cookie']).to.equal(`wild_cookie=${cookieLocalVal}`);
  819. });
  820. });
  821. }
  822. it('should be able correctly filter out cookies that are secure', async () => {
  823. const sess = session.fromPartition(`cookie-tests-${Math.random()}`);
  824. await Promise.all([
  825. sess.cookies.set({
  826. url: 'https://electronjs.org',
  827. domain: 'electronjs.org',
  828. name: 'cookie1',
  829. value: '1',
  830. secure: true
  831. }),
  832. sess.cookies.set({
  833. url: 'https://electronjs.org',
  834. domain: 'electronjs.org',
  835. name: 'cookie2',
  836. value: '2',
  837. secure: false
  838. })
  839. ]);
  840. const secureCookies = await sess.cookies.get({
  841. secure: true
  842. });
  843. expect(secureCookies).to.have.lengthOf(1);
  844. expect(secureCookies[0].name).to.equal('cookie1');
  845. const cookies = await sess.cookies.get({
  846. secure: false
  847. });
  848. expect(cookies).to.have.lengthOf(1);
  849. expect(cookies[0].name).to.equal('cookie2');
  850. });
  851. it('throws when an invalid domain is passed', async () => {
  852. const sess = session.fromPartition(`cookie-tests-${Math.random()}`);
  853. await expect(sess.cookies.set({
  854. url: 'https://electronjs.org',
  855. domain: 'wssss.iamabaddomain.fun',
  856. name: 'cookie1'
  857. })).to.eventually.be.rejectedWith(/Failed to set cookie with an invalid domain attribute/);
  858. });
  859. it('should be able correctly filter out cookies that are session', async () => {
  860. const sess = session.fromPartition(`cookie-tests-${Math.random()}`);
  861. await Promise.all([
  862. sess.cookies.set({
  863. url: 'https://electronjs.org',
  864. domain: 'electronjs.org',
  865. name: 'cookie1',
  866. value: '1'
  867. }),
  868. sess.cookies.set({
  869. url: 'https://electronjs.org',
  870. domain: 'electronjs.org',
  871. name: 'cookie2',
  872. value: '2',
  873. expirationDate: Math.round(Date.now() / 1000) + 10000
  874. })
  875. ]);
  876. const sessionCookies = await sess.cookies.get({
  877. session: true
  878. });
  879. expect(sessionCookies).to.have.lengthOf(1);
  880. expect(sessionCookies[0].name).to.equal('cookie1');
  881. const cookies = await sess.cookies.get({
  882. session: false
  883. });
  884. expect(cookies).to.have.lengthOf(1);
  885. expect(cookies[0].name).to.equal('cookie2');
  886. });
  887. it('should be able correctly filter out cookies that are httpOnly', async () => {
  888. const sess = session.fromPartition(`cookie-tests-${Math.random()}`);
  889. await Promise.all([
  890. sess.cookies.set({
  891. url: 'https://electronjs.org',
  892. domain: 'electronjs.org',
  893. name: 'cookie1',
  894. value: '1',
  895. httpOnly: true
  896. }),
  897. sess.cookies.set({
  898. url: 'https://electronjs.org',
  899. domain: 'electronjs.org',
  900. name: 'cookie2',
  901. value: '2',
  902. httpOnly: false
  903. })
  904. ]);
  905. const httpOnlyCookies = await sess.cookies.get({
  906. httpOnly: true
  907. });
  908. expect(httpOnlyCookies).to.have.lengthOf(1);
  909. expect(httpOnlyCookies[0].name).to.equal('cookie1');
  910. const cookies = await sess.cookies.get({
  911. httpOnly: false
  912. });
  913. expect(cookies).to.have.lengthOf(1);
  914. expect(cookies[0].name).to.equal('cookie2');
  915. });
  916. describe('when {"credentials":"omit"}', () => {
  917. it('should not send cookies');
  918. it('should not store cookies');
  919. });
  920. it('should set sec-fetch-site to same-origin for request from same origin', async () => {
  921. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  922. expect(request.headers['sec-fetch-site']).to.equal('same-origin');
  923. response.statusCode = 200;
  924. response.statusMessage = 'OK';
  925. response.end();
  926. });
  927. const urlRequest = net.request({
  928. url: serverUrl,
  929. origin: serverUrl
  930. });
  931. await collectStreamBody(await getResponse(urlRequest));
  932. });
  933. it('should set sec-fetch-site to same-origin for request with the same origin header', async () => {
  934. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  935. expect(request.headers['sec-fetch-site']).to.equal('same-origin');
  936. response.statusCode = 200;
  937. response.statusMessage = 'OK';
  938. response.end();
  939. });
  940. const urlRequest = net.request({
  941. url: serverUrl
  942. });
  943. urlRequest.setHeader('Origin', serverUrl);
  944. await collectStreamBody(await getResponse(urlRequest));
  945. });
  946. it('should set sec-fetch-site to cross-site for request from other origin', async () => {
  947. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  948. expect(request.headers['sec-fetch-site']).to.equal('cross-site');
  949. response.statusCode = 200;
  950. response.statusMessage = 'OK';
  951. response.end();
  952. });
  953. const urlRequest = net.request({
  954. url: serverUrl,
  955. origin: 'https://not-exists.com'
  956. });
  957. await collectStreamBody(await getResponse(urlRequest));
  958. });
  959. it('should not send sec-fetch-user header by default', async () => {
  960. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  961. expect(request.headers).not.to.have.property('sec-fetch-user');
  962. response.statusCode = 200;
  963. response.statusMessage = 'OK';
  964. response.end();
  965. });
  966. const urlRequest = net.request({
  967. url: serverUrl
  968. });
  969. await collectStreamBody(await getResponse(urlRequest));
  970. });
  971. it('should set sec-fetch-user to ?1 if requested', async () => {
  972. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  973. expect(request.headers['sec-fetch-user']).to.equal('?1');
  974. response.statusCode = 200;
  975. response.statusMessage = 'OK';
  976. response.end();
  977. });
  978. const urlRequest = net.request({
  979. url: serverUrl
  980. });
  981. urlRequest.setHeader('sec-fetch-user', '?1');
  982. await collectStreamBody(await getResponse(urlRequest));
  983. });
  984. it('should set sec-fetch-mode to no-cors by default', async () => {
  985. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  986. expect(request.headers['sec-fetch-mode']).to.equal('no-cors');
  987. response.statusCode = 200;
  988. response.statusMessage = 'OK';
  989. response.end();
  990. });
  991. const urlRequest = net.request({
  992. url: serverUrl
  993. });
  994. await collectStreamBody(await getResponse(urlRequest));
  995. });
  996. for (const mode of ['navigate', 'cors', 'no-cors', 'same-origin']) {
  997. it(`should set sec-fetch-mode to ${mode} if requested`, async () => {
  998. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  999. expect(request.headers['sec-fetch-mode']).to.equal(mode);
  1000. response.statusCode = 200;
  1001. response.statusMessage = 'OK';
  1002. response.end();
  1003. });
  1004. const urlRequest = net.request({
  1005. url: serverUrl,
  1006. origin: serverUrl
  1007. });
  1008. urlRequest.setHeader('sec-fetch-mode', mode);
  1009. await collectStreamBody(await getResponse(urlRequest));
  1010. });
  1011. }
  1012. it('should set sec-fetch-dest to empty by default', async () => {
  1013. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1014. expect(request.headers['sec-fetch-dest']).to.equal('empty');
  1015. response.statusCode = 200;
  1016. response.statusMessage = 'OK';
  1017. response.end();
  1018. });
  1019. const urlRequest = net.request({
  1020. url: serverUrl
  1021. });
  1022. await collectStreamBody(await getResponse(urlRequest));
  1023. });
  1024. for (const dest of [
  1025. 'empty', 'audio', 'audioworklet', 'document', 'embed', 'font',
  1026. 'frame', 'iframe', 'image', 'manifest', 'object', 'paintworklet',
  1027. 'report', 'script', 'serviceworker', 'style', 'track', 'video',
  1028. 'worker', 'xslt'
  1029. ]) {
  1030. it(`should set sec-fetch-dest to ${dest} if requested`, async () => {
  1031. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1032. expect(request.headers['sec-fetch-dest']).to.equal(dest);
  1033. response.statusCode = 200;
  1034. response.statusMessage = 'OK';
  1035. response.end();
  1036. });
  1037. const urlRequest = net.request({
  1038. url: serverUrl,
  1039. origin: serverUrl
  1040. });
  1041. urlRequest.setHeader('sec-fetch-dest', dest);
  1042. await collectStreamBody(await getResponse(urlRequest));
  1043. });
  1044. }
  1045. it('should be able to abort an HTTP request before first write', async () => {
  1046. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1047. response.end();
  1048. expect.fail('Unexpected request event');
  1049. });
  1050. const urlRequest = net.request(serverUrl);
  1051. urlRequest.on('response', () => {
  1052. expect.fail('unexpected response event');
  1053. });
  1054. const aborted = once(urlRequest, 'abort');
  1055. urlRequest.abort();
  1056. urlRequest.write('');
  1057. urlRequest.end();
  1058. await aborted;
  1059. });
  1060. it('it should be able to abort an HTTP request before request end', async () => {
  1061. let requestReceivedByServer = false;
  1062. let urlRequest: ClientRequest | null = null;
  1063. const serverUrl = await respondOnce.toSingleURL(() => {
  1064. requestReceivedByServer = true;
  1065. urlRequest!.abort();
  1066. });
  1067. let requestAbortEventEmitted = false;
  1068. urlRequest = net.request(serverUrl);
  1069. urlRequest.on('response', () => {
  1070. expect.fail('Unexpected response event');
  1071. });
  1072. urlRequest.on('finish', () => {
  1073. expect.fail('Unexpected finish event');
  1074. });
  1075. urlRequest.on('error', () => {
  1076. expect.fail('Unexpected error event');
  1077. });
  1078. urlRequest.on('abort', () => {
  1079. requestAbortEventEmitted = true;
  1080. });
  1081. const p = once(urlRequest, 'close');
  1082. urlRequest.chunkedEncoding = true;
  1083. urlRequest.write(randomString(kOneKiloByte));
  1084. await p;
  1085. expect(requestReceivedByServer).to.equal(true);
  1086. expect(requestAbortEventEmitted).to.equal(true);
  1087. });
  1088. it('it should be able to abort an HTTP request after request end and before response', async () => {
  1089. let requestReceivedByServer = false;
  1090. let urlRequest: ClientRequest | null = null;
  1091. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1092. requestReceivedByServer = true;
  1093. urlRequest!.abort();
  1094. process.nextTick(() => {
  1095. response.statusCode = 200;
  1096. response.statusMessage = 'OK';
  1097. response.end();
  1098. });
  1099. });
  1100. let requestFinishEventEmitted = false;
  1101. urlRequest = net.request(serverUrl);
  1102. urlRequest.on('response', () => {
  1103. expect.fail('Unexpected response event');
  1104. });
  1105. urlRequest.on('finish', () => {
  1106. requestFinishEventEmitted = true;
  1107. });
  1108. urlRequest.on('error', () => {
  1109. expect.fail('Unexpected error event');
  1110. });
  1111. urlRequest.end(randomString(kOneKiloByte));
  1112. await once(urlRequest, 'abort');
  1113. expect(requestFinishEventEmitted).to.equal(true);
  1114. expect(requestReceivedByServer).to.equal(true);
  1115. });
  1116. it('it should be able to abort an HTTP request after response start', async () => {
  1117. let requestReceivedByServer = false;
  1118. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1119. requestReceivedByServer = true;
  1120. response.statusCode = 200;
  1121. response.statusMessage = 'OK';
  1122. response.write(randomString(kOneKiloByte));
  1123. });
  1124. let requestFinishEventEmitted = false;
  1125. let requestResponseEventEmitted = false;
  1126. let responseCloseEventEmitted = false;
  1127. const urlRequest = net.request(serverUrl);
  1128. urlRequest.on('response', (response) => {
  1129. requestResponseEventEmitted = true;
  1130. const statusCode = response.statusCode;
  1131. expect(statusCode).to.equal(200);
  1132. response.on('data', () => {});
  1133. response.on('end', () => {
  1134. expect.fail('Unexpected end event');
  1135. });
  1136. response.on('error', () => {
  1137. expect.fail('Unexpected error event');
  1138. });
  1139. response.on('close' as any, () => {
  1140. responseCloseEventEmitted = true;
  1141. });
  1142. urlRequest.abort();
  1143. });
  1144. urlRequest.on('finish', () => {
  1145. requestFinishEventEmitted = true;
  1146. });
  1147. urlRequest.on('error', () => {
  1148. expect.fail('Unexpected error event');
  1149. });
  1150. urlRequest.end(randomString(kOneKiloByte));
  1151. await once(urlRequest, 'abort');
  1152. expect(requestFinishEventEmitted).to.be.true('request should emit "finish" event');
  1153. expect(requestReceivedByServer).to.be.true('request should be received by the server');
  1154. expect(requestResponseEventEmitted).to.be.true('"response" event should be emitted');
  1155. expect(responseCloseEventEmitted).to.be.true('response should emit "close" event');
  1156. });
  1157. it('abort event should be emitted at most once', async () => {
  1158. let requestReceivedByServer = false;
  1159. let urlRequest: ClientRequest | null = null;
  1160. const serverUrl = await respondOnce.toSingleURL(() => {
  1161. requestReceivedByServer = true;
  1162. urlRequest!.abort();
  1163. urlRequest!.abort();
  1164. });
  1165. let requestFinishEventEmitted = false;
  1166. let abortsEmitted = 0;
  1167. urlRequest = net.request(serverUrl);
  1168. urlRequest.on('response', () => {
  1169. expect.fail('Unexpected response event');
  1170. });
  1171. urlRequest.on('finish', () => {
  1172. requestFinishEventEmitted = true;
  1173. });
  1174. urlRequest.on('error', () => {
  1175. expect.fail('Unexpected error event');
  1176. });
  1177. urlRequest.on('abort', () => {
  1178. abortsEmitted++;
  1179. });
  1180. urlRequest.end(randomString(kOneKiloByte));
  1181. await once(urlRequest, 'abort');
  1182. expect(requestFinishEventEmitted).to.be.true('request should emit "finish" event');
  1183. expect(requestReceivedByServer).to.be.true('request should be received by server');
  1184. expect(abortsEmitted).to.equal(1, 'request should emit exactly 1 "abort" event');
  1185. });
  1186. it('should allow to read response body from non-2xx response', async () => {
  1187. const bodyData = randomString(kOneKiloByte);
  1188. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1189. response.statusCode = 404;
  1190. response.end(bodyData);
  1191. });
  1192. const urlRequest = net.request(serverUrl);
  1193. const bodyCheckPromise = getResponse(urlRequest).then(r => {
  1194. expect(r.statusCode).to.equal(404);
  1195. return r;
  1196. }).then(collectStreamBody).then(receivedBodyData => {
  1197. expect(receivedBodyData.toString()).to.equal(bodyData);
  1198. });
  1199. const eventHandlers = Promise.all([
  1200. bodyCheckPromise,
  1201. once(urlRequest, 'close')
  1202. ]);
  1203. urlRequest.end();
  1204. await eventHandlers;
  1205. });
  1206. describe('webRequest', () => {
  1207. afterEach(() => {
  1208. session.defaultSession.webRequest.onBeforeRequest(null);
  1209. });
  1210. it('Should throw when invalid filters are passed', () => {
  1211. expect(() => {
  1212. session.defaultSession.webRequest.onBeforeRequest(
  1213. { urls: ['*://www.googleapis.com'] },
  1214. (details, callback) => { callback({ cancel: false }); }
  1215. );
  1216. }).to.throw('Invalid url pattern *://www.googleapis.com: Empty path.');
  1217. expect(() => {
  1218. session.defaultSession.webRequest.onBeforeRequest(
  1219. { urls: ['*://www.googleapis.com/', '*://blahblah.dev'] },
  1220. (details, callback) => { callback({ cancel: false }); }
  1221. );
  1222. }).to.throw('Invalid url pattern *://blahblah.dev: Empty path.');
  1223. });
  1224. it('Should not throw when valid filters are passed', () => {
  1225. expect(() => {
  1226. session.defaultSession.webRequest.onBeforeRequest(
  1227. { urls: ['*://www.googleapis.com/'] },
  1228. (details, callback) => { callback({ cancel: false }); }
  1229. );
  1230. }).to.not.throw();
  1231. });
  1232. it('Requests should be intercepted by webRequest module', async () => {
  1233. const requestUrl = '/requestUrl';
  1234. const redirectUrl = '/redirectUrl';
  1235. let requestIsRedirected = false;
  1236. const serverUrl = await respondOnce.toURL(redirectUrl, (request, response) => {
  1237. requestIsRedirected = true;
  1238. response.end();
  1239. });
  1240. let requestIsIntercepted = false;
  1241. session.defaultSession.webRequest.onBeforeRequest(
  1242. (details, callback) => {
  1243. if (details.url === `${serverUrl}${requestUrl}`) {
  1244. requestIsIntercepted = true;
  1245. callback({
  1246. redirectURL: `${serverUrl}${redirectUrl}`
  1247. });
  1248. } else {
  1249. callback({
  1250. cancel: false
  1251. });
  1252. }
  1253. });
  1254. const urlRequest = net.request(`${serverUrl}${requestUrl}`);
  1255. const response = await getResponse(urlRequest);
  1256. expect(response.statusCode).to.equal(200);
  1257. await collectStreamBody(response);
  1258. expect(requestIsRedirected).to.be.true('The server should receive a request to the forward URL');
  1259. expect(requestIsIntercepted).to.be.true('The request should be intercepted by the webRequest module');
  1260. });
  1261. it('should to able to create and intercept a request using a custom session object', async () => {
  1262. const requestUrl = '/requestUrl';
  1263. const redirectUrl = '/redirectUrl';
  1264. const customPartitionName = `custom-partition-${Math.random()}`;
  1265. let requestIsRedirected = false;
  1266. const serverUrl = await respondOnce.toURL(redirectUrl, (request, response) => {
  1267. requestIsRedirected = true;
  1268. response.end();
  1269. });
  1270. session.defaultSession.webRequest.onBeforeRequest(() => {
  1271. expect.fail('Request should not be intercepted by the default session');
  1272. });
  1273. const customSession = session.fromPartition(customPartitionName, { cache: false });
  1274. let requestIsIntercepted = false;
  1275. customSession.webRequest.onBeforeRequest((details, callback) => {
  1276. if (details.url === `${serverUrl}${requestUrl}`) {
  1277. requestIsIntercepted = true;
  1278. callback({
  1279. redirectURL: `${serverUrl}${redirectUrl}`
  1280. });
  1281. } else {
  1282. callback({
  1283. cancel: false
  1284. });
  1285. }
  1286. });
  1287. const urlRequest = net.request({
  1288. url: `${serverUrl}${requestUrl}`,
  1289. session: customSession
  1290. });
  1291. const response = await getResponse(urlRequest);
  1292. expect(response.statusCode).to.equal(200);
  1293. await collectStreamBody(response);
  1294. expect(requestIsRedirected).to.be.true('The server should receive a request to the forward URL');
  1295. expect(requestIsIntercepted).to.be.true('The request should be intercepted by the webRequest module');
  1296. });
  1297. it('should to able to create and intercept a request using a custom partition name', async () => {
  1298. const requestUrl = '/requestUrl';
  1299. const redirectUrl = '/redirectUrl';
  1300. const customPartitionName = `custom-partition-${Math.random()}`;
  1301. let requestIsRedirected = false;
  1302. const serverUrl = await respondOnce.toURL(redirectUrl, (request, response) => {
  1303. requestIsRedirected = true;
  1304. response.end();
  1305. });
  1306. session.defaultSession.webRequest.onBeforeRequest(() => {
  1307. expect.fail('Request should not be intercepted by the default session');
  1308. });
  1309. const customSession = session.fromPartition(customPartitionName, { cache: false });
  1310. let requestIsIntercepted = false;
  1311. customSession.webRequest.onBeforeRequest((details, callback) => {
  1312. if (details.url === `${serverUrl}${requestUrl}`) {
  1313. requestIsIntercepted = true;
  1314. callback({
  1315. redirectURL: `${serverUrl}${redirectUrl}`
  1316. });
  1317. } else {
  1318. callback({
  1319. cancel: false
  1320. });
  1321. }
  1322. });
  1323. const urlRequest = net.request({
  1324. url: `${serverUrl}${requestUrl}`,
  1325. partition: customPartitionName
  1326. });
  1327. const response = await getResponse(urlRequest);
  1328. expect(response.statusCode).to.equal(200);
  1329. await collectStreamBody(response);
  1330. expect(requestIsRedirected).to.be.true('The server should receive a request to the forward URL');
  1331. expect(requestIsIntercepted).to.be.true('The request should be intercepted by the webRequest module');
  1332. });
  1333. it('triggers webRequest handlers when bypassCustomProtocolHandlers', async () => {
  1334. let webRequestDetails: Electron.OnBeforeRequestListenerDetails | null = null;
  1335. const serverUrl = await respondOnce.toSingleURL((req, res) => res.end('hi'));
  1336. session.defaultSession.webRequest.onBeforeRequest((details, cb) => {
  1337. webRequestDetails = details;
  1338. cb({});
  1339. });
  1340. const body = await net.fetch(serverUrl, { bypassCustomProtocolHandlers: true }).then(r => r.text());
  1341. expect(body).to.equal('hi');
  1342. expect(webRequestDetails).to.have.property('url', serverUrl);
  1343. });
  1344. });
  1345. it('should throw when calling getHeader without a name', () => {
  1346. expect(() => {
  1347. (net.request({ url: 'https://test' }).getHeader as any)();
  1348. }).to.throw(/`name` is required for getHeader\(name\)/);
  1349. expect(() => {
  1350. net.request({ url: 'https://test' }).getHeader(null as any);
  1351. }).to.throw(/`name` is required for getHeader\(name\)/);
  1352. });
  1353. it('should throw when calling removeHeader without a name', () => {
  1354. expect(() => {
  1355. (net.request({ url: 'https://test' }).removeHeader as any)();
  1356. }).to.throw(/`name` is required for removeHeader\(name\)/);
  1357. expect(() => {
  1358. net.request({ url: 'https://test' }).removeHeader(null as any);
  1359. }).to.throw(/`name` is required for removeHeader\(name\)/);
  1360. });
  1361. it('should follow redirect when no redirect handler is provided', async () => {
  1362. const requestUrl = '/302';
  1363. const serverUrl = await respondOnce.toRoutes({
  1364. '/302': (request, response) => {
  1365. response.statusCode = 302;
  1366. response.setHeader('Location', '/200');
  1367. response.end();
  1368. },
  1369. '/200': (request, response) => {
  1370. response.statusCode = 200;
  1371. response.end();
  1372. }
  1373. });
  1374. const urlRequest = net.request({
  1375. url: `${serverUrl}${requestUrl}`
  1376. });
  1377. const response = await getResponse(urlRequest);
  1378. expect(response.statusCode).to.equal(200);
  1379. });
  1380. it('should follow redirect chain when no redirect handler is provided', async () => {
  1381. const serverUrl = await respondOnce.toRoutes({
  1382. '/redirectChain': (request, response) => {
  1383. response.statusCode = 302;
  1384. response.setHeader('Location', '/302');
  1385. response.end();
  1386. },
  1387. '/302': (request, response) => {
  1388. response.statusCode = 302;
  1389. response.setHeader('Location', '/200');
  1390. response.end();
  1391. },
  1392. '/200': (request, response) => {
  1393. response.statusCode = 200;
  1394. response.end();
  1395. }
  1396. });
  1397. const urlRequest = net.request({
  1398. url: `${serverUrl}/redirectChain`
  1399. });
  1400. const response = await getResponse(urlRequest);
  1401. expect(response.statusCode).to.equal(200);
  1402. });
  1403. it('should not follow redirect when request is canceled in redirect handler', async () => {
  1404. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1405. response.statusCode = 302;
  1406. response.setHeader('Location', '/200');
  1407. response.end();
  1408. });
  1409. const urlRequest = net.request({
  1410. url: serverUrl
  1411. });
  1412. urlRequest.end();
  1413. urlRequest.on('redirect', () => { urlRequest.abort(); });
  1414. urlRequest.on('error', () => {});
  1415. urlRequest.on('response', () => {
  1416. expect.fail('Unexpected response');
  1417. });
  1418. await once(urlRequest, 'abort');
  1419. });
  1420. it('should not follow redirect when mode is error', async () => {
  1421. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1422. response.statusCode = 302;
  1423. response.setHeader('Location', '/200');
  1424. response.end();
  1425. });
  1426. const urlRequest = net.request({
  1427. url: serverUrl,
  1428. redirect: 'error'
  1429. });
  1430. urlRequest.end();
  1431. await once(urlRequest, 'error');
  1432. });
  1433. it('should follow redirect when handler calls callback', async () => {
  1434. const serverUrl = await respondOnce.toRoutes({
  1435. '/redirectChain': (request, response) => {
  1436. response.statusCode = 302;
  1437. response.setHeader('Location', '/302');
  1438. response.end();
  1439. },
  1440. '/302': (request, response) => {
  1441. response.statusCode = 302;
  1442. response.setHeader('Location', '/200');
  1443. response.end();
  1444. },
  1445. '/200': (request, response) => {
  1446. response.statusCode = 200;
  1447. response.end();
  1448. }
  1449. });
  1450. const urlRequest = net.request({ url: `${serverUrl}/redirectChain`, redirect: 'manual' });
  1451. const redirects: string[] = [];
  1452. urlRequest.on('redirect', (status, method, url) => {
  1453. redirects.push(url);
  1454. urlRequest.followRedirect();
  1455. });
  1456. const response = await getResponse(urlRequest);
  1457. expect(response.statusCode).to.equal(200);
  1458. expect(redirects).to.deep.equal([
  1459. `${serverUrl}/302`,
  1460. `${serverUrl}/200`
  1461. ]);
  1462. });
  1463. it('should throw if given an invalid session option', () => {
  1464. expect(() => {
  1465. net.request({
  1466. url: 'https://foo',
  1467. session: 1 as any
  1468. });
  1469. }).to.throw('`session` should be an instance of the Session class');
  1470. });
  1471. it('should throw if given an invalid partition option', () => {
  1472. expect(() => {
  1473. net.request({
  1474. url: 'https://foo',
  1475. partition: 1 as any
  1476. });
  1477. }).to.throw('`partition` should be a string');
  1478. });
  1479. it('should be able to create a request with options', async () => {
  1480. const customHeaderName = 'Some-Custom-Header-Name';
  1481. const customHeaderValue = 'Some-Customer-Header-Value';
  1482. const serverUrlUnparsed = await respondOnce.toURL('/', (request, response) => {
  1483. expect(request.method).to.equal('GET');
  1484. expect(request.headers[customHeaderName.toLowerCase()]).to.equal(customHeaderValue);
  1485. response.statusCode = 200;
  1486. response.statusMessage = 'OK';
  1487. response.end();
  1488. });
  1489. const serverUrl = url.parse(serverUrlUnparsed);
  1490. const options = {
  1491. port: serverUrl.port ? parseInt(serverUrl.port, 10) : undefined,
  1492. hostname: '127.0.0.1',
  1493. headers: { [customHeaderName]: customHeaderValue }
  1494. };
  1495. const urlRequest = net.request(options);
  1496. const response = await getResponse(urlRequest);
  1497. expect(response.statusCode).to.be.equal(200);
  1498. await collectStreamBody(response);
  1499. });
  1500. it('should be able to pipe a readable stream into a net request', async () => {
  1501. const bodyData = randomString(kOneMegaByte);
  1502. let netRequestReceived = false;
  1503. let netRequestEnded = false;
  1504. const [nodeServerUrl, netServerUrl] = await Promise.all([
  1505. respondOnce.toSingleURL((request, response) => response.end(bodyData)),
  1506. respondOnce.toSingleURL((request, response) => {
  1507. netRequestReceived = true;
  1508. let receivedBodyData = '';
  1509. request.on('data', (chunk) => {
  1510. receivedBodyData += chunk.toString();
  1511. });
  1512. request.on('end', (chunk: Buffer | undefined) => {
  1513. netRequestEnded = true;
  1514. if (chunk) {
  1515. receivedBodyData += chunk.toString();
  1516. }
  1517. expect(receivedBodyData).to.be.equal(bodyData);
  1518. response.end();
  1519. });
  1520. })
  1521. ]);
  1522. const nodeRequest = http.request(nodeServerUrl);
  1523. const nodeResponse = await getResponse(nodeRequest as any) as any as http.ServerResponse;
  1524. const netRequest = net.request(netServerUrl);
  1525. const responsePromise = once(netRequest, 'response');
  1526. // TODO(@MarshallOfSound) - FIXME with #22730
  1527. nodeResponse.pipe(netRequest as any);
  1528. const [netResponse] = await responsePromise;
  1529. expect(netResponse.statusCode).to.equal(200);
  1530. await collectStreamBody(netResponse);
  1531. expect(netRequestReceived).to.be.true('net request received');
  1532. expect(netRequestEnded).to.be.true('net request ended');
  1533. });
  1534. it('should report upload progress', async () => {
  1535. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1536. response.end();
  1537. });
  1538. const netRequest = net.request({ url: serverUrl, method: 'POST' });
  1539. expect(netRequest.getUploadProgress()).to.have.property('active', false);
  1540. netRequest.end(Buffer.from('hello'));
  1541. const [position, total] = await once(netRequest, 'upload-progress');
  1542. expect(netRequest.getUploadProgress()).to.deep.equal({ active: true, started: true, current: position, total });
  1543. });
  1544. it('should emit error event on server socket destroy', async () => {
  1545. const serverUrl = await respondOnce.toSingleURL((request) => {
  1546. request.socket.destroy();
  1547. });
  1548. const urlRequest = net.request(serverUrl);
  1549. urlRequest.end();
  1550. const [error] = await once(urlRequest, 'error');
  1551. expect(error.message).to.equal('net::ERR_EMPTY_RESPONSE');
  1552. });
  1553. it('should emit error event on server request destroy', async () => {
  1554. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1555. request.destroy();
  1556. response.end();
  1557. });
  1558. const urlRequest = net.request(serverUrl);
  1559. urlRequest.end(randomBuffer(kOneMegaByte));
  1560. const [error] = await once(urlRequest, 'error');
  1561. expect(error.message).to.be.oneOf(['net::ERR_FAILED', 'net::ERR_CONNECTION_RESET', 'net::ERR_CONNECTION_ABORTED']);
  1562. });
  1563. it('should not emit any event after close', async () => {
  1564. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1565. response.end();
  1566. });
  1567. const urlRequest = net.request(serverUrl);
  1568. urlRequest.end();
  1569. await once(urlRequest, 'close');
  1570. await new Promise((resolve, reject) => {
  1571. for (const evName of ['finish', 'abort', 'close', 'error']) {
  1572. urlRequest.on(evName as any, () => {
  1573. reject(new Error(`Unexpected ${evName} event`));
  1574. });
  1575. }
  1576. setTimeout(50).then(resolve);
  1577. });
  1578. });
  1579. it('should remove the referer header when no referrer url specified', async () => {
  1580. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1581. expect(request.headers.referer).to.equal(undefined);
  1582. response.statusCode = 200;
  1583. response.statusMessage = 'OK';
  1584. response.end();
  1585. });
  1586. const urlRequest = net.request(serverUrl);
  1587. urlRequest.end();
  1588. const response = await getResponse(urlRequest);
  1589. expect(response.statusCode).to.equal(200);
  1590. await collectStreamBody(response);
  1591. });
  1592. it('should set the referer header when a referrer url specified', async () => {
  1593. const referrerURL = 'https://www.electronjs.org/';
  1594. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1595. expect(request.headers.referer).to.equal(referrerURL);
  1596. response.statusCode = 200;
  1597. response.statusMessage = 'OK';
  1598. response.end();
  1599. });
  1600. // The referrerPolicy must be unsafe-url because the referrer's origin
  1601. // doesn't match the loaded page. With the default referrer policy
  1602. // (strict-origin-when-cross-origin), the request will be canceled by the
  1603. // network service when the referrer header is invalid.
  1604. // See:
  1605. // - https://source.chromium.org/chromium/chromium/src/+/main:net/url_request/url_request.cc;l=682-683;drc=ae587fa7cd2e5cc308ce69353ee9ce86437e5d41
  1606. // - https://source.chromium.org/chromium/chromium/src/+/main:services/network/public/mojom/network_context.mojom;l=316-318;drc=ae5c7fcf09509843c1145f544cce3a61874b9698
  1607. // - https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer
  1608. const urlRequest = net.request({ url: serverUrl, referrerPolicy: 'unsafe-url' });
  1609. urlRequest.setHeader('referer', referrerURL);
  1610. urlRequest.end();
  1611. const response = await getResponse(urlRequest);
  1612. expect(response.statusCode).to.equal(200);
  1613. await collectStreamBody(response);
  1614. });
  1615. });
  1616. describe('IncomingMessage API', () => {
  1617. it('response object should implement the IncomingMessage API', async () => {
  1618. const customHeaderName = 'Some-Custom-Header-Name';
  1619. const customHeaderValue = 'Some-Customer-Header-Value';
  1620. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1621. response.statusCode = 200;
  1622. response.statusMessage = 'OK';
  1623. response.setHeader(customHeaderName, customHeaderValue);
  1624. response.end();
  1625. });
  1626. const urlRequest = net.request(serverUrl);
  1627. const response = await getResponse(urlRequest);
  1628. expect(response.statusCode).to.equal(200);
  1629. expect(response.statusMessage).to.equal('OK');
  1630. const headers = response.headers;
  1631. expect(headers).to.be.an('object');
  1632. const headerValue = headers[customHeaderName.toLowerCase()];
  1633. expect(headerValue).to.equal(customHeaderValue);
  1634. const rawHeaders = response.rawHeaders;
  1635. expect(rawHeaders).to.be.an('array');
  1636. expect(rawHeaders[0]).to.equal(customHeaderName);
  1637. expect(rawHeaders[1]).to.equal(customHeaderValue);
  1638. const httpVersion = response.httpVersion;
  1639. expect(httpVersion).to.be.a('string').and.to.have.lengthOf.at.least(1);
  1640. const httpVersionMajor = response.httpVersionMajor;
  1641. expect(httpVersionMajor).to.be.a('number').and.to.be.at.least(1);
  1642. const httpVersionMinor = response.httpVersionMinor;
  1643. expect(httpVersionMinor).to.be.a('number').and.to.be.at.least(0);
  1644. await collectStreamBody(response);
  1645. });
  1646. it('should discard duplicate headers', async () => {
  1647. const includedHeader = 'max-forwards';
  1648. const discardableHeader = 'Max-Forwards';
  1649. const includedHeaderValue = 'max-fwds-val';
  1650. const discardableHeaderValue = 'max-fwds-val-two';
  1651. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1652. response.statusCode = 200;
  1653. response.statusMessage = 'OK';
  1654. response.setHeader(discardableHeader, discardableHeaderValue);
  1655. response.setHeader(includedHeader, includedHeaderValue);
  1656. response.end();
  1657. });
  1658. const urlRequest = net.request(serverUrl);
  1659. const response = await getResponse(urlRequest);
  1660. expect(response.statusCode).to.equal(200);
  1661. expect(response.statusMessage).to.equal('OK');
  1662. const headers = response.headers;
  1663. expect(headers).to.be.an('object');
  1664. expect(headers).to.have.property(includedHeader);
  1665. expect(headers).to.not.have.property(discardableHeader);
  1666. expect(headers[includedHeader]).to.equal(includedHeaderValue);
  1667. await collectStreamBody(response);
  1668. });
  1669. it('should join repeated non-discardable header values with ,', async () => {
  1670. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1671. response.statusCode = 200;
  1672. response.statusMessage = 'OK';
  1673. response.setHeader('referrer-policy', ['first-text', 'second-text']);
  1674. response.end();
  1675. });
  1676. const urlRequest = net.request(serverUrl);
  1677. const response = await getResponse(urlRequest);
  1678. expect(response.statusCode).to.equal(200);
  1679. expect(response.statusMessage).to.equal('OK');
  1680. const headers = response.headers;
  1681. expect(headers).to.be.an('object');
  1682. expect(headers).to.have.property('referrer-policy');
  1683. expect(headers['referrer-policy']).to.equal('first-text, second-text');
  1684. await collectStreamBody(response);
  1685. });
  1686. it('should not join repeated discardable header values with ,', async () => {
  1687. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1688. response.statusCode = 200;
  1689. response.statusMessage = 'OK';
  1690. response.setHeader('last-modified', ['yesterday', 'today']);
  1691. response.end();
  1692. });
  1693. const urlRequest = net.request(serverUrl);
  1694. const response = await getResponse(urlRequest);
  1695. expect(response.statusCode).to.equal(200);
  1696. expect(response.statusMessage).to.equal('OK');
  1697. const headers = response.headers;
  1698. expect(headers).to.be.an('object');
  1699. expect(headers).to.have.property('last-modified');
  1700. expect(headers['last-modified']).to.equal('yesterday');
  1701. await collectStreamBody(response);
  1702. });
  1703. it('should make set-cookie header an array even if single value', async () => {
  1704. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1705. response.statusCode = 200;
  1706. response.statusMessage = 'OK';
  1707. response.setHeader('set-cookie', 'chocolate-chip');
  1708. response.end();
  1709. });
  1710. const urlRequest = net.request(serverUrl);
  1711. const response = await getResponse(urlRequest);
  1712. expect(response.statusCode).to.equal(200);
  1713. expect(response.statusMessage).to.equal('OK');
  1714. const headers = response.headers;
  1715. expect(headers).to.be.an('object');
  1716. expect(headers).to.have.property('set-cookie');
  1717. expect(headers['set-cookie']).to.be.an('array');
  1718. expect(headers['set-cookie'][0]).to.equal('chocolate-chip');
  1719. await collectStreamBody(response);
  1720. });
  1721. it('should keep set-cookie header an array when an array', async () => {
  1722. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1723. response.statusCode = 200;
  1724. response.statusMessage = 'OK';
  1725. response.setHeader('set-cookie', ['chocolate-chip', 'oatmeal']);
  1726. response.end();
  1727. });
  1728. const urlRequest = net.request(serverUrl);
  1729. const response = await getResponse(urlRequest);
  1730. expect(response.statusCode).to.equal(200);
  1731. expect(response.statusMessage).to.equal('OK');
  1732. const headers = response.headers;
  1733. expect(headers).to.be.an('object');
  1734. expect(headers).to.have.property('set-cookie');
  1735. expect(headers['set-cookie']).to.be.an('array');
  1736. expect(headers['set-cookie'][0]).to.equal('chocolate-chip');
  1737. expect(headers['set-cookie'][1]).to.equal('oatmeal');
  1738. await collectStreamBody(response);
  1739. });
  1740. it('should lowercase header keys', async () => {
  1741. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1742. response.statusCode = 200;
  1743. response.statusMessage = 'OK';
  1744. response.setHeader('HEADER-KEY', ['header-value']);
  1745. response.setHeader('SeT-CookiE', ['chocolate-chip', 'oatmeal']);
  1746. response.setHeader('rEFERREr-pOLICy', ['first-text', 'second-text']);
  1747. response.setHeader('LAST-modified', 'yesterday');
  1748. response.end();
  1749. });
  1750. const urlRequest = net.request(serverUrl);
  1751. const response = await getResponse(urlRequest);
  1752. expect(response.statusCode).to.equal(200);
  1753. expect(response.statusMessage).to.equal('OK');
  1754. const headers = response.headers;
  1755. expect(headers).to.be.an('object');
  1756. expect(headers).to.have.property('header-key');
  1757. expect(headers).to.have.property('set-cookie');
  1758. expect(headers).to.have.property('referrer-policy');
  1759. expect(headers).to.have.property('last-modified');
  1760. await collectStreamBody(response);
  1761. });
  1762. it('should return correct raw headers', async () => {
  1763. const customHeaders: [string, string|string[]][] = [
  1764. ['HEADER-KEY-ONE', 'header-value-one'],
  1765. ['set-cookie', 'chocolate-chip'],
  1766. ['header-key-two', 'header-value-two'],
  1767. ['referrer-policy', ['first-text', 'second-text']],
  1768. ['HEADER-KEY-THREE', 'header-value-three'],
  1769. ['last-modified', ['first-text', 'second-text']],
  1770. ['header-key-four', 'header-value-four']
  1771. ];
  1772. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1773. response.statusCode = 200;
  1774. response.statusMessage = 'OK';
  1775. for (const headerTuple of customHeaders) {
  1776. response.setHeader(headerTuple[0], headerTuple[1]);
  1777. }
  1778. response.end();
  1779. });
  1780. const urlRequest = net.request(serverUrl);
  1781. const response = await getResponse(urlRequest);
  1782. expect(response.statusCode).to.equal(200);
  1783. expect(response.statusMessage).to.equal('OK');
  1784. const rawHeaders = response.rawHeaders;
  1785. expect(rawHeaders).to.be.an('array');
  1786. let rawHeadersIdx = 0;
  1787. for (const headerTuple of customHeaders) {
  1788. const headerKey = headerTuple[0];
  1789. const headerValues = Array.isArray(headerTuple[1]) ? headerTuple[1] : [headerTuple[1]];
  1790. for (const headerValue of headerValues) {
  1791. expect(rawHeaders[rawHeadersIdx]).to.equal(headerKey);
  1792. expect(rawHeaders[rawHeadersIdx + 1]).to.equal(headerValue);
  1793. rawHeadersIdx += 2;
  1794. }
  1795. }
  1796. await collectStreamBody(response);
  1797. });
  1798. it('should be able to pipe a net response into a writable stream', async () => {
  1799. const bodyData = randomString(kOneKiloByte);
  1800. let nodeRequestProcessed = false;
  1801. const [netServerUrl, nodeServerUrl] = await Promise.all([
  1802. respondOnce.toSingleURL((request, response) => response.end(bodyData)),
  1803. respondOnce.toSingleURL(async (request, response) => {
  1804. const receivedBodyData = await collectStreamBody(request);
  1805. expect(receivedBodyData).to.be.equal(bodyData);
  1806. nodeRequestProcessed = true;
  1807. response.end();
  1808. })
  1809. ]);
  1810. const netRequest = net.request(netServerUrl);
  1811. const netResponse = await getResponse(netRequest);
  1812. const serverUrl = url.parse(nodeServerUrl);
  1813. const nodeOptions = {
  1814. method: 'POST',
  1815. path: serverUrl.path,
  1816. port: serverUrl.port
  1817. };
  1818. const nodeRequest = http.request(nodeOptions);
  1819. const nodeResponsePromise = once(nodeRequest, 'response');
  1820. // TODO(@MarshallOfSound) - FIXME with #22730
  1821. (netResponse as any).pipe(nodeRequest);
  1822. const [nodeResponse] = await nodeResponsePromise;
  1823. netRequest.end();
  1824. await collectStreamBody(nodeResponse);
  1825. expect(nodeRequestProcessed).to.equal(true);
  1826. });
  1827. it('should correctly throttle an incoming stream', async () => {
  1828. let numChunksSent = 0;
  1829. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1830. const data = randomString(kOneMegaByte);
  1831. const write = () => {
  1832. let ok = true;
  1833. do {
  1834. numChunksSent++;
  1835. if (numChunksSent > 30) return;
  1836. ok = response.write(data);
  1837. } while (ok);
  1838. response.once('drain', write);
  1839. };
  1840. write();
  1841. });
  1842. const urlRequest = net.request(serverUrl);
  1843. urlRequest.on('response', () => {});
  1844. urlRequest.end();
  1845. await setTimeout(2000);
  1846. // TODO(nornagon): I think this ought to max out at 20, but in practice
  1847. // it seems to exceed that sometimes. This is at 25 to avoid test flakes,
  1848. // but we should investigate if there's actually something broken here and
  1849. // if so fix it and reset this to max at 20, and if not then delete this
  1850. // comment.
  1851. expect(numChunksSent).to.be.at.most(25);
  1852. });
  1853. });
  1854. describe('net.isOnline', () => {
  1855. it('getter returns boolean', () => {
  1856. expect(net.isOnline()).to.be.a('boolean');
  1857. });
  1858. it('property returns boolean', () => {
  1859. expect(net.online).to.be.a('boolean');
  1860. });
  1861. });
  1862. describe('Stability and performance', () => {
  1863. it('should free unreferenced, never-started request objects without crash', (done) => {
  1864. net.request('https://test');
  1865. process.nextTick(() => {
  1866. const v8Util = process._linkedBinding('electron_common_v8_util');
  1867. v8Util.requestGarbageCollectionForTesting();
  1868. done();
  1869. });
  1870. });
  1871. it('should collect on-going requests without crash', async () => {
  1872. let finishResponse: (() => void) | null = null;
  1873. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1874. response.write(randomString(kOneKiloByte));
  1875. finishResponse = () => {
  1876. response.write(randomString(kOneKiloByte));
  1877. response.end();
  1878. };
  1879. });
  1880. const urlRequest = net.request(serverUrl);
  1881. const response = await getResponse(urlRequest);
  1882. process.nextTick(() => {
  1883. // Trigger a garbage collection.
  1884. const v8Util = process._linkedBinding('electron_common_v8_util');
  1885. v8Util.requestGarbageCollectionForTesting();
  1886. finishResponse!();
  1887. });
  1888. await collectStreamBody(response);
  1889. });
  1890. it('should collect unreferenced, ended requests without crash', async () => {
  1891. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  1892. response.end();
  1893. });
  1894. const urlRequest = net.request(serverUrl);
  1895. process.nextTick(() => {
  1896. const v8Util = process._linkedBinding('electron_common_v8_util');
  1897. v8Util.requestGarbageCollectionForTesting();
  1898. });
  1899. const response = await getResponse(urlRequest);
  1900. await collectStreamBody(response);
  1901. });
  1902. it('should finish sending data when urlRequest is unreferenced', async () => {
  1903. const serverUrl = await respondOnce.toSingleURL(async (request, response) => {
  1904. const received = await collectStreamBodyBuffer(request);
  1905. expect(received.length).to.equal(kOneMegaByte);
  1906. response.end();
  1907. });
  1908. const urlRequest = net.request(serverUrl);
  1909. urlRequest.on('close', () => {
  1910. process.nextTick(() => {
  1911. const v8Util = process._linkedBinding('electron_common_v8_util');
  1912. v8Util.requestGarbageCollectionForTesting();
  1913. });
  1914. });
  1915. urlRequest.write(randomBuffer(kOneMegaByte));
  1916. const response = await getResponse(urlRequest);
  1917. await collectStreamBody(response);
  1918. });
  1919. it('should finish sending data when urlRequest is unreferenced for chunked encoding', async () => {
  1920. const serverUrl = await respondOnce.toSingleURL(async (request, response) => {
  1921. const received = await collectStreamBodyBuffer(request);
  1922. response.end();
  1923. expect(received.length).to.equal(kOneMegaByte);
  1924. });
  1925. const urlRequest = net.request(serverUrl);
  1926. urlRequest.chunkedEncoding = true;
  1927. urlRequest.write(randomBuffer(kOneMegaByte));
  1928. const response = await getResponse(urlRequest);
  1929. await collectStreamBody(response);
  1930. process.nextTick(() => {
  1931. const v8Util = process._linkedBinding('electron_common_v8_util');
  1932. v8Util.requestGarbageCollectionForTesting();
  1933. });
  1934. });
  1935. it('should finish sending data when urlRequest is unreferenced before close event for chunked encoding', async () => {
  1936. const serverUrl = await respondOnce.toSingleURL(async (request, response) => {
  1937. const received = await collectStreamBodyBuffer(request);
  1938. response.end();
  1939. expect(received.length).to.equal(kOneMegaByte);
  1940. });
  1941. const urlRequest = net.request(serverUrl);
  1942. urlRequest.chunkedEncoding = true;
  1943. urlRequest.write(randomBuffer(kOneMegaByte));
  1944. const v8Util = process._linkedBinding('electron_common_v8_util');
  1945. v8Util.requestGarbageCollectionForTesting();
  1946. await collectStreamBody(await getResponse(urlRequest));
  1947. });
  1948. it('should finish sending data when urlRequest is unreferenced', async () => {
  1949. const serverUrl = await respondOnce.toSingleURL(async (request, response) => {
  1950. const received = await collectStreamBodyBuffer(request);
  1951. response.end();
  1952. expect(received.length).to.equal(kOneMegaByte);
  1953. });
  1954. const urlRequest = net.request(serverUrl);
  1955. urlRequest.on('close', () => {
  1956. process.nextTick(() => {
  1957. const v8Util = process._linkedBinding('electron_common_v8_util');
  1958. v8Util.requestGarbageCollectionForTesting();
  1959. });
  1960. });
  1961. urlRequest.write(randomBuffer(kOneMegaByte));
  1962. await collectStreamBody(await getResponse(urlRequest));
  1963. });
  1964. it('should finish sending data when urlRequest is unreferenced for chunked encoding', async () => {
  1965. const serverUrl = await respondOnce.toSingleURL(async (request, response) => {
  1966. const received = await collectStreamBodyBuffer(request);
  1967. response.end();
  1968. expect(received.length).to.equal(kOneMegaByte);
  1969. });
  1970. const urlRequest = net.request(serverUrl);
  1971. urlRequest.on('close', () => {
  1972. process.nextTick(() => {
  1973. const v8Util = process._linkedBinding('electron_common_v8_util');
  1974. v8Util.requestGarbageCollectionForTesting();
  1975. });
  1976. });
  1977. urlRequest.chunkedEncoding = true;
  1978. urlRequest.write(randomBuffer(kOneMegaByte));
  1979. await collectStreamBody(await getResponse(urlRequest));
  1980. });
  1981. });
  1982. describe('non-http schemes', () => {
  1983. it('should be rejected by net.request', async () => {
  1984. expect(() => {
  1985. net.request('file://bar');
  1986. }).to.throw('ClientRequest only supports http: and https: protocols');
  1987. });
  1988. it('should be rejected by net.request when passed in url:', async () => {
  1989. expect(() => {
  1990. net.request({ url: 'file://bar' });
  1991. }).to.throw('ClientRequest only supports http: and https: protocols');
  1992. });
  1993. });
  1994. describe('net.fetch', () => {
  1995. // NB. there exist much more comprehensive tests for fetch() in the form of
  1996. // the WPT: https://github.com/web-platform-tests/wpt/tree/master/fetch
  1997. // It's possible to run these tests against net.fetch(), but the test
  1998. // harness to do so is quite complex and hasn't been munged to smoothly run
  1999. // inside the Electron test runner yet.
  2000. //
  2001. // In the meantime, here are some tests for basic functionality and
  2002. // Electron-specific behavior.
  2003. describe('basic', () => {
  2004. it('can fetch http urls', async () => {
  2005. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  2006. response.end('test');
  2007. });
  2008. const resp = await net.fetch(serverUrl);
  2009. expect(resp.ok).to.be.true();
  2010. expect(await resp.text()).to.equal('test');
  2011. });
  2012. it('can upload a string body', async () => {
  2013. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  2014. request.on('data', chunk => response.write(chunk));
  2015. request.on('end', () => response.end());
  2016. });
  2017. const resp = await net.fetch(serverUrl, {
  2018. method: 'POST',
  2019. body: 'anchovies'
  2020. });
  2021. expect(await resp.text()).to.equal('anchovies');
  2022. });
  2023. it('can read response as an array buffer', async () => {
  2024. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  2025. request.on('data', chunk => response.write(chunk));
  2026. request.on('end', () => response.end());
  2027. });
  2028. const resp = await net.fetch(serverUrl, {
  2029. method: 'POST',
  2030. body: 'anchovies'
  2031. });
  2032. expect(new TextDecoder().decode(new Uint8Array(await resp.arrayBuffer()))).to.equal('anchovies');
  2033. });
  2034. it('can read response as form data', async () => {
  2035. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  2036. response.setHeader('content-type', 'application/x-www-form-urlencoded');
  2037. response.end('foo=bar');
  2038. });
  2039. const resp = await net.fetch(serverUrl);
  2040. const result = await resp.formData();
  2041. expect(result.get('foo')).to.equal('bar');
  2042. });
  2043. it('should be able to use a session cookie store', async () => {
  2044. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  2045. response.statusCode = 200;
  2046. response.statusMessage = 'OK';
  2047. response.setHeader('x-cookie', request.headers.cookie!);
  2048. response.end();
  2049. });
  2050. const sess = session.fromPartition(`cookie-tests-${Math.random()}`);
  2051. const cookieVal = `${Date.now()}`;
  2052. await sess.cookies.set({
  2053. url: serverUrl,
  2054. name: 'wild_cookie',
  2055. value: cookieVal
  2056. });
  2057. const response = await sess.fetch(serverUrl, {
  2058. credentials: 'include'
  2059. });
  2060. expect(response.headers.get('x-cookie')).to.equal(`wild_cookie=${cookieVal}`);
  2061. });
  2062. it('should reject promise on DNS failure', async () => {
  2063. const r = net.fetch('https://i.do.not.exist');
  2064. await expect(r).to.be.rejectedWith(/ERR_NAME_NOT_RESOLVED/);
  2065. });
  2066. it('should reject body promise when stream fails', async () => {
  2067. const serverUrl = await respondOnce.toSingleURL((request, response) => {
  2068. response.write('first chunk');
  2069. setTimeout().then(() => response.destroy());
  2070. });
  2071. const r = await net.fetch(serverUrl);
  2072. expect(r.status).to.equal(200);
  2073. await expect(r.text()).to.be.rejectedWith(/ERR_INCOMPLETE_CHUNKED_ENCODING/);
  2074. });
  2075. });
  2076. it('can request file:// URLs', async () => {
  2077. const resp = await net.fetch(url.pathToFileURL(path.join(__dirname, 'fixtures', 'hello.txt')).toString());
  2078. expect(resp.ok).to.be.true();
  2079. // trimRight instead of asserting the whole string to avoid line ending shenanigans on WOA
  2080. expect((await resp.text()).trimRight()).to.equal('hello world');
  2081. });
  2082. it('can make requests to custom protocols', async () => {
  2083. protocol.registerStringProtocol('electron-test', (req, cb) => { cb('hello ' + req.url); });
  2084. defer(() => {
  2085. protocol.unregisterProtocol('electron-test');
  2086. });
  2087. const body = await net.fetch('electron-test://foo').then(r => r.text());
  2088. expect(body).to.equal('hello electron-test://foo');
  2089. });
  2090. it('runs through intercept handlers', async () => {
  2091. protocol.interceptStringProtocol('http', (req, cb) => { cb('hello ' + req.url); });
  2092. defer(() => {
  2093. protocol.uninterceptProtocol('http');
  2094. });
  2095. const body = await net.fetch('http://foo').then(r => r.text());
  2096. expect(body).to.equal('hello http://foo/');
  2097. });
  2098. it('file: runs through intercept handlers', async () => {
  2099. protocol.interceptStringProtocol('file', (req, cb) => { cb('hello ' + req.url); });
  2100. defer(() => {
  2101. protocol.uninterceptProtocol('file');
  2102. });
  2103. const body = await net.fetch('file://foo').then(r => r.text());
  2104. expect(body).to.equal('hello file://foo/');
  2105. });
  2106. it('can be redirected', async () => {
  2107. protocol.interceptStringProtocol('file', (req, cb) => { cb({ statusCode: 302, headers: { location: 'electron-test://bar' } }); });
  2108. defer(() => {
  2109. protocol.uninterceptProtocol('file');
  2110. });
  2111. protocol.registerStringProtocol('electron-test', (req, cb) => { cb('hello ' + req.url); });
  2112. defer(() => {
  2113. protocol.unregisterProtocol('electron-test');
  2114. });
  2115. const body = await net.fetch('file://foo').then(r => r.text());
  2116. expect(body).to.equal('hello electron-test://bar');
  2117. });
  2118. it('should not follow redirect when redirect: error', async () => {
  2119. protocol.registerStringProtocol('electron-test', (req, cb) => {
  2120. if (/redirect/.test(req.url)) return cb({ statusCode: 302, headers: { location: 'electron-test://bar' } });
  2121. cb('hello ' + req.url);
  2122. });
  2123. defer(() => {
  2124. protocol.unregisterProtocol('electron-test');
  2125. });
  2126. await expect(net.fetch('electron-test://redirect', { redirect: 'error' })).to.eventually.be.rejectedWith('Attempted to redirect, but redirect policy was \'error\'');
  2127. });
  2128. it('a 307 redirected POST request preserves the body', async () => {
  2129. const bodyData = 'Hello World!';
  2130. let postedBodyData: any;
  2131. protocol.registerStringProtocol('electron-test', async (req, cb) => {
  2132. if (/redirect/.test(req.url)) return cb({ statusCode: 307, headers: { location: 'electron-test://bar' } });
  2133. postedBodyData = req.uploadData![0].bytes.toString();
  2134. cb('hello ' + req.url);
  2135. });
  2136. defer(() => {
  2137. protocol.unregisterProtocol('electron-test');
  2138. });
  2139. const response = await net.fetch('electron-test://redirect', {
  2140. method: 'POST',
  2141. body: bodyData
  2142. });
  2143. expect(response.status).to.equal(200);
  2144. await response.text();
  2145. expect(postedBodyData).to.equal(bodyData);
  2146. });
  2147. });
  2148. });