66 * and no binary fixture lives in the repo.
77 */
88
9+ import { inflateRawSync } from 'node:zlib' ;
10+
911const CRC_TABLE = ( ( ) => {
1012 const table = new Uint32Array ( 256 ) ;
1113 for ( let n = 0 ; n < 256 ; n ++ ) {
@@ -223,8 +225,9 @@ export function buildPptx(title: string): Uint8Array {
223225 ] ) ;
224226}
225227
226- /** Read every stored/deflated entry name of a zip (central directory walk); enough for L1 checks. */
227- export function zipEntryNames ( bytes : Uint8Array ) : string [ ] {
228+ type ZipEntry = { name : string ; method : number ; compressedSize : number ; localOffset : number } ;
229+
230+ function zipEntries ( bytes : Uint8Array ) : ZipEntry [ ] {
228231 const view = new DataView ( bytes . buffer , bytes . byteOffset , bytes . byteLength ) ;
229232 let eocd = - 1 ;
230233 for ( let i = bytes . length - 22 ; i >= 0 && i >= bytes . length - 65557 ; i -- ) {
@@ -236,15 +239,48 @@ export function zipEntryNames(bytes: Uint8Array): string[] {
236239 if ( eocd < 0 ) return [ ] ;
237240 const count = view . getUint16 ( eocd + 10 , true ) ;
238241 let off = view . getUint32 ( eocd + 16 , true ) ;
239- const names : string [ ] = [ ] ;
242+ const entries : ZipEntry [ ] = [ ] ;
240243 const dec = new TextDecoder ( ) ;
241244 for ( let n = 0 ; n < count ; n ++ ) {
242245 if ( view . getUint32 ( off , true ) !== 0x02014b50 ) break ;
246+ const method = view . getUint16 ( off + 10 , true ) ;
247+ const compressedSize = view . getUint32 ( off + 20 , true ) ;
243248 const nameLen = view . getUint16 ( off + 28 , true ) ;
244249 const extraLen = view . getUint16 ( off + 30 , true ) ;
245250 const commentLen = view . getUint16 ( off + 32 , true ) ;
246- names . push ( dec . decode ( bytes . subarray ( off + 46 , off + 46 + nameLen ) ) ) ;
251+ const localOffset = view . getUint32 ( off + 42 , true ) ;
252+ entries . push ( {
253+ name : dec . decode ( bytes . subarray ( off + 46 , off + 46 + nameLen ) ) ,
254+ method,
255+ compressedSize,
256+ localOffset,
257+ } ) ;
247258 off += 46 + nameLen + extraLen + commentLen ;
248259 }
249- return names ;
260+ return entries ;
261+ }
262+
263+ /** Entry names of a zip (central directory walk); enough for L1 checks. */
264+ export function zipEntryNames ( bytes : Uint8Array ) : string [ ] {
265+ return zipEntries ( bytes ) . map ( ( e ) => e . name ) ;
266+ }
267+
268+ /** Decoded text of one zip entry (stored or deflated) -- for L2 content checks on OOXML parts. */
269+ export function zipEntryText ( bytes : Uint8Array , name : string ) : string | null {
270+ const entry = zipEntries ( bytes ) . find ( ( e ) => e . name === name ) ;
271+ if ( ! entry ) return null ;
272+ const view = new DataView ( bytes . buffer , bytes . byteOffset , bytes . byteLength ) ;
273+ const lo = entry . localOffset ;
274+ if ( view . getUint32 ( lo , true ) !== 0x04034b50 ) return null ;
275+ const nameLen = view . getUint16 ( lo + 26 , true ) ;
276+ const extraLen = view . getUint16 ( lo + 28 , true ) ;
277+ const start = lo + 30 + nameLen + extraLen ;
278+ const raw = bytes . subarray ( start , start + entry . compressedSize ) ;
279+ const data = entry . method === 8 ? new Uint8Array ( inflateRawSync ( raw ) ) : raw ;
280+ return new TextDecoder ( ) . decode ( data ) ;
281+ }
282+
283+ /** Concatenated text of all <w:t>/<a:t> runs in an OOXML part. */
284+ export function ooxmlText ( xml : string ) : string {
285+ return Array . from ( xml . matchAll ( / < (?: w | a ) : t (?: \s [ ^ > ] * ) ? > ( [ ^ < ] * ) < \/ (?: w | a ) : t > / g) , ( m ) => m [ 1 ] ) . join ( '' ) ;
250286}
0 commit comments