83 lines
3.5 KiB
TypeScript
83 lines
3.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { createStaticArtifactSnapshot, startStaticReleaseServer } from '@electron/services/static-release-server';
|
|
|
|
function fixture() {
|
|
const source = [
|
|
{ path: 'index.html', bytes: Buffer.from('<main>ok</main>') },
|
|
{ path: 'assets/app.js', bytes: Buffer.from('globalThis.ok=true') },
|
|
];
|
|
return { snapshot: createStaticArtifactSnapshot(source), source };
|
|
}
|
|
|
|
describe('startStaticReleaseServer', () => {
|
|
it('serves only exact regular files under an unguessable root', async () => {
|
|
const { snapshot, source } = fixture();
|
|
source[0].bytes.fill(0);
|
|
const server = await startStaticReleaseServer(snapshot);
|
|
try {
|
|
const entry = new URL(server.entryUrl);
|
|
expect(entry.hostname).toBe('127.0.0.1');
|
|
expect(entry.pathname).toMatch(/^\/[a-f0-9]{48}\/index\.html$/);
|
|
const response = await fetch(server.entryUrl);
|
|
expect(await response.text()).toBe('<main>ok</main>');
|
|
expect(response.headers.get('cache-control')).toBe('no-store');
|
|
expect((await fetch(new URL('missing', server.entryUrl))).status).toBe(404);
|
|
expect((await fetch(new URL('.', server.entryUrl))).status).toBe(404);
|
|
expect((await fetch(server.entryUrl, { method: 'POST' })).status).toBe(405);
|
|
} finally {
|
|
await server.close();
|
|
}
|
|
await expect(fetch(server.entryUrl)).rejects.toThrow();
|
|
});
|
|
|
|
it('rejects traversal and backslashes while serving GET/HEAD from memory', async () => {
|
|
const server = await startStaticReleaseServer(fixture().snapshot);
|
|
try {
|
|
const entry = new URL(server.entryUrl);
|
|
const prefix = entry.pathname.slice(0, entry.pathname.lastIndexOf('/') + 1);
|
|
for (const path of [`${prefix}%2e%2e%2findex.html`, `${prefix}assets%5capp.js`]) {
|
|
const response = await fetch(`${entry.origin}${path}`);
|
|
expect(response.status).toBe(404);
|
|
}
|
|
const scriptUrl = `${entry.origin}${prefix}assets/app.js`;
|
|
expect((await fetch(scriptUrl)).headers.get('content-type')).toBe('text/javascript; charset=utf-8');
|
|
const head = await fetch(scriptUrl, { method: 'HEAD' });
|
|
expect(head.status).toBe(200);
|
|
expect(head.headers.get('content-length')).toBe(String(Buffer.byteLength('globalThis.ok=true')));
|
|
expect(await head.text()).toBe('');
|
|
} finally {
|
|
await server.close();
|
|
}
|
|
});
|
|
|
|
it('rejects forged, duplicate and unsafe snapshots', async () => {
|
|
await expect(startStaticReleaseServer({} as never)).rejects.toThrow('Main-owned');
|
|
for (const files of [
|
|
[{ path: '../index.html', bytes: Buffer.from('bad') }],
|
|
[{ path: 'index.html', bytes: Buffer.from('a') }, { path: 'INDEX.HTML', bytes: Buffer.from('b') }],
|
|
]) {
|
|
expect(() => createStaticArtifactSnapshot(files)).toThrow();
|
|
}
|
|
});
|
|
|
|
it.each([
|
|
['source.map', 'application/json; charset=utf-8'],
|
|
['sound.ogg', 'audio/ogg'],
|
|
['notes.txt', 'text/plain; charset=utf-8'],
|
|
])('serves %s with the production media type', async (path, expectedType) => {
|
|
const snapshot = createStaticArtifactSnapshot([
|
|
{ path: 'index.html', bytes: Buffer.from('<main>ok</main>') },
|
|
{ path, bytes: Buffer.from('data') },
|
|
]);
|
|
const server = await startStaticReleaseServer(snapshot);
|
|
try {
|
|
const entry = new URL(server.entryUrl);
|
|
const prefix = entry.pathname.slice(0, entry.pathname.lastIndexOf('/') + 1);
|
|
const response = await fetch(`${entry.origin}${prefix}${path}`);
|
|
expect(response.headers.get('content-type')).toBe(expectedType);
|
|
} finally {
|
|
await server.close();
|
|
}
|
|
});
|
|
});
|