api-session-spec.js 28 KB

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