// file: 앱 오리진 판정을 렌더러 디렉터리로 제한하는 회귀 테스트(드롭한 파일이 앱 창을 대체하던 문제). import { describe, expect, it } from 'vitest' import { isAppNavigationTarget, isAppOrigin, isFileUrlWithinRoot } from '../src/url-policy' const ROOT = 'file:///C:/Program%20Files/D3RO%20Voice/resources/app.asar/out/renderer/' const ORIGINS = ['file://', 'http://localhost:5173'] const OPTIONS = { fileRoot: ROOT } describe('isFileUrlWithinRoot', () => { it('accepts files under the renderer root (encoding, drive case, hash/query ignored)', () => { expect(isFileUrlWithinRoot(`${ROOT}index.html#/meetings`, ROOT)).toBe(true) expect(isFileUrlWithinRoot(`${ROOT}popups/result-popup/index.html?x=1`, ROOT)).toBe(true) expect(isFileUrlWithinRoot('file:///c:/program files/d3ro voice/resources/app.asar/out/renderer/index.html', ROOT)).toBe(true) expect(isFileUrlWithinRoot(`${ROOT}index.html`, ROOT.slice(0, -1))).toBe(true) }) it('accepts a non-ASCII install path encoded the way Chromium encodes it', () => { const root = 'file:///C:/Users/%EC%9C%A4%EC%B0%AC/AppData/Local/Programs/d3ro-voice/resources/app.asar/out/renderer/' expect(isFileUrlWithinRoot('file:///C:/Users/윤찬/AppData/Local/Programs/d3ro-voice/resources/app.asar/out/renderer/index.html', root)).toBe(true) }) it.each([ 'file:///C:/Users/me/Downloads/x.html', 'file:///C:/Program%20Files/D3RO%20Voice/resources/app.asar/out/renderer-evil/index.html', 'file:///C:/Program%20Files/D3RO%20Voice/resources/app.asar/out/index.html', 'file://evil-host/share/x.html', 'file://evil-host/C:/Program%20Files/D3RO%20Voice/resources/app.asar/out/renderer/index.html', `${ROOT}..%2F..%2Fx.html`, `${ROOT}..%5C..%5Cx.html`, `${ROOT}%2e%2e/%2e%2e/x.html`, 'http://localhost:5173/index.html', 'not a url', ])('rejects %s', (url) => { expect(isFileUrlWithinRoot(url, ROOT)).toBe(false) }) it('rejects everything when the root itself is not a file URL', () => { expect(isFileUrlWithinRoot(`${ROOT}index.html`, 'https://example.com/')).toBe(false) }) }) describe('isAppOrigin with fileRoot', () => { it('trusts only the renderer bundle and the dev server', () => { expect(isAppOrigin(`${ROOT}index.html#/settings`, ORIGINS, OPTIONS)).toBe(true) expect(isAppOrigin('http://localhost:5173/#/settings', ORIGINS, OPTIONS)).toBe(true) expect(isAppOrigin('file:///C:/Users/me/Downloads/evil.html', ORIGINS, OPTIONS)).toBe(false) expect(isAppOrigin('file://host/share/evil.html', ORIGINS, OPTIONS)).toBe(false) expect(isAppOrigin('https://evil.example/', ORIGINS, OPTIONS)).toBe(false) }) it('keeps the dev server origin exact even with a fileRoot', () => { expect(isAppOrigin('http://localhost:5174/', ORIGINS, OPTIONS)).toBe(false) }) }) describe('isAppNavigationTarget', () => { it('allows app HTML pages and dev server routes', () => { expect(isAppNavigationTarget(`${ROOT}index.html`, ORIGINS, OPTIONS)).toBe(true) expect(isAppNavigationTarget(`${ROOT}popups/command-popup/index.html#x`, ORIGINS, OPTIONS)).toBe(true) expect(isAppNavigationTarget('http://localhost:5173/popups/result-popup/index.html', ORIGINS, OPTIONS)).toBe(true) }) it('blocks dropped files and non-HTML files inside the bundle', () => { expect(isAppNavigationTarget('file:///C:/Users/me/Music/meeting.mp3', ORIGINS, OPTIONS)).toBe(false) expect(isAppNavigationTarget('file:///C:/Users/me/Downloads/x.html', ORIGINS, OPTIONS)).toBe(false) expect(isAppNavigationTarget(`${ROOT}assets/index-abc.js`, ORIGINS, OPTIONS)).toBe(false) expect(isAppNavigationTarget(`${ROOT}`, ORIGINS, OPTIONS)).toBe(false) }) })