|
4 | 4 | import JSZip from 'jszip' |
5 | 5 | import { describe, expect, it } from 'vitest' |
6 | 6 | import { extractDocAssets } from '@/lib/copilot/tools/server/files/doc-asset-extract' |
| 7 | +import { MAX_OOXML_CENTRAL_DIRECTORY_RECORDS, ZipBombError } from '@/lib/file-parsers/ooxml-limits' |
7 | 8 |
|
8 | 9 | const THEME_XML = `<?xml version="1.0"?> |
9 | 10 | <a:theme xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" name="Office"> |
@@ -143,6 +144,21 @@ describe('extractDocAssets', () => { |
143 | 144 | expect(slide.texts.some((t) => t.text.includes('grouped'))).toBe(false) |
144 | 145 | }) |
145 | 146 |
|
| 147 | + it('refuses an archive the OOXML guard rejects', async () => { |
| 148 | + // Every media entry is inflated into a retained Buffer with no cap of its |
| 149 | + // own, so the guard is the only thing bounding this. Tripping its |
| 150 | + // record-count ceiling asserts the call site is guarded without building a |
| 151 | + // multi-megabyte fixture; the size ceilings are covered in zip-guard.test.ts. |
| 152 | + const zip = new JSZip() |
| 153 | + for (let index = 0; index <= MAX_OOXML_CENTRAL_DIRECTORY_RECORDS; index++) { |
| 154 | + zip.file(`ppt/media/image${index}.png`, PNG_BYTES) |
| 155 | + } |
| 156 | + |
| 157 | + await expect( |
| 158 | + extractDocAssets(await zip.generateAsync({ type: 'nodebuffer' }), 'pptx') |
| 159 | + ).rejects.toThrow(ZipBombError) |
| 160 | + }) |
| 161 | + |
146 | 162 | it('tolerates a package with no theme or media', async () => { |
147 | 163 | const zip = new JSZip() |
148 | 164 | zip.file('ppt/slides/slide1.xml', '<p:sld/>') |
|
0 commit comments