Prechádzať zdrojové kódy

Add comments and extra fields

Arjun Barrett 4 rokov pred
rodič
commit
4ed4e682e7

+ 5 - 0
CHANGELOG.md

@@ -1,3 +1,8 @@
+## 0.7.0
+- Replace Adler-32 implementation (used in Zlib compression) with one more optimized for V8
+  - Advice from @SheetJSDev
+- Add support for extra fields, file comments in ZIP files
+- Work on Rust version
 ## 0.6.0
 - Revamped streaming unzip for compatibility and performance improvements
 - Fixed streaming data bugs

+ 19 - 19
docs/README.md

@@ -258,16 +258,16 @@ ___
 
 ### deflateSync
 
-▸ **deflateSync**(`data`: Uint8Array, `opts`: [DeflateOptions](interfaces/deflateoptions.md)): Uint8Array
+▸ **deflateSync**(`data`: Uint8Array, `opts?`: [DeflateOptions](interfaces/deflateoptions.md)): Uint8Array
 
 Compresses data with DEFLATE without any wrapper
 
 #### Parameters:
 
-Name | Type | Default value | Description |
------- | ------ | ------ | ------ |
-`data` | Uint8Array | - | The data to compress |
-`opts` | [DeflateOptions](interfaces/deflateoptions.md) | {} | The compression options |
+Name | Type | Description |
+------ | ------ | ------ |
+`data` | Uint8Array | The data to compress |
+`opts?` | [DeflateOptions](interfaces/deflateoptions.md) | The compression options |
 
 **Returns:** Uint8Array
 
@@ -381,16 +381,16 @@ ___
 
 ### gzipSync
 
-▸ **gzipSync**(`data`: Uint8Array, `opts`: [GzipOptions](interfaces/gzipoptions.md)): Uint8Array
+▸ **gzipSync**(`data`: Uint8Array, `opts?`: [GzipOptions](interfaces/gzipoptions.md)): Uint8Array
 
 Compresses data with GZIP
 
 #### Parameters:
 
-Name | Type | Default value | Description |
------- | ------ | ------ | ------ |
-`data` | Uint8Array | - | The data to compress |
-`opts` | [GzipOptions](interfaces/gzipoptions.md) | {} | The compression options |
+Name | Type | Description |
+------ | ------ | ------ |
+`data` | Uint8Array | The data to compress |
+`opts?` | [GzipOptions](interfaces/gzipoptions.md) | The compression options |
 
 **Returns:** Uint8Array
 
@@ -593,17 +593,17 @@ ___
 
 ### zipSync
 
-▸ **zipSync**(`data`: [Zippable](interfaces/zippable.md), `opts`: [ZipOptions](interfaces/zipoptions.md)): Uint8Array
+▸ **zipSync**(`data`: [Zippable](interfaces/zippable.md), `opts?`: [ZipOptions](interfaces/zipoptions.md)): Uint8Array
 
 Synchronously creates a ZIP file. Prefer using `zip` for better performance
 with more than one file.
 
 #### Parameters:
 
-Name | Type | Default value | Description |
------- | ------ | ------ | ------ |
-`data` | [Zippable](interfaces/zippable.md) | - | The directory structure for the ZIP archive |
-`opts` | [ZipOptions](interfaces/zipoptions.md) | {} | The main options, merged with per-file options |
+Name | Type | Description |
+------ | ------ | ------ |
+`data` | [Zippable](interfaces/zippable.md) | The directory structure for the ZIP archive |
+`opts?` | [ZipOptions](interfaces/zipoptions.md) | The main options, merged with per-file options |
 
 **Returns:** Uint8Array
 
@@ -648,9 +648,9 @@ Compress data with Zlib
 
 #### Parameters:
 
-Name | Type | Default value | Description |
------- | ------ | ------ | ------ |
-`data` | Uint8Array | - | The data to compress |
-`opts` | [ZlibOptions](interfaces/zliboptions.md) | {} | The compression options |
+Name | Type | Description |
+------ | ------ | ------ |
+`data` | Uint8Array | The data to compress |
+`opts` | [ZlibOptions](interfaces/zliboptions.md) | The compression options |
 
 **Returns:** Uint8Array

+ 23 - 5
docs/classes/asynczipdeflate.md

