api-protocol-spec.js 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309
  1. const assert = require('assert')
  2. const http = require('http')
  3. const path = require('path')
  4. const qs = require('querystring')
  5. const { closeWindow } = require('./window-helpers')
  6. const { remote } = require('electron')
  7. const { BrowserWindow, ipcMain, protocol, session, webContents } = remote
  8. // The RPC API doesn't seem to support calling methods on remote objects very
  9. // well. In order to test stream protocol, we must work around this limitation
  10. // and use Stream instances created in the browser process.
  11. const stream = remote.require('stream')
  12. /* The whole protocol API doesn't use standard callbacks */
  13. /* eslint-disable standard/no-callback-literal */
  14. describe('protocol module', () => {
  15. const protocolName = 'sp'
  16. const text = 'valar morghulis'
  17. const postData = {
  18. name: 'post test',
  19. type: 'string'
  20. }
  21. function delay (ms) {
  22. return new Promise((resolve) => {
  23. setTimeout(resolve, ms)
  24. })
  25. }
  26. function getStream (chunkSize = text.length, data = text) {
  27. const body = stream.PassThrough()
  28. async function sendChunks () {
  29. let buf = Buffer.from(data)
  30. for (;;) {
  31. body.push(buf.slice(0, chunkSize))
  32. buf = buf.slice(chunkSize)
  33. if (!buf.length) {
  34. break
  35. }
  36. // emulate network delay
  37. await delay(50)
  38. }
  39. body.push(null)
  40. }
  41. sendChunks()
  42. return body
  43. }
  44. afterEach((done) => {
  45. protocol.unregisterProtocol(protocolName, () => {
  46. protocol.uninterceptProtocol('http', () => done())
  47. })
  48. })
  49. describe('protocol.register(Any)Protocol', () => {
  50. const emptyHandler = (request, callback) => callback()
  51. it('throws error when scheme is already registered', (done) => {
  52. protocol.registerStringProtocol(protocolName, emptyHandler, (error) => {
  53. assert.strictEqual(error, null)
  54. protocol.registerBufferProtocol(protocolName, emptyHandler, (error) => {
  55. assert.notStrictEqual(error, null)
  56. done()
  57. })
  58. })
  59. })
  60. it('does not crash when handler is called twice', (done) => {
  61. const doubleHandler = (request, callback) => {
  62. try {
  63. callback(text)
  64. callback()
  65. } catch (error) {
  66. // Ignore error
  67. }
  68. }
  69. protocol.registerStringProtocol(protocolName, doubleHandler, (error) => {
  70. if (error) return done(error)
  71. $.ajax({
  72. url: protocolName + '://fake-host',
  73. cache: false,
  74. success: (data) => {
  75. assert.strictEqual(data, text)
  76. done()
  77. },
  78. error: (xhr, errorType, error) => done(error)
  79. })
  80. })
  81. })
  82. it('sends error when callback is called with nothing', (done) => {
  83. protocol.registerBufferProtocol(protocolName, emptyHandler, (error) => {
  84. if (error) return done(error)
  85. $.ajax({
  86. url: protocolName + '://fake-host',
  87. cache: false,
  88. success: () => done('request succeeded but it should not'),
  89. error: (xhr, errorType) => {
  90. assert.strictEqual(errorType, 'error')
  91. return done()
  92. }
  93. })
  94. })
  95. })
  96. it('does not crash when callback is called in next tick', (done) => {
  97. const handler = (request, callback) => {
  98. setImmediate(() => callback(text))
  99. }
  100. protocol.registerStringProtocol(protocolName, handler, (error) => {
  101. if (error) return done(error)
  102. $.ajax({
  103. url: protocolName + '://fake-host',
  104. cache: false,
  105. success: (data) => {
  106. assert.strictEqual(data, text)
  107. done()
  108. },
  109. error: (xhr, errorType, error) => done(error)
  110. })
  111. })
  112. })
  113. })
  114. describe('protocol.unregisterProtocol', () => {
  115. it('returns error when scheme does not exist', (done) => {
  116. protocol.unregisterProtocol('not-exist', (error) => {
  117. assert.notStrictEqual(error, null)
  118. done()
  119. })
  120. })
  121. })
  122. describe('protocol.registerStringProtocol', () => {
  123. it('sends string as response', (done) => {
  124. const handler = (request, callback) => callback(text)
  125. protocol.registerStringProtocol(protocolName, handler, (error) => {
  126. if (error) return done(error)
  127. $.ajax({
  128. url: protocolName + '://fake-host',
  129. cache: false,
  130. success: (data) => {
  131. assert.strictEqual(data, text)
  132. done()
  133. },
  134. error: (xhr, errorType, error) => done(error)
  135. })
  136. })
  137. })
  138. it('sets Access-Control-Allow-Origin', (done) => {
  139. const handler = (request, callback) => callback(text)
  140. protocol.registerStringProtocol(protocolName, handler, (error) => {
  141. if (error) return done(error)
  142. $.ajax({
  143. url: protocolName + '://fake-host',
  144. cache: false,
  145. success: (data, status, request) => {
  146. assert.strictEqual(data, text)
  147. assert.strictEqual(request.getResponseHeader('Access-Control-Allow-Origin'), '*')
  148. done()
  149. },
  150. error: (xhr, errorType, error) => done(error)
  151. })
  152. })
  153. })
  154. it('sends object as response', (done) => {
  155. const handler = (request, callback) => {
  156. callback({
  157. data: text,
  158. mimeType: 'text/html'
  159. })
  160. }
  161. protocol.registerStringProtocol(protocolName, handler, (error) => {
  162. if (error) return done(error)
  163. $.ajax({
  164. url: protocolName + '://fake-host',
  165. cache: false,
  166. success: (data) => {
  167. assert.strictEqual(data, text)
  168. done()
  169. },
  170. error: (xhr, errorType, error) => done(error)
  171. })
  172. })
  173. })
  174. it('fails when sending object other than string', (done) => {
  175. const handler = (request, callback) => callback(new Date())
  176. protocol.registerBufferProtocol(protocolName, handler, (error) => {
  177. if (error) return done(error)
  178. $.ajax({
  179. url: protocolName + '://fake-host',
  180. cache: false,
  181. success: () => done('request succeeded but it should not'),
  182. error: (xhr, errorType) => {
  183. assert.strictEqual(errorType, 'error')
  184. done()
  185. }
  186. })
  187. })
  188. })
  189. })
  190. describe('protocol.registerBufferProtocol', () => {
  191. const buffer = Buffer.from(text)
  192. it('sends Buffer as response', (done) => {
  193. const handler = (request, callback) => callback(buffer)
  194. protocol.registerBufferProtocol(protocolName, handler, (error) => {
  195. if (error) return done(error)
  196. $.ajax({
  197. url: protocolName + '://fake-host',
  198. cache: false,
  199. success: (data) => {
  200. assert.strictEqual(data, text)
  201. done()
  202. },
  203. error: (xhr, errorType, error) => done(error)
  204. })
  205. })
  206. })
  207. it('sets Access-Control-Allow-Origin', (done) => {
  208. const handler = (request, callback) => callback(buffer)
  209. protocol.registerBufferProtocol(protocolName, handler, (error) => {
  210. if (error) return done(error)
  211. $.ajax({
  212. url: protocolName + '://fake-host',
  213. cache: false,
  214. success: (data, status, request) => {
  215. assert.strictEqual(data, text)
  216. assert.strictEqual(request.getResponseHeader('Access-Control-Allow-Origin'), '*')
  217. done()
  218. },
  219. error: (xhr, errorType, error) => done(error)
  220. })
  221. })
  222. })
  223. it('sends object as response', (done) => {
  224. const handler = (request, callback) => {
  225. callback({
  226. data: buffer,
  227. mimeType: 'text/html'
  228. })
  229. }
  230. protocol.registerBufferProtocol(protocolName, handler, (error) => {
  231. if (error) return done(error)
  232. $.ajax({
  233. url: protocolName + '://fake-host',
  234. cache: false,
  235. success: (data) => {
  236. assert.strictEqual(data, text)
  237. done()
  238. },
  239. error: (xhr, errorType, error) => done(error)
  240. })
  241. })
  242. })
  243. it('fails when sending string', (done) => {
  244. const handler = (request, callback) => callback(text)
  245. protocol.registerBufferProtocol(protocolName, handler, (error) => {
  246. if (error) return done(error)
  247. $.ajax({
  248. url: protocolName + '://fake-host',
  249. cache: false,
  250. success: () => done('request succeeded but it should not'),
  251. error: (xhr, errorType) => {
  252. assert.strictEqual(errorType, 'error')
  253. done()
  254. }
  255. })
  256. })
  257. })
  258. })
  259. describe('protocol.registerFileProtocol', () => {
  260. const filePath = path.join(__dirname, 'fixtures', 'test.asar', 'a.asar', 'file1')
  261. const fileContent = require('fs').readFileSync(filePath)
  262. const normalPath = path.join(__dirname, 'fixtures', 'pages', 'a.html')
  263. const normalContent = require('fs').readFileSync(normalPath)
  264. it('sends file path as response', (done) => {
  265. const handler = (request, callback) => callback(filePath)
  266. protocol.registerFileProtocol(protocolName, handler, (error) => {
  267. if (error) return done(error)
  268. $.ajax({
  269. url: protocolName + '://fake-host',
  270. cache: false,
  271. success: (data) => {
  272. assert.strictEqual(data, String(fileContent))
  273. return done()
  274. },
  275. error: (xhr, errorType, error) => done(error)
  276. })
  277. })
  278. })
  279. it('sets Access-Control-Allow-Origin', (done) => {
  280. const handler = (request, callback) => callback(filePath)
  281. protocol.registerFileProtocol(protocolName, handler, (error) => {
  282. if (error) return done(error)
  283. $.ajax({
  284. url: protocolName + '://fake-host',
  285. cache: false,
  286. success: (data, status, request) => {
  287. assert.strictEqual(data, String(fileContent))
  288. assert.strictEqual(request.getResponseHeader('Access-Control-Allow-Origin'), '*')
  289. done()
  290. },
  291. error: (xhr, errorType, error) => {
  292. done(error)
  293. }
  294. })
  295. })
  296. })
  297. it('sets custom headers', (done) => {
  298. const handler = (request, callback) => callback({
  299. path: filePath,
  300. headers: { 'X-Great-Header': 'sogreat' }
  301. })
  302. protocol.registerFileProtocol(protocolName, handler, (error) => {
  303. if (error) return done(error)
  304. $.ajax({
  305. url: protocolName + '://fake-host',
  306. cache: false,
  307. success: (data, status, request) => {
  308. assert.strictEqual(data, String(fileContent))
  309. assert.strictEqual(request.getResponseHeader('X-Great-Header'), 'sogreat')
  310. done()
  311. },
  312. error: (xhr, errorType, error) => {
  313. done(error)
  314. }
  315. })
  316. })
  317. })
  318. it('throws an error when custom headers are invalid', (done) => {
  319. const handler = (request, callback) => {
  320. assert.throws(() => callback({
  321. path: filePath,
  322. headers: { 'X-Great-Header': 42 }
  323. }), /Value of 'X-Great-Header' header has to be a string/)
  324. done()
  325. }
  326. protocol.registerFileProtocol(protocolName, handler, (error) => {
  327. if (error) return done(error)
  328. $.ajax({
  329. url: protocolName + '://fake-host',
  330. cache: false,
  331. success: () => done('request succeeded but it should not'),
  332. error: (xhr, errorType, error) => done(error)
  333. })
  334. })
  335. })
  336. it('sends object as response', (done) => {
  337. const handler = (request, callback) => callback({ path: filePath })
  338. protocol.registerFileProtocol(protocolName, handler, (error) => {
  339. if (error) return done(error)
  340. $.ajax({
  341. url: protocolName + '://fake-host',
  342. cache: false,
  343. success: (data) => {
  344. assert.strictEqual(data, String(fileContent))
  345. done()
  346. },
  347. error: (xhr, errorType, error) => done(error)
  348. })
  349. })
  350. })
  351. it('can send normal file', (done) => {
  352. const handler = (request, callback) => callback(normalPath)
  353. protocol.registerFileProtocol(protocolName, handler, (error) => {
  354. if (error) return done(error)
  355. $.ajax({
  356. url: protocolName + '://fake-host',
  357. cache: false,
  358. success: (data) => {
  359. assert.strictEqual(data, String(normalContent))
  360. done()
  361. },
  362. error: (xhr, errorType, error) => done(error)
  363. })
  364. })
  365. })
  366. it('fails when sending unexist-file', (done) => {
  367. const fakeFilePath = path.join(__dirname, 'fixtures', 'test.asar', 'a.asar', 'not-exist')
  368. const handler = (request, callback) => callback(fakeFilePath)
  369. protocol.registerFileProtocol(protocolName, handler, (error) => {
  370. if (error) return done(error)
  371. $.ajax({
  372. url: protocolName + '://fake-host',
  373. cache: false,
  374. success: () => done('request succeeded but it should not'),
  375. error: (xhr, errorType) => {
  376. assert.strictEqual(errorType, 'error')
  377. done()
  378. }
  379. })
  380. })
  381. })
  382. it('fails when sending unsupported content', (done) => {
  383. const handler = (request, callback) => callback(new Date())
  384. protocol.registerFileProtocol(protocolName, handler, (error) => {
  385. if (error) return done(error)
  386. $.ajax({
  387. url: protocolName + '://fake-host',
  388. cache: false,
  389. success: () => done('request succeeded but it should not'),
  390. error: (xhr, errorType) => {
  391. assert.strictEqual(errorType, 'error')
  392. done()
  393. }
  394. })
  395. })
  396. })
  397. })
  398. describe('protocol.registerHttpProtocol', () => {
  399. it('sends url as response', (done) => {
  400. const server = http.createServer((req, res) => {
  401. assert.notStrictEqual(req.headers.accept, '')
  402. res.end(text)
  403. server.close()
  404. })
  405. server.listen(0, '127.0.0.1', () => {
  406. const port = server.address().port
  407. const url = 'http://127.0.0.1:' + port
  408. const handler = (request, callback) => callback({ url })
  409. protocol.registerHttpProtocol(protocolName, handler, (error) => {
  410. if (error) return done(error)
  411. $.ajax({
  412. url: protocolName + '://fake-host',
  413. cache: false,
  414. success: (data) => {
  415. assert.strictEqual(data, text)
  416. done()
  417. },
  418. error: (xhr, errorType, error) => done(error)
  419. })
  420. })
  421. })
  422. })
  423. it('fails when sending invalid url', (done) => {
  424. const handler = (request, callback) => callback({ url: 'url' })
  425. protocol.registerHttpProtocol(protocolName, handler, (error) => {
  426. if (error) return done(error)
  427. $.ajax({
  428. url: protocolName + '://fake-host',
  429. cache: false,
  430. success: () => done('request succeeded but it should not'),
  431. error: (xhr, errorType) => {
  432. assert.strictEqual(errorType, 'error')
  433. done()
  434. }
  435. })
  436. })
  437. })
  438. it('fails when sending unsupported content', (done) => {
  439. const handler = (request, callback) => callback(new Date())
  440. protocol.registerHttpProtocol(protocolName, handler, (error) => {
  441. if (error) return done(error)
  442. $.ajax({
  443. url: protocolName + '://fake-host',
  444. cache: false,
  445. success: () => {
  446. done('request succeeded but it should not')
  447. },
  448. error: (xhr, errorType) => {
  449. assert.strictEqual(errorType, 'error')
  450. done()
  451. }
  452. })
  453. })
  454. })
  455. it('works when target URL redirects', (done) => {
  456. let contents = null
  457. const server = http.createServer((req, res) => {
  458. if (req.url === '/serverRedirect') {
  459. res.statusCode = 301
  460. res.setHeader('Location', `http://${req.rawHeaders[1]}`)
  461. res.end()
  462. } else {
  463. res.end(text)
  464. }
  465. })
  466. server.listen(0, '127.0.0.1', () => {
  467. const port = server.address().port
  468. const url = `${protocolName}://fake-host`
  469. const redirectURL = `http://127.0.0.1:${port}/serverRedirect`
  470. const handler = (request, callback) => callback({ url: redirectURL })
  471. protocol.registerHttpProtocol(protocolName, handler, (error) => {
  472. if (error) return done(error)
  473. contents = webContents.create({})
  474. contents.on('did-finish-load', () => {
  475. assert.strictEqual(contents.getURL(), url)
  476. server.close()
  477. contents.destroy()
  478. done()
  479. })
  480. contents.loadURL(url)
  481. })
  482. })
  483. })
  484. it('can access request headers', (done) => {
  485. const handler = (request) => {
  486. assert.ok('headers' in request)
  487. done()
  488. }
  489. protocol.registerHttpProtocol(protocolName, handler, () => {
  490. $.ajax({
  491. url: protocolName + '://fake-host',
  492. cache: false
  493. })
  494. })
  495. })
  496. })
  497. describe('protocol.registerStreamProtocol', () => {
  498. it('sends Stream as response', (done) => {
  499. const handler = (request, callback) => callback(getStream())
  500. protocol.registerStreamProtocol(protocolName, handler, (error) => {
  501. if (error) return done(error)
  502. $.ajax({
  503. url: protocolName + '://fake-host',
  504. cache: false,
  505. success: (data) => {
  506. assert.strictEqual(data, text)
  507. done()
  508. },
  509. error: (xhr, errorType, error) => {
  510. done(error || new Error(`Request failed: ${xhr.status}`))
  511. }
  512. })
  513. })
  514. })
  515. it('sends object as response', (done) => {
  516. const handler = (request, callback) => callback({ data: getStream() })
  517. protocol.registerStreamProtocol(protocolName, handler, (error) => {
  518. if (error) return done(error)
  519. $.ajax({
  520. url: protocolName + '://fake-host',
  521. cache: false,
  522. success: (data, _, request) => {
  523. assert.strictEqual(request.status, 200)
  524. assert.strictEqual(data, text)
  525. done()
  526. },
  527. error: (xhr, errorType, error) => {
  528. done(error || new Error(`Request failed: ${xhr.status}`))
  529. }
  530. })
  531. })
  532. })
  533. it('sends custom response headers', (done) => {
  534. const handler = (request, callback) => callback({
  535. data: getStream(3),
  536. headers: {
  537. 'x-electron': ['a', 'b']
  538. }
  539. })
  540. protocol.registerStreamProtocol(protocolName, handler, (error) => {
  541. if (error) return done(error)
  542. $.ajax({
  543. url: protocolName + '://fake-host',
  544. cache: false,
  545. success: (data, _, request) => {
  546. assert.strictEqual(request.status, 200)
  547. assert.strictEqual(request.getResponseHeader('x-electron'), 'a, b')
  548. assert.strictEqual(data, text)
  549. done()
  550. },
  551. error: (xhr, errorType, error) => {
  552. done(error || new Error(`Request failed: ${xhr.status}`))
  553. }
  554. })
  555. })
  556. })
  557. it('sends custom status code', (done) => {
  558. const handler = (request, callback) => callback({
  559. statusCode: 204,
  560. data: null
  561. })
  562. protocol.registerStreamProtocol(protocolName, handler, (error) => {
  563. if (error) return done(error)
  564. $.ajax({
  565. url: protocolName + '://fake-host',
  566. cache: false,
  567. success: (data, _, request) => {
  568. assert.strictEqual(request.status, 204)
  569. assert.strictEqual(data, undefined)
  570. done()
  571. },
  572. error: (xhr, errorType, error) => {
  573. done(error || new Error(`Request failed: ${xhr.status}`))
  574. }
  575. })
  576. })
  577. })
  578. it('receives request headers', (done) => {
  579. const handler = (request, callback) => {
  580. callback({
  581. headers: {
  582. 'content-type': 'application/json'
  583. },
  584. data: getStream(5, JSON.stringify(Object.assign({}, request.headers)))
  585. })
  586. }
  587. protocol.registerStreamProtocol(protocolName, handler, (error) => {
  588. if (error) return done(error)
  589. $.ajax({
  590. url: protocolName + '://fake-host',
  591. headers: {
  592. 'x-return-headers': 'yes'
  593. },
  594. cache: false,
  595. success: (data) => {
  596. assert.strictEqual(data['x-return-headers'], 'yes')
  597. done()
  598. },
  599. error: (xhr, errorType, error) => {
  600. done(error || new Error(`Request failed: ${xhr.status}`))
  601. }
  602. })
  603. })
  604. })
  605. it('returns response multiple response headers with the same name', (done) => {
  606. const handler = (request, callback) => {
  607. callback({
  608. headers: {
  609. 'header1': ['value1', 'value2'],
  610. 'header2': 'value3'
  611. },
  612. data: getStream()
  613. })
  614. }
  615. protocol.registerStreamProtocol(protocolName, handler, (error) => {
  616. if (error) return done(error)
  617. $.ajax({
  618. url: protocolName + '://fake-host',
  619. cache: false,
  620. success: (data, status, request) => {
  621. // SUBTLE: when the response headers have multiple values it
  622. // separates values by ", ". When the response headers are incorrectly
  623. // converting an array to a string it separates values by ",".
  624. assert.strictEqual(request.getAllResponseHeaders(), 'header1: value1, value2\r\nheader2: value3\r\n')
  625. done()
  626. },
  627. error: (xhr, errorType, error) => {
  628. done(error || new Error(`Request failed: ${xhr.status}`))
  629. }
  630. })
  631. })
  632. })
  633. it('can handle large responses', async () => {
  634. const data = Buffer.alloc(128 * 1024)
  635. const handler = (request, callback) => {
  636. callback(getStream(data.length, data))
  637. }
  638. await new Promise((resolve, reject) => {
  639. protocol.registerStreamProtocol(protocolName, handler, err => {
  640. if (err) return reject(err)
  641. resolve()
  642. })
  643. })
  644. const r = await new Promise((resolve, reject) => {
  645. $.ajax({
  646. url: protocolName + '://fake-host',
  647. cache: false,
  648. success: resolve,
  649. error: (xhr, errorType, error) => {
  650. reject(error || new Error(`Request failed: ${xhr.status}`))
  651. }
  652. })
  653. })
  654. assert.strictEqual(r.length, data.length)
  655. })
  656. })
  657. describe('protocol.isProtocolHandled', () => {
  658. it('returns true for about:', async () => {
  659. const result = await protocol.isProtocolHandled('about')
  660. assert.strictEqual(result, true)
  661. })
  662. // TODO(codebytere): remove when promisification is complete
  663. it('returns true for about: (callback)', (done) => {
  664. protocol.isProtocolHandled('about', (result) => {
  665. assert.strictEqual(result, true)
  666. done()
  667. })
  668. })
  669. it('returns true for file:', async () => {
  670. const result = await protocol.isProtocolHandled('file')
  671. assert.strictEqual(result, true)
  672. })
  673. // TODO(codebytere): remove when promisification is complete
  674. it('returns true for file: (callback)', (done) => {
  675. protocol.isProtocolHandled('file', (result) => {
  676. assert.strictEqual(result, true)
  677. done()
  678. })
  679. })
  680. it('returns true for http:', async () => {
  681. const result = await protocol.isProtocolHandled('http')
  682. assert.strictEqual(result, true)
  683. })
  684. it('returns true for https:', async () => {
  685. const result = await protocol.isProtocolHandled('https')
  686. assert.strictEqual(result, true)
  687. })
  688. it('returns false when scheme is not registered', async () => {
  689. const result = await protocol.isProtocolHandled('no-exist')
  690. assert.strictEqual(result, false)
  691. })
  692. it('returns true for custom protocol', (done) => {
  693. const emptyHandler = (request, callback) => callback()
  694. protocol.registerStringProtocol(protocolName, emptyHandler, async (error) => {
  695. assert.strictEqual(error, null)
  696. const result = await protocol.isProtocolHandled(protocolName)
  697. assert.strictEqual(result, true)
  698. done()
  699. })
  700. })
  701. // TODO(codebytere): remove when promisification is complete
  702. it('returns true for custom protocol (callback)', (done) => {
  703. const emptyHandler = (request, callback) => callback()
  704. protocol.registerStringProtocol(protocolName, emptyHandler, (error) => {
  705. assert.strictEqual(error, null)
  706. protocol.isProtocolHandled(protocolName, (result) => {
  707. assert.strictEqual(result, true)
  708. done()
  709. })
  710. })
  711. })
  712. it('returns true for intercepted protocol', (done) => {
  713. const emptyHandler = (request, callback) => callback()
  714. protocol.interceptStringProtocol('http', emptyHandler, async (error) => {
  715. assert.strictEqual(error, null)
  716. const result = await protocol.isProtocolHandled('http')
  717. assert.strictEqual(result, true)
  718. done()
  719. })
  720. })
  721. // TODO(codebytere): remove when promisification is complete
  722. it('returns true for intercepted protocol (callback)', (done) => {
  723. const emptyHandler = (request, callback) => callback()
  724. protocol.interceptStringProtocol('http', emptyHandler, (error) => {
  725. assert.strictEqual(error, null)
  726. protocol.isProtocolHandled('http', (result) => {
  727. assert.strictEqual(result, true)
  728. done()
  729. })
  730. })
  731. })
  732. })
  733. describe('protocol.intercept(Any)Protocol', () => {
  734. const emptyHandler = (request, callback) => callback()
  735. it('throws error when scheme is already intercepted', (done) => {
  736. protocol.interceptStringProtocol('http', emptyHandler, (error) => {
  737. assert.strictEqual(error, null)
  738. protocol.interceptBufferProtocol('http', emptyHandler, (error) => {
  739. assert.notStrictEqual(error, null)
  740. done()
  741. })
  742. })
  743. })
  744. it('does not crash when handler is called twice', (done) => {
  745. const doubleHandler = (request, callback) => {
  746. try {
  747. callback(text)
  748. callback()
  749. } catch (error) {
  750. // Ignore error
  751. }
  752. }
  753. protocol.interceptStringProtocol('http', doubleHandler, (error) => {
  754. if (error) return done(error)
  755. $.ajax({
  756. url: 'http://fake-host',
  757. cache: false,
  758. success: (data) => {
  759. assert.strictEqual(data, text)
  760. done()
  761. },
  762. error: (xhr, errorType, error) => done(error)
  763. })
  764. })
  765. })
  766. it('sends error when callback is called with nothing', function (done) {
  767. protocol.interceptBufferProtocol('http', emptyHandler, (error) => {
  768. if (error) return done(error)
  769. $.ajax({
  770. url: 'http://fake-host',
  771. cache: false,
  772. success: () => done('request succeeded but it should not'),
  773. error: (xhr, errorType) => {
  774. assert.strictEqual(errorType, 'error')
  775. done()
  776. }
  777. })
  778. })
  779. })
  780. })
  781. describe('protocol.interceptStringProtocol', () => {
  782. it('can intercept http protocol', (done) => {
  783. const handler = (request, callback) => callback(text)
  784. protocol.interceptStringProtocol('http', handler, (error) => {
  785. if (error) return done(error)
  786. $.ajax({
  787. url: 'http://fake-host',
  788. cache: false,
  789. success: (data) => {
  790. assert.strictEqual(data, text)
  791. done()
  792. },
  793. error: (xhr, errorType, error) => done(error)
  794. })
  795. })
  796. })
  797. it('can set content-type', (done) => {
  798. const handler = (request, callback) => {
  799. callback({
  800. mimeType: 'application/json',
  801. data: '{"value": 1}'
  802. })
  803. }
  804. protocol.interceptStringProtocol('http', handler, (error) => {
  805. if (error) return done(error)
  806. $.ajax({
  807. url: 'http://fake-host',
  808. cache: false,
  809. success: (data) => {
  810. assert.strictEqual(typeof data, 'object')
  811. assert.strictEqual(data.value, 1)
  812. done()
  813. },
  814. error: (xhr, errorType, error) => done(error)
  815. })
  816. })
  817. })
  818. it('can receive post data', (done) => {
  819. const handler = (request, callback) => {
  820. const uploadData = request.uploadData[0].bytes.toString()
  821. callback({ data: uploadData })
  822. }
  823. protocol.interceptStringProtocol('http', handler, (error) => {
  824. if (error) return done(error)
  825. $.ajax({
  826. url: 'http://fake-host',
  827. cache: false,
  828. type: 'POST',
  829. data: postData,
  830. success: (data) => {
  831. assert.deepStrictEqual({ ...qs.parse(data) }, postData)
  832. done()
  833. },
  834. error: (xhr, errorType, error) => done(error)
  835. })
  836. })
  837. })
  838. })
  839. describe('protocol.interceptBufferProtocol', () => {
  840. it('can intercept http protocol', (done) => {
  841. const handler = (request, callback) => callback(Buffer.from(text))
  842. protocol.interceptBufferProtocol('http', handler, (error) => {
  843. if (error) return done(error)
  844. $.ajax({
  845. url: 'http://fake-host',
  846. cache: false,
  847. success: (data) => {
  848. assert.strictEqual(data, text)
  849. done()
  850. },
  851. error: (xhr, errorType, error) => done(error)
  852. })
  853. })
  854. })
  855. it('can receive post data', (done) => {
  856. const handler = (request, callback) => {
  857. const uploadData = request.uploadData[0].bytes
  858. callback(uploadData)
  859. }
  860. protocol.interceptBufferProtocol('http', handler, (error) => {
  861. if (error) return done(error)
  862. $.ajax({
  863. url: 'http://fake-host',
  864. cache: false,
  865. type: 'POST',
  866. data: postData,
  867. success: (data) => {
  868. assert.strictEqual(data, $.param(postData))
  869. done()
  870. },
  871. error: (xhr, errorType, error) => done(error)
  872. })
  873. })
  874. })
  875. })
  876. describe('protocol.interceptHttpProtocol', () => {
  877. it('can send POST request', (done) => {
  878. const server = http.createServer((req, res) => {
  879. let body = ''
  880. req.on('data', (chunk) => {
  881. body += chunk
  882. })
  883. req.on('end', () => {
  884. res.end(body)
  885. })
  886. server.close()
  887. })
  888. server.listen(0, '127.0.0.1', () => {
  889. const port = server.address().port
  890. const url = `http://127.0.0.1:${port}`
  891. const handler = (request, callback) => {
  892. const data = {
  893. url: url,
  894. method: 'POST',
  895. uploadData: {
  896. contentType: 'application/x-www-form-urlencoded',
  897. data: request.uploadData[0].bytes.toString()
  898. },
  899. session: null
  900. }
  901. callback(data)
  902. }
  903. protocol.interceptHttpProtocol('http', handler, (error) => {
  904. if (error) return done(error)
  905. $.ajax({
  906. url: 'http://fake-host',
  907. cache: false,
  908. type: 'POST',
  909. data: postData,
  910. success: (data) => {
  911. assert.deepStrictEqual({ ...qs.parse(data) }, postData)
  912. done()
  913. },
  914. error: (xhr, errorType, error) => done(error)
  915. })
  916. })
  917. })
  918. })
  919. it('can use custom session', (done) => {
  920. const customSession = session.fromPartition('custom-ses', { cache: false })
  921. customSession.webRequest.onBeforeRequest((details, callback) => {
  922. assert.strictEqual(details.url, 'http://fake-host/')
  923. callback({ cancel: true })
  924. })
  925. const handler = (request, callback) => {
  926. callback({
  927. url: request.url,
  928. session: customSession
  929. })
  930. }
  931. protocol.interceptHttpProtocol('http', handler, (error) => {
  932. if (error) return done(error)
  933. fetch('http://fake-host').then(() => {
  934. done('request succeeded but it should not')
  935. }).catch(() => {
  936. customSession.webRequest.onBeforeRequest(null)
  937. done()
  938. })
  939. })
  940. })
  941. it('can access request headers', (done) => {
  942. const handler = (request) => {
  943. assert.ok('headers' in request)
  944. done()
  945. }
  946. protocol.interceptHttpProtocol('http', handler, () => {
  947. fetch('http://fake-host')
  948. })
  949. })
  950. })
  951. describe('protocol.interceptStreamProtocol', () => {
  952. it('can intercept http protocol', (done) => {
  953. const handler = (request, callback) => callback(getStream())
  954. protocol.interceptStreamProtocol('http', handler, (error) => {
  955. if (error) return done(error)
  956. $.ajax({
  957. url: 'http://fake-host',
  958. cache: false,
  959. success: (data) => {
  960. assert.strictEqual(data, text)
  961. done()
  962. },
  963. error: (xhr, errorType, error) => {
  964. done(error || new Error(`Request failed: ${xhr.status}`))
  965. }
  966. })
  967. })
  968. })
  969. it('can receive post data', (done) => {
  970. const handler = (request, callback) => {
  971. callback(getStream(3, request.uploadData[0].bytes.toString()))
  972. }
  973. protocol.interceptStreamProtocol('http', handler, (error) => {
  974. if (error) return done(error)
  975. $.ajax({
  976. url: 'http://fake-host',
  977. cache: false,
  978. type: 'POST',
  979. data: postData,
  980. success: (data) => {
  981. assert.deepStrictEqual({ ...qs.parse(data) }, postData)
  982. done()
  983. },
  984. error: (xhr, errorType, error) => {
  985. done(error || new Error(`Request failed: ${xhr.status}`))
  986. }
  987. })
  988. })
  989. })
  990. it('can execute redirects', (done) => {
  991. const handler = (request, callback) => {
  992. if (request.url.indexOf('http://fake-host') === 0) {
  993. setTimeout(() => {
  994. callback({
  995. data: null,
  996. statusCode: 302,
  997. headers: {
  998. Location: 'http://fake-redirect'
  999. }
  1000. })
  1001. }, 300)
  1002. } else {
  1003. assert.strictEqual(request.url.indexOf('http://fake-redirect'), 0)
  1004. callback(getStream(1, 'redirect'))
  1005. }
  1006. }
  1007. protocol.interceptStreamProtocol('http', handler, (error) => {
  1008. if (error) return done(error)
  1009. $.ajax({
  1010. url: 'http://fake-host',
  1011. cache: false,
  1012. success: (data) => {
  1013. assert.strictEqual(data, 'redirect')
  1014. done()
  1015. },
  1016. error: (xhr, errorType, error) => {
  1017. done(error || new Error(`Request failed: ${xhr.status}`))
  1018. }
  1019. })
  1020. })
  1021. })
  1022. })
  1023. describe('protocol.uninterceptProtocol', () => {
  1024. it('returns error when scheme does not exist', (done) => {
  1025. protocol.uninterceptProtocol('not-exist', (error) => {
  1026. assert.notStrictEqual(error, null)
  1027. done()
  1028. })
  1029. })
  1030. it('returns error when scheme is not intercepted', (done) => {
  1031. protocol.uninterceptProtocol('http', (error) => {
  1032. assert.notStrictEqual(error, null)
  1033. done()
  1034. })
  1035. })
  1036. })
  1037. describe('protocol.registerSchemesAsPrivileged standard', () => {
  1038. const standardScheme = remote.getGlobal('standardScheme')
  1039. const origin = `${standardScheme}://fake-host`
  1040. const imageURL = `${origin}/test.png`
  1041. const filePath = path.join(__dirname, 'fixtures', 'pages', 'b.html')
  1042. const fileContent = '<img src="/test.png" />'
  1043. let w = null
  1044. let success = null
  1045. beforeEach(() => {
  1046. w = new BrowserWindow({
  1047. show: false,
  1048. webPreferences: {
  1049. nodeIntegration: true
  1050. }
  1051. })
  1052. success = false
  1053. })
  1054. afterEach((done) => {
  1055. protocol.unregisterProtocol(standardScheme, () => {
  1056. closeWindow(w).then(() => {
  1057. w = null
  1058. done()
  1059. })
  1060. })
  1061. })
  1062. it('resolves relative resources', (done) => {
  1063. const handler = (request, callback) => {
  1064. if (request.url === imageURL) {
  1065. success = true
  1066. callback()
  1067. } else {
  1068. callback(filePath)
  1069. }
  1070. }
  1071. protocol.registerFileProtocol(standardScheme, handler, (error) => {
  1072. if (error) return done(error)
  1073. w.webContents.on('did-finish-load', () => {
  1074. assert(success)
  1075. done()
  1076. })
  1077. w.loadURL(origin)
  1078. })
  1079. })
  1080. it('resolves absolute resources', (done) => {
  1081. const handler = (request, callback) => {
  1082. if (request.url === imageURL) {
  1083. success = true
  1084. callback()
  1085. } else {
  1086. callback({
  1087. data: fileContent,
  1088. mimeType: 'text/html'
  1089. })
  1090. }
  1091. }
  1092. protocol.registerStringProtocol(standardScheme, handler, (error) => {
  1093. if (error) return done(error)
  1094. w.webContents.on('did-finish-load', () => {
  1095. assert(success)
  1096. done()
  1097. })
  1098. w.loadURL(origin)
  1099. })
  1100. })
  1101. it('can have fetch working in it', (done) => {
  1102. const content = '<html><script>fetch("http://github.com")</script></html>'
  1103. const handler = (request, callback) => callback({ data: content, mimeType: 'text/html' })
  1104. protocol.registerStringProtocol(standardScheme, handler, (error) => {
  1105. if (error) return done(error)
  1106. w.webContents.on('crashed', () => done('WebContents crashed'))
  1107. w.webContents.on('did-finish-load', () => done())
  1108. w.loadURL(origin)
  1109. })
  1110. })
  1111. it('can access files through the FileSystem API', (done) => {
  1112. const filePath = path.join(__dirname, 'fixtures', 'pages', 'filesystem.html')
  1113. const handler = (request, callback) => callback({ path: filePath })
  1114. protocol.registerFileProtocol(standardScheme, handler, (error) => {
  1115. if (error) return done(error)
  1116. w.loadURL(origin)
  1117. })
  1118. ipcMain.once('file-system-error', (event, err) => done(err))
  1119. ipcMain.once('file-system-write-end', () => done())
  1120. })
  1121. it('registers secure, when {secure: true}', (done) => {
  1122. const filePath = path.join(__dirname, 'fixtures', 'pages', 'cache-storage.html')
  1123. const handler = (request, callback) => callback({ path: filePath })
  1124. ipcMain.once('success', () => done())
  1125. ipcMain.once('failure', (event, err) => done(err))
  1126. protocol.registerFileProtocol(standardScheme, handler, (error) => {
  1127. if (error) return done(error)
  1128. w.loadURL(origin)
  1129. })
  1130. })
  1131. })
  1132. describe('protocol.registerSchemesAsPrivileged cors-fetch', function () {
  1133. const standardScheme = remote.getGlobal('standardScheme')
  1134. const fixtures = path.resolve(__dirname, 'fixtures')
  1135. let w = null
  1136. beforeEach((done) => {
  1137. protocol.unregisterProtocol(standardScheme, () => done())
  1138. })
  1139. afterEach((done) => {
  1140. closeWindow(w).then(() => {
  1141. w = null
  1142. done()
  1143. })
  1144. })
  1145. it('supports fetch api by default', (done) => {
  1146. const url = 'file://' + fixtures + '/assets/logo.png'
  1147. window.fetch(url)
  1148. .then(function (response) {
  1149. assert(response.ok)
  1150. done()
  1151. })
  1152. .catch(function (err) {
  1153. done('unexpected error : ' + err)
  1154. })
  1155. })
  1156. it('allows CORS requests by default', (done) => {
  1157. allowsCORSRequests('cors', 200, `<html>
  1158. <script>
  1159. const {ipcRenderer} = require('electron')
  1160. fetch('cors://myhost').then(function (response) {
  1161. ipcRenderer.send('response', response.status)
  1162. }).catch(function (response) {
  1163. ipcRenderer.send('response', 'failed')
  1164. })
  1165. </script>
  1166. </html>`, done)
  1167. })
  1168. it('disallows CORS, but allows fetch requests, when specified', (done) => {
  1169. allowsCORSRequests('no-cors', 'failed', `<html>
  1170. <script>
  1171. const {ipcRenderer} = require('electron')
  1172. fetch('no-cors://myhost').then(function (response) {
  1173. ipcRenderer.send('response', response.status)
  1174. }).catch(function (response) {
  1175. ipcRenderer.send('response', 'failed')
  1176. })
  1177. </script>
  1178. </html>`, done)
  1179. })
  1180. it('allows CORS, but disallows fetch requests, when specified', (done) => {
  1181. allowsCORSRequests('no-fetch', 'failed', `<html>
  1182. <script>
  1183. const {ipcRenderer} = require('electron')
  1184. fetch('no-fetch://myhost').then(function
  1185. (response) {
  1186. ipcRenderer.send('response', response.status)
  1187. }).catch(function (response) {
  1188. ipcRenderer.send('response', 'failed')
  1189. })
  1190. </script>
  1191. </html>`, done)
  1192. })
  1193. function allowsCORSRequests (corsScheme, expected, content, done) {
  1194. const url = standardScheme + '://fake-host'
  1195. w = new BrowserWindow({
  1196. show: false,
  1197. webPreferences: {
  1198. nodeIntegration: true
  1199. }
  1200. })
  1201. const handler = (request, callback) => {
  1202. callback({ data: content, mimeType: 'text/html' })
  1203. }
  1204. protocol.registerStringProtocol(standardScheme, handler, (error) => {
  1205. if (error) { return done(error) }
  1206. })
  1207. protocol.registerStringProtocol(corsScheme,
  1208. (request, callback) => {
  1209. callback('')
  1210. }, (error) => {
  1211. if (error) { return done(error) }
  1212. ipcMain.once('response', function (event, status) {
  1213. assert.strictEqual(status, expected)
  1214. protocol.unregisterProtocol(corsScheme, () => done())
  1215. })
  1216. w.loadURL(url)
  1217. })
  1218. }
  1219. })
  1220. })