Something went wrong. Try again.
Monorepo for wisp.place. A static site hosting service built on top of the AT Protocol.
Something went wrong. Try again.
3.5 kB · 104 lines
TypeScript
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105import { afterEach, describe, expect, it } from 'bun:test'import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync } from 'node:fs'import { tmpdir } from 'node:os'import { join } from 'node:path'import { gzipSync } from 'node:zlib'import { createLogicalSiteBudget, decompressPulledGzip, readPulledBlob, resolvePullFilePath, validatePulledGzipHeader,} from './pull'
const temporaryPaths: string[] = []
function makeTempDirectory(): string { const path = mkdtempSync(join(tmpdir(), 'wisp-pull-path-')) temporaryPaths.push(path) return path}
afterEach(() => { for (const path of temporaryPaths.splice(0)) { rmSync(path, { recursive: true, force: true }) }})
describe('resolvePullFilePath', () => { it('accepts only canonical site-relative paths on POSIX and Windows', () => { const root = makeTempDirectory() const target = resolvePullFilePath(root, 'assets/app.js', true)
expect(target).toBe(join(realpathSync(root), 'assets', 'app.js')) for (const path of [ '../outside.txt', '..\\outside.txt', 'C:/outside.txt', 'C:\\outside.txt', 'nested//file.txt', 'index.html:stream', 'file.', 'file ', 'CON.txt', ]) { expect(() => resolvePullFilePath(root, path, true)).toThrow('invalid file path') } })
it('rejects child and final symlinks instead of resolving outside the pull root', () => { const root = makeTempDirectory() const outside = makeTempDirectory() mkdirSync(join(root, 'assets')) const linkType = process.platform === 'win32' ? 'junction' : 'dir' symlinkSync(outside, join(root, 'assets', 'linked'), linkType) symlinkSync(join(outside, 'target.txt'), join(root, 'file.txt'))
const linkContainer = makeTempDirectory() const rootLink = join(linkContainer, 'root-link') symlinkSync(root, rootLink, linkType)
expect(() => resolvePullFilePath(root, 'assets/linked/escape.txt', true)).toThrow('unsafe directory') expect(() => resolvePullFilePath(root, 'file.txt', true)).toThrow('symbolic link') expect(() => resolvePullFilePath(rootLink, 'inside.txt', true)).toThrow('Pull root') })})
describe('decompressPulledGzip', () => { it('rejects a gzip expansion over the supplied bounded output limit', async () => { const compressed = gzipSync(Buffer.alloc(8 * 1024, 'x')) await expect(decompressPulledGzip(compressed, 1024)).rejects.toThrow('Could not safely decompress gzip blob') })
it('keeps a valid expansion at its inclusive output limit', async () => { const original = Buffer.alloc(1024, 'x') expect(await decompressPulledGzip(gzipSync(original), original.byteLength)).toEqual(original) })})
describe('pull safety budgets', () => { it('rejects gzip metadata without a gzip header', () => { expect(() => validatePulledGzipHeader(Buffer.from('plain text'))).toThrow('not a gzip stream') })
it('enforces the inclusive aggregate logical site limit', () => { const budget = createLogicalSiteBudget(10) budget.reserve(6) budget.reserve(4) expect(budget.totalSize).toBe(10) expect(() => budget.reserve(1)).toThrow('logical size limit') })})
describe('readPulledBlob', () => { it('cancels an oversized blob response before retaining its full body', async () => { await expect(readPulledBlob(new Response(Buffer.alloc(1025)), 1024)).rejects.toThrow( 'Downloaded blob exceeds the 1024-byte limit', ) })
it('returns a blob exactly at its inclusive byte limit', async () => { const body = Buffer.alloc(1024, 'x') expect(await readPulledBlob(new Response(body), body.byteLength)).toEqual(body) })})