@@ -19,8 +19,10 @@ Asynchronous streaming DEFLATE compression for ZIP archives
 ### Properties
 
 * [attrs](asynczipdeflate.md#attrs)
+* [comment](asynczipdeflate.md#comment)
 * [compression](asynczipdeflate.md#compression)
 * [crc](asynczipdeflate.md#crc)
+* [extra](asynczipdeflate.md#extra)
 * [filename](asynczipdeflate.md#filename)
 * [flag](asynczipdeflate.md#flag)
 * [mtime](asynczipdeflate.md#mtime)
@@ -38,16 +40,16 @@ Asynchronous streaming DEFLATE compression for ZIP archives
 
 ### constructor
 
-\+ **new AsyncZipDeflate**(`filename`: string, `opts`: [DeflateOptions](../interfaces/deflateoptions.md)): [AsyncZipDeflate](asynczipdeflate.md)
+\+ **new AsyncZipDeflate**(`filename`: string, `opts?`: [DeflateOptions](../interfaces/deflateoptions.md)): [AsyncZipDeflate](asynczipdeflate.md)
 
 Creates a DEFLATE stream that can be added to ZIP archives
 
 #### Parameters:
 
-Name | Type | Default value | Description |
------- | ------ | ------ | ------ |
-`filename` | string | - | The filename to associate with this data stream |
-`opts` | [DeflateOptions](../interfaces/deflateoptions.md) | {} | The compression options  |
+Name | Type | Description |
+------ | ------ | ------ |
+`filename` | string | The filename to associate with this data stream |
+`opts?` | [DeflateOptions](../interfaces/deflateoptions.md) | The compression options  |
 
 **Returns:** [AsyncZipDeflate](asynczipdeflate.md)
 
@@ -61,6 +63,14 @@ Name | Type | Default value | Description |
 
 ___
 
+### comment
+
+• `Optional` **comment**: string
+
+*Implementation of [ZipInputFile](../interfaces/zipinputfile.md).[comment](../interfaces/zipinputfile.md#comment)*
+
+___
+
 ### compression
 
 •  **compression**: number
@@ -77,6 +87,14 @@ ___
 
 ___
 
+### extra
+
+• `Optional` **extra**: Record\<number, Uint8Array>
+
+*Implementation of [ZipInputFile](../interfaces/zipinputfile.md).[extra](../interfaces/zipinputfile.md#extra)*
+
+___
+
 ### filename
 
 •  **filename**: string

+ 23 - 5
docs/classes/zipdeflate.md

@@ -20,8 +20,10 @@ for better performance
 ### Properties
 
 * [attrs](zipdeflate.md#attrs)
+* [comment](zipdeflate.md#comment)
 * [compression](zipdeflate.md#compression)
 * [crc](zipdeflate.md#crc)
+* [extra](zipdeflate.md#extra)
 * [filename](zipdeflate.md#filename)
 * [flag](zipdeflate.md#flag)
 * [mtime](zipdeflate.md#mtime)
@@ -38,16 +40,16 @@ for better performance
 
 ### constructor
 
-\+ **new ZipDeflate**(`filename`: string, `opts`: [DeflateOptions](../interfaces/deflateoptions.md)): [ZipDeflate](zipdeflate.md)
+\+ **new ZipDeflate**(`filename`: string, `opts?`: [DeflateOptions](../interfaces/deflateoptions.md)): [ZipDeflate](zipdeflate.md)
 
 Creates a DEFLATE stream that can be added to ZIP archives
 
 #### Parameters:
 
-Name | Type | Default value | Description |
------- | ------ | ------ | ------ |
-`filename` | string | - | The filename to associate with this data stream |
-`opts` | [DeflateOptions](../interfaces/deflateoptions.md) | {} | The compression options  |
+Name | Type | Description |
+------ | ------ | ------ |
+`filename` | string | The filename to associate with this data stream |
+`opts?` | [DeflateOptions](../interfaces/deflateoptions.md) | The compression options  |
 
 **Returns:** [ZipDeflate](zipdeflate.md)
 
@@ -61,6 +63,14 @@ Name | Type | Default value | Description |
 
 ___
 
+### comment
+
+• `Optional` **comment**: string
+
+*Implementation of [ZipInputFile](../interfaces/zipinputfile.md).[comment](../interfaces/zipinputfile.md#comment)*
+
+___
+
 ### compression
 
 •  **compression**: number
@@ -77,6 +87,14 @@ ___
 
 ___
 
+### extra
+
+• `Optional` **extra**: Record\<number, Uint8Array>
+
+*Implementation of [ZipInputFile](../interfaces/zipinputfile.md).[extra](../interfaces/zipinputfile.md#extra)*
+
+___
+
 ### filename
 
 •  **filename**: string

+ 18 - 0
docs/classes/zippassthrough.md

@@ -19,8 +19,10 @@ A pass-through stream to keep data uncompressed in a ZIP archive.
 ### Properties
 
 * [attrs](zippassthrough.md#attrs)
+* [comment](zippassthrough.md#comment)
 * [compression](zippassthrough.md#compression)
 * [crc](zippassthrough.md#crc)
+* [extra](zippassthrough.md#extra)
 * [filename](zippassthrough.md#filename)
 * [mtime](zippassthrough.md#mtime)
 * [ondata](zippassthrough.md#ondata)
@@ -57,6 +59,14 @@ Name | Type | Description |
 
 ___
 
+### comment
+
+• `Optional` **comment**: string
+
+*Implementation of [ZipInputFile](../interfaces/zipinputfile.md).[comment](../interfaces/zipinputfile.md#comment)*
+
+___
+
 ### compression
 
 •  **compression**: number
@@ -73,6 +83,14 @@ ___
 
 ___
 
+### extra
+
+• `Optional` **extra**: Record\<number, Uint8Array>
+
+*Implementation of [ZipInputFile](../interfaces/zipinputfile.md).[extra](../interfaces/zipinputfile.md#extra)*
+
+___
+
 ### filename
 
 •  **filename**: string

+ 29 - 0
docs/interfaces/asynczipoptions.md

@@ -15,7 +15,9 @@ Options for asynchronously creating a ZIP archive
 ### Properties
 
 * [attrs](asynczipoptions.md#attrs)
+* [comment](asynczipoptions.md#comment)
 * [consume](asynczipoptions.md#consume)
+* [extra](asynczipoptions.md#extra)
 * [level](asynczipoptions.md#level)
 * [mem](asynczipoptions.md#mem)
 * [mtime](asynczipoptions.md#mtime)
@@ -49,6 +51,18 @@ If you want to set the Unix permissions, for instance, just bit shift by 16, e.g
 
 ___
 
+### comment
+
+• `Optional` **comment**: string
+
+*Inherited from [ZipAttributes](zipattributes.md).[comment](zipattributes.md#comment)*
+
+The comment to attach to the file. This field is defined by PKZIP's APPNOTE.txt,
+section 4.4.26. The comment must be at most 65,535 bytes long UTF-8 encoded. This
+field is not read by consumer software.
+
+___
+
 ### consume
 
 • `Optional` **consume**: boolean
@@ -60,6 +74,21 @@ unusable but will increase performance and reduce memory usage.
 
 ___
 
+### extra
+
+• `Optional` **extra**: Record\<number, Uint8Array>
+
+*Inherited from [ZipAttributes](zipattributes.md).[extra](zipattributes.md#extra)*
+
+Extra metadata to add to the file. This field is defined by PKZIP's APPNOTE.txt,
+section 4.4.28. At most 65,535 bytes may be used in each ID. The ID must be an
+integer between 0 and 65,535, inclusive.
+
+This field is incredibly rare and almost never needed except for compliance with
+proprietary standards and software.
+
+___
+
 ### level
 
 • `Optional` **level**: 0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9

+ 25 - 0
docs/interfaces/zipattributes.md

@@ -17,6 +17,8 @@ Attributes for files added to a ZIP archive object
 ### Properties
 
 * [attrs](zipattributes.md#attrs)
+* [comment](zipattributes.md#comment)
+* [extra](zipattributes.md#extra)
 * [mtime](zipattributes.md#mtime)
 * [os](zipattributes.md#os)
 
@@ -46,6 +48,29 @@ If you want to set the Unix permissions, for instance, just bit shift by 16, e.g
 
 ___
 
+### comment
+
+• `Optional` **comment**: string
+
+The comment to attach to the file. This field is defined by PKZIP's APPNOTE.txt,
+section 4.4.26. The comment must be at most 65,535 bytes long UTF-8 encoded. This
+field is not read by consumer software.
+
+___
+
+### extra
+
+• `Optional` **extra**: Record\<number, Uint8Array>
+
+Extra metadata to add to the file. This field is defined by PKZIP's APPNOTE.txt,
+section 4.4.28. At most 65,535 bytes may be used in each ID. The ID must be an
+integer between 0 and 65,535, inclusive.
+
+This field is incredibly rare and almost never needed except for compliance with
+proprietary standards and software.
+
+___
+
 ### mtime
 
 • `Optional` **mtime**: GzipOptions[\"mtime\"]

+ 29 - 0
docs/interfaces/zipinputfile.md

@@ -19,8 +19,10 @@ A stream that can be used to create a file in a ZIP archive
 ### Properties
 
 * [attrs](zipinputfile.md#attrs)
+* [comment](zipinputfile.md#comment)
 * [compression](zipinputfile.md#compression)
 * [crc](zipinputfile.md#crc)
+* [extra](zipinputfile.md#extra)
 * [filename](zipinputfile.md#filename)
 * [flag](zipinputfile.md#flag)
 * [mtime](zipinputfile.md#mtime)
@@ -57,6 +59,18 @@ If you want to set the Unix permissions, for instance, just bit shift by 16, e.g
 
 ___
 
+### comment
+
+• `Optional` **comment**: string
+
+*Inherited from [ZipAttributes](zipattributes.md).[comment](zipattributes.md#comment)*
+
+The comment to attach to the file. This field is defined by PKZIP's APPNOTE.txt,
+section 4.4.26. The comment must be at most 65,535 bytes long UTF-8 encoded. This
+field is not read by consumer software.
+
+___
+
 ### compression
 
 •  **compression**: number
@@ -81,6 +95,21 @@ ZipDeflate or AsyncZipDeflate.
 
 ___
 
+### extra
+
+• `Optional` **extra**: Record\<number, Uint8Array>
+
+*Inherited from [ZipAttributes](zipattributes.md).[extra](zipattributes.md#extra)*
+
+Extra metadata to add to the file. This field is defined by PKZIP's APPNOTE.txt,
+section 4.4.28. At most 65,535 bytes may be used in each ID. The ID must be an
+integer between 0 and 65,535, inclusive.
+
+This field is incredibly rare and almost never needed except for compliance with
+proprietary standards and software.
+
+___
+
 ### filename
 
 •  **filename**: string

+ 29 - 0
docs/interfaces/zipoptions.md

@@ -15,6 +15,8 @@ Options for creating a ZIP archive
 ### Properties
 
 * [attrs](zipoptions.md#attrs)
+* [comment](zipoptions.md#comment)
+* [extra](zipoptions.md#extra)
 * [level](zipoptions.md#level)
 * [mem](zipoptions.md#mem)
 * [mtime](zipoptions.md#mtime)
@@ -48,6 +50,33 @@ If you want to set the Unix permissions, for instance, just bit shift by 16, e.g
 
 ___
 
+### comment
+
+• `Optional` **comment**: string
+
+*Inherited from [ZipAttributes](zipattributes.md).[comment](zipattributes.md#comment)*
+
+The comment to attach to the file. This field is defined by PKZIP's APPNOTE.txt,
+section 4.4.26. The comment must be at most 65,535 bytes long UTF-8 encoded. This
+field is not read by consumer software.
+
+___
+
+### extra
+
+• `Optional` **extra**: Record\<number, Uint8Array>
+
+*Inherited from [ZipAttributes](zipattributes.md).[extra](zipattributes.md#extra)*
+
+Extra metadata to add to the file. This field is defined by PKZIP's APPNOTE.txt,
+section 4.4.28. At most 65,535 bytes may be used in each ID. The ID must be an
+integer between 0 and 65,535, inclusive.
+
+This field is incredibly rare and almost never needed except for compliance with
+proprietary standards and software.
+
+___
+
 ### level
 
 • `Optional` **level**: 0 \| 1 \| 2 \| 3 \| 4 \| 5 \| 6 \| 7 \| 8 \| 9

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "fflate",
-  "version": "0.6.1",
+  "version": "0.6.2",
   "description": "High performance (de)compression in an 8kB package",
   "main": "./lib/index.cjs",
   "module": "./esm/browser.js",

+ 35 - 0
rs/fflate-wasm/src/lib.rs

@@ -22,4 +22,39 @@ impl Inflate {
     pub fn push(&mut self, dat: &[u8], last: bool) -> Result<> {
         self.inflator.write_all(dat);
     }
+}
+
+
+use std::io::prelude::*;
+use std::env;
+use std::fs::read;
+use std::time;
+use flate2::Compression;
+use flate2::write::DeflateEncoder;
+use flate2::write::DeflateDecoder;
+use libflate::deflate::Decoder;
+mod lib;
+
+
+
+fn main() {
+    let args: Vec<String> = env::args().collect();
+    let buf = read(&args[1]).unwrap();
+    let mut e = DeflateEncoder::new(Vec::new(), Compression::default());
+    e.write_all(&buf).unwrap();
+    let cmpr = e.finish().unwrap();
+    let t = time::Instant::now();
+    let mut infld = Vec::new();
+    let decmpr = lib::inflate(&cmpr, &mut infld);
+    println!("fflate time: {:?}", t.elapsed());
+    let t2 = time::Instant::now();
+    let mut dec = DeflateDecoder::new(Vec::new());
+    dec.write_all(&cmpr).unwrap();
+    let decmpr2 = dec.finish().unwrap();
+    println!("flate2 time: {:?}", t2.elapsed());
+    let t3 = time::Instant::now();
+    let mut dec = Decoder::new(&*cmpr);
+    let mut decmpr3 = Vec::new();
+    dec.read_to_end(&mut decmpr3).unwrap();
+    println!("libflate time: {:?}", t3.elapsed());
 }

+ 27 - 25
rs/fflate/src/lib.rs

@@ -164,13 +164,13 @@ fn byte(dat: &[u8], bpos: usize) -> u8 {
    }
 }
 
-#[inline]
+#[inline(always)]
 fn bits(dat: &[u8], pos: usize, mask: u8) -> u8 {
     let b = pos >> 3;
     ((byte(dat, b) as u16 | ((byte(dat, b + 1) as u16) << 8)) >> (pos & 7)) as u8 & mask
 }
 
-#[inline]
+#[inline(always)]
 fn bits16(dat: &[u8], pos: usize, mask: u16) -> u16 {
     let b = pos >> 3;
     ((byte(dat, b) as u32
@@ -191,6 +191,8 @@ struct InflateState {
     dmap: [u16; 32768],
     clmap: [u16; 128],
     le: [u16; 16],
+    ldt: [u8; 320],
+    clt: [u8; 19],
     lbits: u8,
     dbits: u8,
     bfinal: bool,
@@ -213,6 +215,8 @@ impl Default for InflateState {
             dmap: [0; 32768],
             clmap: [0; 128],
             le: [0; 16],
+            ldt: [0; 320],
+            clt: [0; 19],
             lbits: 0,
             dbits: 0,
             bfinal: false,
@@ -246,13 +250,13 @@ impl From<InflateError> for Error {
 }
 
 pub trait OutputBuffer {
-    fn w(&mut self, value: u8);
-    fn wall(&mut self, slice: &[u8]) {
+    fn write(&mut self, value: u8);
+    fn write_all(&mut self, slice: &[u8]) {
         for &value in slice {
-            self.w(value);
+            self.write(value);
         }
     }
-    fn palloc(&mut self, extra_bytes: usize);
+    fn pre_alloc(&mut self, extra_bytes: usize);
     fn back(&self, back: usize) -> u8;
 }
 
@@ -293,14 +297,14 @@ impl<'a> SliceOutputBuffer<'a> {
 
 impl<'a> OutputBuffer for SliceOutputBuffer<'a> {
     #[inline(always)]
-    fn w(&mut self, value: u8) {
+    fn write(&mut self, value: u8) {
         if self.byte < self.buf.len() {
             self.buf[self.byte] = value;
         }
         self.byte += 1;
     }
     #[inline(always)]
-    fn wall(&mut self, slice: &[u8]) {
+    fn write_all(&mut self, slice: &[u8]) {
         let sl = slice.len();
         let end = self.byte + sl;
         if end <= self.buf.len() {
@@ -309,7 +313,7 @@ impl<'a> OutputBuffer for SliceOutputBuffer<'a> {
         self.byte = end;
     }
     #[inline(always)]
-    fn palloc(&mut self, _eb: usize) {}
+    fn pre_alloc(&mut self, _eb: usize) {}
     #[inline(always)]
     fn back(&self, back: usize) -> u8 {
         self.buf[self.byte - back]
@@ -319,10 +323,8 @@ impl<'a> OutputBuffer for SliceOutputBuffer<'a> {
 fn inflt(dat: &[u8], buf: &mut dyn OutputBuffer, st: &mut InflateState) -> Result<(), InflateError> {
     let mut pos = st.pos;
     let sl = dat.len();
-    if st.bfinal && st.head { return Ok(()) };
+    if sl == 0 || (st.head && sl < 5) { return Ok(()); }
     let tbts = sl << 3;
-    let mut ldt = [0u8; 320];
-    let mut clt = [0u8; 19];
     loop {
         if st.head {
             st.bfinal = bits(dat, pos, 1) != 0;
@@ -339,7 +341,7 @@ fn inflt(dat: &[u8], buf: &mut dyn OutputBuffer, st: &mut InflateState) -> Resul
                         }
                         break;
                     }
-                    buf.wall(&dat[s..t]);
+                    buf.write_all(&dat[s..t]);
                     continue;
                 }
                 1 => {
@@ -354,25 +356,25 @@ fn inflt(dat: &[u8], buf: &mut dyn OutputBuffer, st: &mut InflateState) -> Resul
                     let tl = hlit + (bits(dat, pos + 5, 31) + 1) as usize;
                     pos += 14;
                     for i in 0..hclen {
-                        clt[clim[i]] = bits(dat, pos + (i * 3) as usize, 7);
+                        st.clt[clim[i]] = bits(dat, pos + (i * 3) as usize, 7);
                     }
                     pos += hclen * 3;
                     for i in hclen..19 {
-                        clt[clim[i]] = 0;
+                        st.clt[clim[i]] = 0;
                     }
-                    let clb = *clt.iter().max().unwrap();
+                    let clb = *st.clt.iter().max().unwrap();
                     let clbmsk = (1 << clb) - 1;
                     if !st.last && pos + tl * (clb + 7) as usize > tbts {
                         break;
                     }
-                    hrmap(&clt, clb, &mut st.clmap, &mut st.le);
+                    hrmap(&st.clt, clb, &mut st.clmap, &mut st.le);
                     let mut i = 0;
                     loop {
                         let r = st.clmap[bits(dat, pos, clbmsk) as usize];
                         pos += (r & 15) as usize;
                         let s = (r >> 4) as u8;
                         if s < 16 {
-                            ldt[i] = s;
+                            st.ldt[i] = s;
                             i += 1;
                         } else {
                             let mut c = 0;
@@ -380,7 +382,7 @@ fn inflt(dat: &[u8], buf: &mut dyn OutputBuffer, st: &mut InflateState) -> Resul
                             if s == 16 {
                                 n = 3 + bits(dat, pos, 3);
                                 pos += 2;
-                                c = ldt[i - 1];
+                                c = st.ldt[i - 1];
                             }
                             else if s == 17 {
                                 n = 3 + bits(dat, pos, 7);
@@ -393,7 +395,7 @@ fn inflt(dat: &[u8], buf: &mut dyn OutputBuffer, st: &mut InflateState) -> Resul
                             let mut un = n as usize;
                             i += un;
                             while un > 0 {
-                                ldt[i - un] = c;
+                                st.ldt[i - un] = c;
                                 un -= 1;
                             }
                         }
@@ -401,8 +403,8 @@ fn inflt(dat: &[u8], buf: &mut dyn OutputBuffer, st: &mut InflateState) -> Resul
                             break;
                         }
                     }
-                    let lt = &ldt[0..hlit];
-                    let dt = &ldt[hlit..tl];
+                    let lt = &st.ldt[0..hlit];
+                    let dt = &st.ldt[hlit..tl];
                     st.lbits = *lt.iter().max().unwrap();
                     st.dbits = *dt.iter().max().unwrap();
                     hrmap(lt, st.lbits, &mut st.lmap, &mut st.le);
@@ -431,7 +433,7 @@ fn inflt(dat: &[u8], buf: &mut dyn OutputBuffer, st: &mut InflateState) -> Resul
             }
             let sym = c >> 4;
             if (sym >> 8) == 0 {
-                buf.w(sym as u8);
+                buf.write(sym as u8);
             } else if sym == 256 {
                 st.head = true;
                 break;
@@ -459,7 +461,7 @@ fn inflt(dat: &[u8], buf: &mut dyn OutputBuffer, st: &mut InflateState) -> Resul
                     return Err(InflateError::UnexpectedEOF);
                 }
                 while add != 0 {
-                    buf.w(buf.back(dt));
+                    buf.write(buf.back(dt));
                     add -= 1;
                 }
             }
@@ -473,7 +475,7 @@ fn inflt(dat: &[u8], buf: &mut dyn OutputBuffer, st: &mut InflateState) -> Resul
 }
 
 pub fn inflate(dat: &[u8], out: &mut dyn OutputBuffer) -> Result<(), InflateError> {
-    out.palloc(dat.len() * 3);
+    out.pre_alloc(dat.len() * 3);
     let mut st = InflateState::new();
     st.last = true;
     inflt(dat, out, &mut st)?;

+ 115 - 40
src/index.ts

@@ -96,7 +96,11 @@ const hMap = ((cd: Uint8Array, mb: number, r: 0 | 1) => {
     }
   } else {
     co = new u16(s);
-    for (i = 0; i < s; ++i) co[i] = rev[le[cd[i] - 1]++] >>> (15 - cd[i]);
+    for (i = 0; i < s; ++i) {
+      if (cd[i]) {
+        co[i] = rev[le[cd[i] - 1]++] >>> (15 - cd[i]);
+      }
+    }
   }
   return co;
 });
@@ -127,13 +131,13 @@ const max = (a: Uint8Array | number[]) => {
 // read d, starting at bit p and mask with m
 const bits = (d: Uint8Array, p: number, m: number) => {
   const o = (p / 8) | 0;
-  return ((d[o] | (d[o + 1] << 8)) >>> (p & 7)) & m;
+  return ((d[o] | (d[o + 1] << 8)) >> (p & 7)) & m;
 }
 
 // read d, starting at bit p continuing for at least 16 bits
 const bits16 = (d: Uint8Array, p: number) => {
   const o = (p / 8) | 0;
-  return ((d[o] | (d[o + 1] << 8) | (d[o + 2] << 16)) >>> (p & 7));
+  return ((d[o] | (d[o + 1] << 8) | (d[o + 2] << 16)) >> (p & 7));
 }
 
 // get end of byte
@@ -579,11 +583,10 @@ const dflt = (dat: Uint8Array, lvl: number, plvl: number, pre: number, post: num
     let lc = 0, eb = 0, i = 0, li = 0, wi = 0, bs = 0;
     for (; i < s; ++i) {
       // hash value
+      // deopt when i > s - 3 - at end, deopt acceptable
       const hv = hsh(i);
-      // index mod 32768
-      let imod = i & 32767;
-      // previous index with this value
-      let pimod = head[hv];
+      // index mod 32768    previous index mod
+      let imod = i & 32767, pimod = head[hv];
       prev[imod] = pimod;
       head[hv] = imod;
       // We always should modify head and prev, but only add symbols if
@@ -674,7 +677,7 @@ const crct = /*#__PURE__*/ (() => {
 
 // CRC32
 const crc = (): CRCV => {
-  let c = 0xFFFFFFFF;
+  let c = -1;
   return {
     p(d) {
       // closures have awful performance
@@ -682,7 +685,7 @@ const crc = (): CRCV => {
       for (let i = 0; i < d.length; ++i) cr = crct[(cr & 255) ^ d[i]] ^ (cr >>> 8);
       c = cr;
     },
-    d() { return c ^ 0xFFFFFFFF }
+    d() { return ~c; }
   }
 }
 
@@ -695,13 +698,16 @@ const adler = (): CRCV => {
       let n = a, m = b;
       const l = d.length;
       for (let i = 0; i != l;) {
-        const e = Math.min(i + 5552, l);
-        for (; i < e; ++i) n += d[i], m += n;
-        n %= 65521, m %= 65521;
+        const e = Math.min(i + 2655, l);
+        for (; i < e; ++i) m += n += d[i];
+        n = (n & 65535) + 15 * (n >> 16), m = (m & 65535) + 15 * (m >> 16);
       }
       a = n, b = m;
     },
-    d() { return ((a >>> 8) << 16 | (b & 255) << 8 | (b >>> 8)) + ((a & 255) << 23) * 2; }
+    d() {
+      a %= 65521, b %= 65521;
+      return ((a >>> 8) << 16 | (b & 255) << 8 | (b >>> 8)) + ((a & 255) << 23) * 2;
+    }
   }
 }
 
@@ -1162,8 +1168,8 @@ export function deflate(data: Uint8Array, opts: AsyncDeflateOptions | FlateCallb
  * @param opts The compression options
  * @returns The deflated version of the data
  */
-export function deflateSync(data: Uint8Array, opts: DeflateOptions = {}) {
-  return dopt(data, opts, 0, 0);
+export function deflateSync(data: Uint8Array, opts?: DeflateOptions) {
+  return dopt(data, opts || {}, 0, 0);
 }
 
 /**
@@ -1409,7 +1415,8 @@ export function gzip(data: Uint8Array, opts: AsyncGzipOptions | FlateCallback, c
  * @param opts The compression options
  * @returns The gzipped version of the data
  */
-export function gzipSync(data: Uint8Array, opts: GzipOptions = {}) {
+export function gzipSync(data: Uint8Array, opts?: GzipOptions) {
+  if (!opts) opts = {};
   const c = crc(), l = data.length;
   c.p(data);
   const d = dopt(data, opts, gzhl(opts), 8), s = d.length;
@@ -1651,7 +1658,8 @@ export function zlib(data: Uint8Array, opts: AsyncZlibOptions | FlateCallback, c
  * @param opts The compression options
  * @returns The zlib-compressed version of the data
  */
-export function zlibSync(data: Uint8Array, opts: ZlibOptions = {}) {
+export function zlibSync(data: Uint8Array, opts: ZlibOptions) {
+  if (!opts) opts = {};
   const a = adler();
   a.p(data);
   const d = dopt(data, opts, 2, 4);
@@ -1920,6 +1928,23 @@ export interface ZipAttributes {
    */
   attrs?: number;
 
+  /**
+   * Extra metadata to add to the file. This field is defined by PKZIP's APPNOTE.txt,
+   * section 4.4.28. At most 65,535 bytes may be used in each ID. The ID must be an
+   * integer between 0 and 65,535, inclusive.
+   * 
+   * This field is incredibly rare and almost never needed except for compliance with
+   * proprietary standards and software.
+   */
+  extra?: Record<number, Uint8Array>;
+
+  /**
+   * The comment to attach to the file. This field is defined by PKZIP's APPNOTE.txt,
+   * section 4.4.26. The comment must be at most 65,535 bytes long UTF-8 encoded. This
+   * field is not read by consumer software.
+   */
+  comment?: string;
+
   /**
    * When the file was last modified. Defaults to the current time.
    */
@@ -2007,9 +2032,9 @@ const fltn = <A extends boolean>(d: A extends true ? AsyncZippable : Zippable, p
 }
 
 // text encoder
-const te = typeof TextEncoder != 'undefined' && new TextEncoder();
+const te = typeof TextEncoder != 'undefined' && /*#__PURE__*/ new TextEncoder();
 // text decoder
-const td = typeof TextDecoder != 'undefined' && new TextDecoder();
+const td = typeof TextDecoder != 'undefined' && /*#__PURE__*/ new TextDecoder();
 // text decoder stream
 let tds = 0;
 try {
@@ -2178,10 +2203,23 @@ const z64e = (d: Uint8Array, b: number) => {
 // zip header file
 type ZHF = Omit<ZipInputFile, 'terminate' | 'ondata' | 'filename'>;
 
+// extra field length
+const exfl = (ex?: ZHF['extra']) => {
+  let le = 0;
+  if (ex) {
+    for (const k in ex) {
+      const l = ex[k].length;
+      if (l > 65535) throw 'extra field too long';
+      le += l + 4;
+    }
+  }
+  return le;
+}
 
 // write zip header
-const wzh = (d: Uint8Array, b: number, f: ZHF, fn: Uint8Array, u: boolean, c?: number, ce?: number) => {
-  const fl = fn.length;
+const wzh = (d: Uint8Array, b: number, f: ZHF, fn: Uint8Array, u: boolean, c?: number, ce?: number, co?: Uint8Array) => {
+  const fl = fn.length, ex = f.extra, col = co && co.length;
+  let exl = exfl(ex);
   wbytes(d, b, ce != null ? 0x2014B50 : 0x4034B50), b += 4;
   if (ce != null) d[b++] = 20, d[b++] = f.os;
   d[b] = 20, b += 2; // spec compliance? what's that?
@@ -2195,13 +2233,25 @@ const wzh = (d: Uint8Array, b: number, f: ZHF, fn: Uint8Array, u: boolean, c?: n
     wbytes(d, b + 4, c);
     wbytes(d, b + 8, f.size);
   }
-  wbytes(d, b + 12, fl), b += 16;
+  wbytes(d, b + 12, fl);
+  wbytes(d, b + 14, exl), b += 16;
   if (ce != null) {
+    wbytes(d, b, col);
     wbytes(d, b + 6, f.attrs);
     wbytes(d, b + 10, ce), b += 14;
   }
   d.set(fn, b);
-  return b + fl;
+  b += fl;
+  if (exl) {
+    for (const k in ex) {
+      const exf = ex[k], l = exf.length;
+      wbytes(d, b, +k);
+      wbytes(d, b + 2, l);
+      d.set(exf, b + 4), b += 4 + l;
+    }
+  }
+  if (col) d.set(co, b), b += col;
+  return b;
 }
 
 // write zip footer (end of central directory)
@@ -2289,6 +2339,8 @@ type AsyncZipDat = ZHF & {
   c: Uint8Array;
   // filename
   f: Uint8Array;
+  // comment
+  m?: Uint8Array;
   // unicode
   u: boolean;
 };
@@ -2308,6 +2360,8 @@ export class ZipPassThrough implements ZipInputFile {
   compression: number;
   os?: number;
   attrs?: number;
+  comment?: string;
+  extra?: Record<number, Uint8Array>;
   mtime?: GzipOptions['mtime'];
   ondata: AsyncFlateStreamHandler;
   private c: CRCV;
@@ -2365,6 +2419,8 @@ export class ZipDeflate implements ZipInputFile {
   flag: 0 | 1 | 2 | 3;
   os?: number;
   attrs?: number;
+  comment?: string;
+  extra?: Record<number, Uint8Array>;
   mtime?: GzipOptions['mtime'];
   ondata: AsyncFlateStreamHandler;
   private d: Deflate;
@@ -2374,7 +2430,8 @@ export class ZipDeflate implements ZipInputFile {
    * @param filename The filename to associate with this data stream
    * @param opts The compression options
    */
-  constructor(filename: string, opts: DeflateOptions = {}) {
+  constructor(filename: string, opts?: DeflateOptions) {
+    if (!opts) opts = {};
     ZipPassThrough.call(this, filename);
     this.d = new Deflate(opts, (dat, final) => {
       this.ondata(null, dat, final);
@@ -2412,6 +2469,8 @@ export class AsyncZipDeflate implements ZipInputFile {
   flag: 0 | 1 | 2 | 3;
   os?: number;
   attrs?: number;
+  comment?: string;
+  extra?: Record<number, Uint8Array>;
   mtime?: GzipOptions['mtime'];
   ondata: AsyncFlateStreamHandler;
   private d: AsyncDeflate;
@@ -2422,7 +2481,8 @@ export class AsyncZipDeflate implements ZipInputFile {
    * @param filename The filename to associate with this data stream
    * @param opts The compression options
    */
-  constructor(filename: string, opts: DeflateOptions = {}) {
+  constructor(filename: string, opts?: DeflateOptions) {
+    if (!opts) opts = {};
     ZipPassThrough.call(this, filename);
     this.d = new AsyncDeflate(opts, (err, dat, final) => {
       this.ondata(err, dat, final);
@@ -2451,6 +2511,8 @@ type ZIFE = {
   c: number;
   // filename
   f: Uint8Array;
+  // comment
+  o?: Uint8Array;
   // unicode
   u: boolean;
   // byte offset
@@ -2490,7 +2552,10 @@ export class Zip {
    */
   add(file: ZipInputFile) {
     if (this.d & 2) throw 'stream finished';
-    const f = strToU8(file.filename), fl = f.length, u = fl != file.filename.length, hl = fl + 30;
+    const f = strToU8(file.filename), fl = f.length;
+    const com = file.comment, o = com && strToU8(com);
+    const u = fl != file.filename.length || (o && (com.length != o.length));
+    const hl = fl + exfl(file.extra) + 30;
     if (fl > 65535) throw 'filename too long';
     const header = new u8(hl);
     wzh(header, 0, file, f, u);
@@ -2505,6 +2570,7 @@ export class Zip {
     const uf = mrg(file, {
       f,
       u,
+      o,
       t: () => { 
         if (file.terminate) file.terminate();
       },
@@ -2566,11 +2632,11 @@ export class Zip {
 
   private e() {
     let bt = 0, l = 0, tl = 0;
-    for (const f of this.u) tl += 46 + f.f.length;
+    for (const f of this.u) tl += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0);
     const out = new u8(tl + 22);
     for (const f of this.u) {
-      wzh(out, bt, f, f.f, f.u, f.c, l);
-      bt += 46 + f.f.length, l += f.b;
+      wzh(out, bt, f, f.f, f.u, f.c, l, f.o);
+      bt += 46 + f.f.length + exfl(f.extra) + (f.o ? f.o.length : 0), l += f.b;
     }
     wzf(out, bt, this.u.length, tl, l)
     this.ondata(null, out, true);
@@ -2627,9 +2693,10 @@ export function zip(data: AsyncZippable, opts: AsyncZipOptions | FlateCallback,
       try {
         const l = f.c.length;
         wzh(out, tot, f, f.f, f.u, l);
-        const loc = tot + 30 + f.f.length;
+        const badd = 30 + f.f.length + exfl(f.extra);
+        const loc = tot + badd;
         out.set(f.c, loc);
-        wzh(out, o, f, f.f, f.u, l, tot), o += 46 + f.f.length, tot = loc + l;
+        wzh(out, o, f, f.f, f.u, l, tot, f.m), o += 16 + badd + (f.m ? f.m.length : 0), tot = loc + l;
       } catch(e) {
         return cb(e, null);
       }
@@ -2645,6 +2712,8 @@ export function zip(data: AsyncZippable, opts: AsyncZipOptions | FlateCallback,
     const c = crc(), size = file.length;
     c.p(file);
     const f = strToU8(fn), s = f.length;
+    const com = p.comment, m = com && strToU8(com), ms = m && m.length;
+    const exl = exfl(p.extra);
     const compression = p.level == 0 ? 0 : 8;
     const cbl: FlateCallback = (e, d) => {
       if (e) {
@@ -2657,11 +2726,12 @@ export function zip(data: AsyncZippable, opts: AsyncZipOptions | FlateCallback,
           crc: c.d(),
           c: d,
           f,
-          u: s != fn.length,
+          m,
+          u: s != fn.length || (m && (com.length != ms)),
           compression
         });
-        o += 30 + s + l;
-        tot += 76 + 2 * s + l;
+        o += 30 + s + exl + l;
+        tot += 76 + 2 * (s + exl) + (ms || 0) + l;
         if (!--lft) cbf();
       }
     }
@@ -2685,7 +2755,8 @@ export function zip(data: AsyncZippable, opts: AsyncZipOptions | FlateCallback,
  * @param opts The main options, merged with per-file options
  * @returns The generated ZIP archive
  */
-export function zipSync(data: Zippable, opts: ZipOptions = {}) {
+export function zipSync(data: Zippable, opts?: ZipOptions) {
+  if (!opts) opts = {};
   const r: FlatZippable<false> = {};
   const files: ZipDat[] = [];
   fltn(data, '', r, opts);
@@ -2695,6 +2766,8 @@ export function zipSync(data: Zippable, opts: ZipOptions = {}) {
     const [file, p] = r[fn];
     const compression = p.level == 0 ? 0 : 8;
     const f = strToU8(fn), s = f.length;
+    const com = p.comment, m = com && strToU8(com), ms = m && m.length;
+    const exl = exfl(p.extra);
     if (s > 65535) throw 'filename too long';
     const d = compression ? deflateSync(file, p) : file, l = d.length;
     const c = crc();
@@ -2704,19 +2777,21 @@ export function zipSync(data: Zippable, opts: ZipOptions = {}) {
       crc: c.d(),
       c: d,
       f,
-      u: s != fn.length,
+      m,
+      u: s != fn.length || (m && (com.length != ms)),
       o,
       compression
     }));
-    o += 30 + s + l;
-    tot += 76 + 2 * s + l;
+    o += 30 + s + exl + l;
+    tot += 76 + 2 * (s + exl) + (ms || 0) + l;
   }
   const out = new u8(tot + 22), oe = o, cdl = tot - o;
   for (let i = 0; i < files.length; ++i) {
     const f = files[i];
     wzh(out, f.o, f, f.f, f.u, f.c.length);
-    out.set(f.c, f.o + 30 + f.f.length);
-    wzh(out, o, f, f.f, f.u, f.c.length, f.o), o += 46 + f.f.length;
+    const badd = 30 + f.f.length + exfl(f.extra);
+    out.set(f.c, f.o + badd);
+    wzh(out, o, f, f.f, f.u, f.c.length, f.o, f.m), o += 16 + badd + (f.m ? f.m.length : 0);
   }
   wzf(out, o, files.length, cdl, oe);
   return out;