api-session-spec.js 28 KB

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