api-session-spec.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962
  1. const assert = require('assert')
  2. const chai = require('chai')
  3. const http = require('http')
  4. const https = require('https')
  5. const path = require('path')
  6. const fs = require('fs')
  7. const send = require('send')
  8. const auth = require('basic-auth')
  9. const ChildProcess = require('child_process')
  10. const { closeWindow } = require('./window-helpers')
  11. const { ipcRenderer, remote } = require('electron')
  12. const { ipcMain, session, BrowserWindow, net } = remote
  13. const { expect } = chai
  14. /* The whole session API doesn't use standard callbacks */
  15. /* eslint-disable standard/no-callback-literal */
  16. describe('session module', () => {
  17. let fixtures = path.resolve(__dirname, 'fixtures')
  18. let w = null
  19. let webview = null
  20. const url = 'http://127.0.0.1'
  21. beforeEach(() => {
  22. w = new BrowserWindow({
  23. show: false,
  24. width: 400,
  25. height: 400,
  26. webPreferences: {
  27. nodeIntegration: true
  28. }
  29. })
  30. })
  31. afterEach(() => {
  32. if (webview != null) {
  33. if (!document.body.contains(webview)) {
  34. document.body.appendChild(webview)
  35. }
  36. webview.remove()
  37. }
  38. return closeWindow(w).then(() => { w = null })
  39. })
  40. describe('session.defaultSession', () => {
  41. it('returns the default session', () => {
  42. assert.strictEqual(session.defaultSession, session.fromPartition(''))
  43. })
  44. })
  45. describe('session.fromPartition(partition, options)', () => {
  46. it('returns existing session with same partition', () => {
  47. assert.strictEqual(session.fromPartition('test'), session.fromPartition('test'))
  48. })
  49. it('created session is ref-counted', () => {
  50. const partition = 'test2'
  51. const userAgent = 'test-agent'
  52. const ses1 = session.fromPartition(partition)
  53. ses1.setUserAgent(userAgent)
  54. assert.strictEqual(ses1.getUserAgent(), userAgent)
  55. ses1.destroy()
  56. const ses2 = session.fromPartition(partition)
  57. assert.notStrictEqual(ses2.getUserAgent(), userAgent)
  58. })
  59. })
  60. describe('ses.cookies', () => {
  61. const name = '0'
  62. const value = '0'
  63. it('should get cookies with promises', (done) => {
  64. const server = http.createServer((req, res) => {
  65. res.setHeader('Set-Cookie', [`${name}=${value}`])
  66. res.end('finished')
  67. server.close()
  68. })
  69. server.listen(0, '127.0.0.1', () => {
  70. w.webContents.once('did-finish-load', async () => {
  71. const list = await w.webContents.session.cookies.get({ url })
  72. const cookie = list.find(cookie => cookie.name === name)
  73. expect(cookie).to.exist.and.to.have.property('value', value)
  74. done()
  75. })
  76. const { port } = server.address()
  77. w.loadURL(`${url}:${port}`)
  78. })
  79. })
  80. it('should get cookies with callbacks', (done) => {
  81. const server = http.createServer((req, res) => {
  82. res.setHeader('Set-Cookie', [`${name}=${value}`])
  83. res.end('finished')
  84. server.close()
  85. })
  86. server.listen(0, '127.0.0.1', () => {
  87. w.webContents.once('did-finish-load', () => {
  88. w.webContents.session.cookies.get({ url }, (error, list) => {
  89. if (error) return done(error)
  90. const cookie = list.find(cookie => cookie.name === name)
  91. expect(cookie).to.exist.and.to.have.property('value', value)
  92. done()
  93. })
  94. })
  95. const { port } = server.address()
  96. w.loadURL(`${url}:${port}`)
  97. })
  98. })
  99. it('sets cookies with promises', async () => {
  100. let error
  101. try {
  102. const { cookies } = session.defaultSession
  103. const name = '1'
  104. const value = '1'
  105. await cookies.set({ url, name, value })
  106. } catch (e) {
  107. error = e
  108. }
  109. expect(error).to.be.undefined(error)
  110. })
  111. it('sets cookies with callbacks', (done) => {
  112. const { cookies } = session.defaultSession
  113. const name = '1'
  114. const value = '1'
  115. cookies.set({ url, name, value }, (error, list) => done(error))
  116. })
  117. it('yields an error when setting a cookie with missing required fields', async () => {
  118. let error
  119. try {
  120. const { cookies } = session.defaultSession
  121. const name = '1'
  122. const value = '1'
  123. await cookies.set({ url: '', name, value })
  124. } catch (e) {
  125. error = e
  126. }
  127. expect(error).is.an('Error')
  128. expect(error).to.have.property('message').which.equals('Setting cookie failed')
  129. })
  130. it('yields an error when setting a cookie with an invalid URL', async () => {
  131. let error
  132. try {
  133. const { cookies } = session.defaultSession
  134. const name = '1'
  135. const value = '1'
  136. await cookies.set({ url: 'asdf', name, value })
  137. } catch (e) {
  138. error = e
  139. }
  140. expect(error).is.an('Error')
  141. expect(error).to.have.property('message').which.equals('Setting cookie failed')
  142. })
  143. it('should overwrite previous cookies', async () => {
  144. let error
  145. try {
  146. const { cookies } = session.defaultSession
  147. const name = 'DidOverwrite'
  148. for (const value of [ 'No', 'Yes' ]) {
  149. await cookies.set({ url, name, value })
  150. const list = await cookies.get({ url })
  151. assert(list.some(cookie => cookie.name === name && cookie.value === value))
  152. }
  153. } catch (e) {
  154. error = e
  155. }
  156. expect(error).to.be.undefined(error)
  157. })
  158. it('should remove cookies with promises', async () => {
  159. let error
  160. try {
  161. const { cookies } = session.defaultSession
  162. const name = '2'
  163. const value = '2'
  164. await cookies.set({ url, name, value })
  165. await cookies.remove(url, name)
  166. const list = await cookies.get({ url })
  167. assert(!list.some(cookie => cookie.name === name && cookie.value === value))
  168. } catch (e) {
  169. error = e
  170. }
  171. expect(error).to.be.undefined(error)
  172. })
  173. it('should remove cookies with callbacks', (done) => {
  174. const { cookies } = session.defaultSession
  175. const name = '2'
  176. const value = '2'
  177. cookies.set({ url, name, value }, (error) => {
  178. if (error) return done(error)
  179. cookies.remove(url, name, (error) => {
  180. if (error) return done(error)
  181. cookies.get({ url }, (error, list) => {
  182. if (error) return done(error)
  183. assert(!list.some(cookie => cookie.name === name))
  184. done()
  185. })
  186. })
  187. })
  188. })
  189. it('should set cookie for standard scheme', async () => {
  190. let error
  191. try {
  192. const { cookies } = session.defaultSession
  193. const standardScheme = remote.getGlobal('standardScheme')
  194. const domain = 'fake-host'
  195. const url = `${standardScheme}://${domain}`
  196. const name = 'custom'
  197. const value = '1'
  198. await cookies.set({ url, name, value })
  199. const list = await cookies.get({ url })
  200. expect(list).to.have.lengthOf(1)
  201. expect(list[0]).to.have.property('name', name)
  202. expect(list[0]).to.have.property('value', value)
  203. expect(list[0]).to.have.property('domain', domain)
  204. } catch (e) {
  205. error = e
  206. }
  207. expect(error).to.be.undefined(error)
  208. })
  209. it('emits a changed event when a cookie is added or removed', async () => {
  210. let error
  211. const changes = []
  212. try {
  213. const { cookies } = session.fromPartition('cookies-changed')
  214. const name = 'foo'
  215. const value = 'bar'
  216. const eventName = 'changed'
  217. const listener = (event, cookie, cause, removed) => { changes.push({ cookie, cause, removed }) }
  218. cookies.on(eventName, listener)
  219. await cookies.set({ url, name, value })
  220. await cookies.remove(url, name)
  221. cookies.off(eventName, listener)
  222. expect(changes).to.have.lengthOf(2)
  223. expect(changes.every(change => change.cookie.name === name))
  224. expect(changes.every(change => change.cookie.value === value))
  225. expect(changes.every(change => change.cause === 'explicit'))
  226. expect(changes[0].removed).to.be.false()
  227. expect(changes[1].removed).to.be.true()
  228. } catch (e) {
  229. error = e
  230. }
  231. expect(error).to.be.undefined(error)
  232. })
  233. describe('ses.cookies.flushStore()', async () => {
  234. describe('flushes the cookies to disk and invokes the callback when done', async () => {
  235. it('with promises', async () => {
  236. let error
  237. try {
  238. const name = 'foo'
  239. const value = 'bar'
  240. const { cookies } = session.defaultSession
  241. await cookies.set({ url, name, value })
  242. await cookies.flushStore()
  243. } catch (e) {
  244. error = e
  245. }
  246. expect(error).to.be.undefined(error)
  247. })
  248. it('with callbacks', (done) => {
  249. const name = 'foo'
  250. const value = 'bar'
  251. const { cookies } = session.defaultSession
  252. cookies.set({ url, name, value }, error => {
  253. if (error) return done(error)
  254. cookies.flushStore(error => done(error))
  255. })
  256. })
  257. })
  258. })
  259. it('should survive an app restart for persistent partition', async () => {
  260. const appPath = path.join(__dirname, 'fixtures', 'api', 'cookie-app')
  261. const electronPath = remote.getGlobal('process').execPath
  262. const test = (result, phase) => {
  263. return new Promise((resolve, reject) => {
  264. let output = ''
  265. const appProcess = ChildProcess.spawn(
  266. electronPath,
  267. [appPath],
  268. { env: { PHASE: phase, ...process.env } }
  269. )
  270. appProcess.stdout.on('data', data => { output += data })
  271. appProcess.stdout.on('end', () => {
  272. output = output.replace(/(\r\n|\n|\r)/gm, '')
  273. assert.strictEqual(output, result)
  274. resolve()
  275. })
  276. })
  277. }
  278. await test('011', 'one')
  279. await test('110', 'two')
  280. })
  281. })
  282. describe('ses.clearStorageData(options)', () => {
  283. fixtures = path.resolve(__dirname, 'fixtures')
  284. it('clears localstorage data', (done) => {
  285. ipcMain.on('count', (event, count) => {
  286. ipcMain.removeAllListeners('count')
  287. assert.strictEqual(count, 0)
  288. done()
  289. })
  290. w.webContents.on('did-finish-load', () => {
  291. const options = {
  292. origin: 'file://',
  293. storages: ['localstorage'],
  294. quotas: ['persistent']
  295. }
  296. w.webContents.session.clearStorageData(options, () => {
  297. w.webContents.send('getcount')
  298. })
  299. })
  300. w.loadFile(path.join(fixtures, 'api', 'localstorage.html'))
  301. })
  302. })
  303. describe('will-download event', () => {
  304. beforeEach(() => {
  305. if (w != null) w.destroy()
  306. w = new BrowserWindow({
  307. show: false,
  308. width: 400,
  309. height: 400
  310. })
  311. })
  312. it('can cancel default download behavior', (done) => {
  313. const mockFile = Buffer.alloc(1024)
  314. const contentDisposition = 'inline; filename="mockFile.txt"'
  315. const downloadServer = http.createServer((req, res) => {
  316. res.writeHead(200, {
  317. 'Content-Length': mockFile.length,
  318. 'Content-Type': 'application/plain',
  319. 'Content-Disposition': contentDisposition
  320. })
  321. res.end(mockFile)
  322. downloadServer.close()
  323. })
  324. downloadServer.listen(0, '127.0.0.1', () => {
  325. const port = downloadServer.address().port
  326. const url = `http://127.0.0.1:${port}/`
  327. ipcRenderer.sendSync('set-download-option', false, true)
  328. w.loadURL(url)
  329. ipcRenderer.once('download-error', (event, downloadUrl, filename, error) => {
  330. assert.strictEqual(downloadUrl, url)
  331. assert.strictEqual(filename, 'mockFile.txt')
  332. assert.strictEqual(error, 'Object has been destroyed')
  333. done()
  334. })
  335. })
  336. })
  337. })
  338. describe('DownloadItem', () => {
  339. const mockPDF = Buffer.alloc(1024 * 1024 * 5)
  340. const protocolName = 'custom-dl'
  341. let contentDisposition = 'inline; filename="mock.pdf"'
  342. const downloadFilePath = path.join(fixtures, 'mock.pdf')
  343. const downloadServer = http.createServer((req, res) => {
  344. if (req.url === '/?testFilename') contentDisposition = 'inline'
  345. res.writeHead(200, {
  346. 'Content-Length': mockPDF.length,
  347. 'Content-Type': 'application/pdf',
  348. 'Content-Disposition': contentDisposition
  349. })
  350. res.end(mockPDF)
  351. downloadServer.close()
  352. })
  353. const isPathEqual = (path1, path2) => {
  354. return path.relative(path1, path2) === ''
  355. }
  356. const assertDownload = (event, state, url, mimeType,
  357. receivedBytes, totalBytes, disposition,
  358. filename, port, savePath, isCustom) => {
  359. assert.strictEqual(state, 'completed')
  360. assert.strictEqual(filename, 'mock.pdf')
  361. assert.ok(path.isAbsolute(savePath))
  362. assert.ok(isPathEqual(savePath, path.join(__dirname, 'fixtures', 'mock.pdf')))
  363. if (isCustom) {
  364. assert.strictEqual(url, `${protocolName}://item`)
  365. } else {
  366. assert.strictEqual(url, `http://127.0.0.1:${port}/`)
  367. }
  368. assert.strictEqual(mimeType, 'application/pdf')
  369. assert.strictEqual(receivedBytes, mockPDF.length)
  370. assert.strictEqual(totalBytes, mockPDF.length)
  371. assert.strictEqual(disposition, contentDisposition)
  372. assert(fs.existsSync(downloadFilePath))
  373. fs.unlinkSync(downloadFilePath)
  374. }
  375. it('can download using WebContents.downloadURL', (done) => {
  376. downloadServer.listen(0, '127.0.0.1', () => {
  377. const port = downloadServer.address().port
  378. ipcRenderer.sendSync('set-download-option', false, false)
  379. w.webContents.downloadURL(`${url}:${port}`)
  380. ipcRenderer.once('download-done', (event, state, url,
  381. mimeType, receivedBytes,
  382. totalBytes, disposition,
  383. filename, savePath) => {
  384. assertDownload(event, state, url, mimeType, receivedBytes,
  385. totalBytes, disposition, filename, port, savePath)
  386. done()
  387. })
  388. })
  389. })
  390. it('can download from custom protocols using WebContents.downloadURL', (done) => {
  391. const protocol = session.defaultSession.protocol
  392. downloadServer.listen(0, '127.0.0.1', () => {
  393. const port = downloadServer.address().port
  394. const handler = (ignoredError, callback) => {
  395. callback({ url: `${url}:${port}` })
  396. }
  397. protocol.registerHttpProtocol(protocolName, handler, (error) => {
  398. if (error) return done(error)
  399. ipcRenderer.sendSync('set-download-option', false, false)
  400. w.webContents.downloadURL(`${protocolName}://item`)
  401. ipcRenderer.once('download-done', (event, state, url,
  402. mimeType, receivedBytes,
  403. totalBytes, disposition,
  404. filename, savePath) => {
  405. assertDownload(event, state, url, mimeType, receivedBytes,
  406. totalBytes, disposition, filename, port, savePath,
  407. true)
  408. done()
  409. })
  410. })
  411. })
  412. })
  413. it('can download using WebView.downloadURL', (done) => {
  414. downloadServer.listen(0, '127.0.0.1', () => {
  415. const port = downloadServer.address().port
  416. ipcRenderer.sendSync('set-download-option', false, false)
  417. webview = new WebView()
  418. webview.addEventListener('did-finish-load', () => {
  419. webview.downloadURL(`${url}:${port}/`)
  420. })
  421. webview.src = `file://${fixtures}/api/blank.html`
  422. ipcRenderer.once('download-done', (event, state, url,
  423. mimeType, receivedBytes,
  424. totalBytes, disposition,
  425. filename, savePath) => {
  426. assertDownload(event, state, url, mimeType, receivedBytes,
  427. totalBytes, disposition, filename, port, savePath)
  428. document.body.removeChild(webview)
  429. done()
  430. })
  431. document.body.appendChild(webview)
  432. })
  433. })
  434. it('can cancel download', (done) => {
  435. downloadServer.listen(0, '127.0.0.1', () => {
  436. const port = downloadServer.address().port
  437. ipcRenderer.sendSync('set-download-option', true, false)
  438. w.webContents.downloadURL(`${url}:${port}/`)
  439. ipcRenderer.once('download-done', (event, state, url,
  440. mimeType, receivedBytes,
  441. totalBytes, disposition,
  442. filename) => {
  443. assert.strictEqual(state, 'cancelled')
  444. assert.strictEqual(filename, 'mock.pdf')
  445. assert.strictEqual(mimeType, 'application/pdf')
  446. assert.strictEqual(receivedBytes, 0)
  447. assert.strictEqual(totalBytes, mockPDF.length)
  448. assert.strictEqual(disposition, contentDisposition)
  449. done()
  450. })
  451. })
  452. })
  453. it('can generate a default filename', function (done) {
  454. if (process.env.APPVEYOR === 'True') {
  455. // FIXME(alexeykuzmin): Skip the test.
  456. // this.skip()
  457. return done()
  458. }
  459. downloadServer.listen(0, '127.0.0.1', () => {
  460. const port = downloadServer.address().port
  461. ipcRenderer.sendSync('set-download-option', true, false)
  462. w.webContents.downloadURL(`${url}:${port}/?testFilename`)
  463. ipcRenderer.once('download-done', (event, state, url,
  464. mimeType, receivedBytes,
  465. totalBytes, disposition,
  466. filename) => {
  467. assert.strictEqual(state, 'cancelled')
  468. assert.strictEqual(filename, 'download.pdf')
  469. assert.strictEqual(mimeType, 'application/pdf')
  470. assert.strictEqual(receivedBytes, 0)
  471. assert.strictEqual(totalBytes, mockPDF.length)
  472. assert.strictEqual(disposition, contentDisposition)
  473. done()
  474. })
  475. })
  476. })
  477. it('can set options for the save dialog', (done) => {
  478. downloadServer.listen(0, '127.0.0.1', () => {
  479. const filePath = path.join(__dirname, 'fixtures', 'mock.pdf')
  480. const port = downloadServer.address().port
  481. const options = {
  482. window: null,
  483. title: 'title',
  484. message: 'message',
  485. buttonLabel: 'buttonLabel',
  486. nameFieldLabel: 'nameFieldLabel',
  487. defaultPath: '/',
  488. filters: [{
  489. name: '1', extensions: ['.1', '.2']
  490. }, {
  491. name: '2', extensions: ['.3', '.4', '.5']
  492. }],
  493. showsTagField: true,
  494. securityScopedBookmarks: true
  495. }
  496. ipcRenderer.sendSync('set-download-option', true, false, filePath, options)
  497. w.webContents.downloadURL(`${url}:${port}`)
  498. ipcRenderer.once('download-done', (event, state, url,
  499. mimeType, receivedBytes,
  500. totalBytes, disposition,
  501. filename, savePath, dialogOptions) => {
  502. expect(dialogOptions).to.deep.equal(options)
  503. done()
  504. })
  505. })
  506. })
  507. describe('when a save path is specified and the URL is unavailable', () => {
  508. it('does not display a save dialog and reports the done state as interrupted', (done) => {
  509. ipcRenderer.sendSync('set-download-option', false, false)
  510. ipcRenderer.once('download-done', (event, state) => {
  511. assert.strictEqual(state, 'interrupted')
  512. done()
  513. })
  514. w.webContents.downloadURL(`file://${path.join(__dirname, 'does-not-exist.txt')}`)
  515. })
  516. })
  517. })
  518. describe('ses.protocol', () => {
  519. const partitionName = 'temp'
  520. const protocolName = 'sp'
  521. const partitionProtocol = session.fromPartition(partitionName).protocol
  522. const protocol = session.defaultSession.protocol
  523. const handler = (ignoredError, callback) => {
  524. callback({ data: 'test', mimeType: 'text/html' })
  525. }
  526. beforeEach((done) => {
  527. if (w != null) w.destroy()
  528. w = new BrowserWindow({
  529. show: false,
  530. webPreferences: {
  531. partition: partitionName
  532. }
  533. })
  534. partitionProtocol.registerStringProtocol(protocolName, handler, (error) => {
  535. done(error != null ? error : undefined)
  536. })
  537. })
  538. afterEach((done) => {
  539. partitionProtocol.unregisterProtocol(protocolName, () => done())
  540. })
  541. it('does not affect defaultSession', async () => {
  542. const result1 = await protocol.isProtocolHandled(protocolName)
  543. assert.strictEqual(result1, false)
  544. const result2 = await partitionProtocol.isProtocolHandled(protocolName)
  545. assert.strictEqual(result2, true)
  546. })
  547. it('handles requests from partition', (done) => {
  548. w.webContents.on('did-finish-load', () => done())
  549. w.loadURL(`${protocolName}://fake-host`)
  550. })
  551. })
  552. describe('ses.setProxy(options, callback)', () => {
  553. let server = null
  554. let customSession = null
  555. beforeEach((done) => {
  556. customSession = session.fromPartition('proxyconfig')
  557. // FIXME(deepak1556): This is just a hack to force
  558. // creation of request context which in turn initializes
  559. // the network context, can be removed with network
  560. // service enabled.
  561. customSession.clearHostResolverCache(() => done())
  562. })
  563. afterEach(() => {
  564. if (server) {
  565. server.close()
  566. }
  567. if (customSession) {
  568. customSession.destroy()
  569. }
  570. })
  571. it('allows configuring proxy settings', (done) => {
  572. const config = { proxyRules: 'http=myproxy:80' }
  573. customSession.setProxy(config, () => {
  574. customSession.resolveProxy('http://example.com/', (proxy) => {
  575. assert.strictEqual(proxy, 'PROXY myproxy:80')
  576. done()
  577. })
  578. })
  579. })
  580. it('allows removing the implicit bypass rules for localhost', (done) => {
  581. const config = {
  582. proxyRules: 'http=myproxy:80',
  583. proxyBypassRules: '<-loopback>'
  584. }
  585. customSession.setProxy(config, () => {
  586. customSession.resolveProxy('http://localhost', (proxy) => {
  587. assert.strictEqual(proxy, 'PROXY myproxy:80')
  588. done()
  589. })
  590. })
  591. })
  592. it('allows configuring proxy settings with pacScript', (done) => {
  593. server = http.createServer((req, res) => {
  594. const pac = `
  595. function FindProxyForURL(url, host) {
  596. return "PROXY myproxy:8132";
  597. }
  598. `
  599. res.writeHead(200, {
  600. 'Content-Type': 'application/x-ns-proxy-autoconfig'
  601. })
  602. res.end(pac)
  603. })
  604. server.listen(0, '127.0.0.1', () => {
  605. const config = { pacScript: `http://127.0.0.1:${server.address().port}` }
  606. customSession.setProxy(config, () => {
  607. customSession.resolveProxy('https://google.com', (proxy) => {
  608. assert.strictEqual(proxy, 'PROXY myproxy:8132')
  609. done()
  610. })
  611. })
  612. })
  613. })
  614. it('allows bypassing proxy settings', (done) => {
  615. const config = {
  616. proxyRules: 'http=myproxy:80',
  617. proxyBypassRules: '<local>'
  618. }
  619. customSession.setProxy(config, () => {
  620. customSession.resolveProxy('http://example/', (proxy) => {
  621. assert.strictEqual(proxy, 'DIRECT')
  622. done()
  623. })
  624. })
  625. })
  626. })
  627. describe('ses.getBlobData(identifier, callback)', () => {
  628. it('returns blob data for uuid', (done) => {
  629. const scheme = 'cors-blob'
  630. const protocol = session.defaultSession.protocol
  631. const url = `${scheme}://host`
  632. before(() => {
  633. if (w != null) w.destroy()
  634. w = new BrowserWindow({ show: false })
  635. })
  636. after((done) => {
  637. protocol.unregisterProtocol(scheme, () => {
  638. closeWindow(w).then(() => {
  639. w = null
  640. done()
  641. })
  642. })
  643. })
  644. const postData = JSON.stringify({
  645. type: 'blob',
  646. value: 'hello'
  647. })
  648. const content = `<html>
  649. <script>
  650. let fd = new FormData();
  651. fd.append('file', new Blob(['${postData}'], {type:'application/json'}));
  652. fetch('${url}', {method:'POST', body: fd });
  653. </script>
  654. </html>`
  655. protocol.registerStringProtocol(scheme, (request, callback) => {
  656. if (request.method === 'GET') {
  657. callback({ data: content, mimeType: 'text/html' })
  658. } else if (request.method === 'POST') {
  659. const uuid = request.uploadData[1].blobUUID
  660. assert(uuid)
  661. session.defaultSession.getBlobData(uuid, (result) => {
  662. assert.strictEqual(result.toString(), postData)
  663. done()
  664. })
  665. }
  666. }, (error) => {
  667. if (error) return done(error)
  668. w.loadURL(url)
  669. })
  670. })
  671. })
  672. describe('ses.setCertificateVerifyProc(callback)', () => {
  673. let server = null
  674. beforeEach((done) => {
  675. const certPath = path.join(__dirname, 'fixtures', 'certificates')
  676. const options = {
  677. key: fs.readFileSync(path.join(certPath, 'server.key')),
  678. cert: fs.readFileSync(path.join(certPath, 'server.pem')),
  679. ca: [
  680. fs.readFileSync(path.join(certPath, 'rootCA.pem')),
  681. fs.readFileSync(path.join(certPath, 'intermediateCA.pem'))
  682. ],
  683. requestCert: true,
  684. rejectUnauthorized: false
  685. }
  686. server = https.createServer(options, (req, res) => {
  687. res.writeHead(200)
  688. res.end('<title>hello</title>')
  689. })
  690. server.listen(0, '127.0.0.1', done)
  691. })
  692. afterEach(() => {
  693. session.defaultSession.setCertificateVerifyProc(null)
  694. server.close()
  695. })
  696. it('accepts the request when the callback is called with 0', (done) => {
  697. session.defaultSession.setCertificateVerifyProc(({ hostname, certificate, verificationResult, errorCode }, callback) => {
  698. assert(['net::ERR_CERT_AUTHORITY_INVALID', 'net::ERR_CERT_COMMON_NAME_INVALID'].includes(verificationResult), verificationResult)
  699. assert([-202, -200].includes(errorCode), errorCode)
  700. callback(0)
  701. })
  702. w.webContents.once('did-finish-load', () => {
  703. assert.strictEqual(w.webContents.getTitle(), 'hello')
  704. done()
  705. })
  706. w.loadURL(`https://127.0.0.1:${server.address().port}`)
  707. })
  708. it('rejects the request when the callback is called with -2', (done) => {
  709. session.defaultSession.setCertificateVerifyProc(({ hostname, certificate, verificationResult }, callback) => {
  710. assert.strictEqual(hostname, '127.0.0.1')
  711. assert.strictEqual(certificate.issuerName, 'Intermediate CA')
  712. assert.strictEqual(certificate.subjectName, 'localhost')
  713. assert.strictEqual(certificate.issuer.commonName, 'Intermediate CA')
  714. assert.strictEqual(certificate.subject.commonName, 'localhost')
  715. assert.strictEqual(certificate.issuerCert.issuer.commonName, 'Root CA')
  716. assert.strictEqual(certificate.issuerCert.subject.commonName, 'Intermediate CA')
  717. assert.strictEqual(certificate.issuerCert.issuerCert.issuer.commonName, 'Root CA')
  718. assert.strictEqual(certificate.issuerCert.issuerCert.subject.commonName, 'Root CA')
  719. assert.strictEqual(certificate.issuerCert.issuerCert.issuerCert, undefined)
  720. assert(['net::ERR_CERT_AUTHORITY_INVALID', 'net::ERR_CERT_COMMON_NAME_INVALID'].includes(verificationResult), verificationResult)
  721. callback(-2)
  722. })
  723. const url = `https://127.0.0.1:${server.address().port}`
  724. w.webContents.once('did-finish-load', () => {
  725. assert.strictEqual(w.webContents.getTitle(), url)
  726. done()
  727. })
  728. w.loadURL(url)
  729. })
  730. })
  731. describe('ses.createInterruptedDownload(options)', () => {
  732. it('can create an interrupted download item', (done) => {
  733. ipcRenderer.sendSync('set-download-option', true, false)
  734. const filePath = path.join(__dirname, 'fixtures', 'mock.pdf')
  735. const options = {
  736. path: filePath,
  737. urlChain: ['http://127.0.0.1/'],
  738. mimeType: 'application/pdf',
  739. offset: 0,
  740. length: 5242880
  741. }
  742. w.webContents.session.createInterruptedDownload(options)
  743. ipcRenderer.once('download-created', (event, state, urlChain,
  744. mimeType, receivedBytes,
  745. totalBytes, filename,
  746. savePath) => {
  747. assert.strictEqual(state, 'interrupted')
  748. assert.deepStrictEqual(urlChain, ['http://127.0.0.1/'])
  749. assert.strictEqual(mimeType, 'application/pdf')
  750. assert.strictEqual(receivedBytes, 0)
  751. assert.strictEqual(totalBytes, 5242880)
  752. assert.strictEqual(savePath, filePath)
  753. done()
  754. })
  755. })
  756. it('can be resumed', (done) => {
  757. const fixtures = path.join(__dirname, 'fixtures')
  758. const downloadFilePath = path.join(fixtures, 'logo.png')
  759. const rangeServer = http.createServer((req, res) => {
  760. const options = { root: fixtures }
  761. send(req, req.url, options)
  762. .on('error', (error) => { done(error) }).pipe(res)
  763. })
  764. ipcRenderer.sendSync('set-download-option', true, false, downloadFilePath)
  765. rangeServer.listen(0, '127.0.0.1', () => {
  766. const port = rangeServer.address().port
  767. const downloadUrl = `http://127.0.0.1:${port}/assets/logo.png`
  768. const callback = (event, state, url, mimeType,
  769. receivedBytes, totalBytes, disposition,
  770. filename, savePath, dialogOptions, urlChain,
  771. lastModifiedTime, eTag) => {
  772. if (state === 'cancelled') {
  773. const options = {
  774. path: savePath,
  775. urlChain: urlChain,
  776. mimeType: mimeType,
  777. offset: receivedBytes,
  778. length: totalBytes,
  779. lastModified: lastModifiedTime,
  780. eTag: eTag
  781. }
  782. ipcRenderer.sendSync('set-download-option', false, false, downloadFilePath)
  783. w.webContents.session.createInterruptedDownload(options)
  784. } else {
  785. assert.strictEqual(state, 'completed')
  786. assert.strictEqual(filename, 'logo.png')
  787. assert.strictEqual(savePath, downloadFilePath)
  788. assert.strictEqual(url, downloadUrl)
  789. assert.strictEqual(mimeType, 'image/png')
  790. assert.strictEqual(receivedBytes, 14022)
  791. assert.strictEqual(totalBytes, 14022)
  792. assert(fs.existsSync(downloadFilePath))
  793. fs.unlinkSync(downloadFilePath)
  794. rangeServer.close()
  795. ipcRenderer.removeListener('download-done', callback)
  796. done()
  797. }
  798. }
  799. ipcRenderer.on('download-done', callback)
  800. w.webContents.downloadURL(downloadUrl)
  801. })
  802. })
  803. })
  804. describe('ses.clearAuthCache(options[, callback])', () => {
  805. it('can clear http auth info from cache', (done) => {
  806. const ses = session.fromPartition('auth-cache')
  807. const server = http.createServer((req, res) => {
  808. const credentials = auth(req)
  809. if (!credentials || credentials.name !== 'test' || credentials.pass !== 'test') {
  810. res.statusCode = 401
  811. res.setHeader('WWW-Authenticate', 'Basic realm="Restricted"')
  812. res.end()
  813. } else {
  814. res.end('authenticated')
  815. }
  816. })
  817. server.listen(0, '127.0.0.1', () => {
  818. const port = server.address().port
  819. function issueLoginRequest (attempt = 1) {
  820. if (attempt > 2) {
  821. server.close()
  822. return done()
  823. }
  824. const request = net.request({
  825. url: `http://127.0.0.1:${port}`,
  826. session: ses
  827. })
  828. request.on('login', (info, callback) => {
  829. attempt += 1
  830. assert.strictEqual(info.scheme, 'basic')
  831. assert.strictEqual(info.realm, 'Restricted')
  832. callback('test', 'test')
  833. })
  834. request.on('response', (response) => {
  835. let data = ''
  836. response.pause()
  837. response.on('data', (chunk) => {
  838. data += chunk
  839. })
  840. response.on('end', () => {
  841. assert.strictEqual(data, 'authenticated')
  842. ses.clearAuthCache({ type: 'password' }, () => {
  843. issueLoginRequest(attempt)
  844. })
  845. })
  846. response.on('error', (error) => { done(error) })
  847. response.resume()
  848. })
  849. // Internal api to bypass cache for testing.
  850. request.urlRequest._setLoadFlags(1 << 1)
  851. request.end()
  852. }
  853. issueLoginRequest()
  854. })
  855. })
  856. })
  857. describe('ses.setPermissionRequestHandler(handler)', () => {
  858. it('cancels any pending requests when cleared', (done) => {
  859. const ses = session.fromPartition('permissionTest')
  860. ses.setPermissionRequestHandler(() => {
  861. ses.setPermissionRequestHandler(null)
  862. })
  863. webview = new WebView()
  864. webview.addEventListener('ipc-message', (e) => {
  865. assert.strictEqual(e.channel, 'message')
  866. assert.deepStrictEqual(e.args, ['SecurityError'])
  867. done()
  868. })
  869. webview.src = `file://${fixtures}/pages/permissions/midi-sysex.html`
  870. webview.partition = 'permissionTest'
  871. webview.setAttribute('nodeintegration', 'on')
  872. document.body.appendChild(webview)
  873. })
  874. })
  875. })