/* * Copyright (C) 2022 Kristóf Marussy * * This file is part of Sophie. * * Sophie is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * * SPDX-License-Identifier: AGPL-3.0-only */ import { URL } from 'node:url'; import { jest } from '@jest/globals'; import { each, fake } from '@sophie/test-utils'; import type { OnBeforeRequestListenerDetails, PermissionRequestHandlerHandlerDetails, Response, Session, WebContents, } from 'electron'; import type Resources from '../../../resources/Resources'; import hardenSession from '../hardenSession'; const permissions = [ 'clipboard-read', 'media', 'display-capture', 'mediaKeySystem', 'geolocation', 'notifications', 'midi', 'midiSysex', 'pointerLock', 'fullscreen', 'openExternal', 'unknown', ] as const; type Permission = typeof permissions[number]; let permissionRequestHandler: | (( webContents: WebContents, permission: Permission, callback: (permissionGranted: boolean) => void, details: PermissionRequestHandlerHandlerDetails, ) => void) | undefined; let onBeforeRequest: | (( details: OnBeforeRequestListenerDetails, callback: (response: Response) => void, ) => void) | undefined; function getFakeResources(devMode: boolean) { const resourcesUrl = devMode ? 'http://localhost:3000/' : 'file:///opt/sophie/resources/app.asar/renderer/dist/'; return fake({ getRendererURL(path: string) { return new URL(path, resourcesUrl).toString(); }, }); } const fakeSession = fake({ setPermissionRequestHandler(handler) { permissionRequestHandler = handler instanceof Function ? handler : undefined; }, webRequest: { onBeforeRequest(handler) { onBeforeRequest = handler instanceof Function ? handler : undefined; }, }, }); beforeEach(() => { permissionRequestHandler = undefined; onBeforeRequest = undefined; }); it('should set permission request and before request handlers', () => { hardenSession(getFakeResources(false), false, fakeSession); expect(permissionRequestHandler).toBeDefined(); expect(onBeforeRequest).toBeDefined(); }); each(permissions.map((permission) => [permission])).it( 'should reject %s permission requests', (permission: Permission) => { hardenSession(getFakeResources(false), false, fakeSession); const callback = jest.fn(); permissionRequestHandler!(fake({}), permission, callback, { requestingUrl: 'file:///opt/sophie/resources/app.asar/pacakges/renderer/dist/index.html', isMainFrame: true, }); expect(callback).toHaveBeenCalledWith(false); }, ); each([ [ false, 'GET', 'file:///opt/sophie/resources/app.asar/pacakges/renderer/dist/index.html', false, ], [ false, 'POST', 'file:///opt/sophie/resources/app.asar/pacakges/renderer/dist/index.html', true, ], [false, 'GET', 'chrome-extension:aaaa', true], [false, 'GET', 'devtools:aaaa', true], [false, 'GET', 'https://clients2.google.com/service/update2/crx/aaaa', true], [false, 'GET', 'https://clients2.googleusercontent.com/crx/aaaa', true], [false, 'GET', 'https://example.com', true], [false, 'GET', 'invalid-url', true], [true, 'GET', 'http://localhost:3000/index.html', false], [true, 'POST', 'http://localhost:3000/index.html', true], [true, 'GET', 'ws://localhost:3000/index.html', false], [true, 'GET', 'chrome-extension:aaaa', false], [true, 'GET', 'devtools:aaaa', false], [true, 'GET', 'https://clients2.google.com/service/update2/crx/aaaa', false], [true, 'GET', 'https://clients2.googleusercontent.com/crx/aaaa', false], [true, 'GET', 'https://example.com', true], ]).it( 'in dev mode: %s the request %s %s should be cancelled: %s', (devMode: boolean, method: string, url: string, cancel: boolean) => { hardenSession(getFakeResources(devMode), devMode, fakeSession); const callback = jest.fn(); onBeforeRequest!( { id: 0, url, method, resourceType: 'mainFrame', referrer: '', timestamp: 0, uploadData: [], }, callback, ); expect(callback).toHaveBeenCalledWith(expect.objectContaining({ cancel })); }, );