-
Notifications
You must be signed in to change notification settings - Fork 6
/
inflate.ts
193 lines (162 loc) · 5.53 KB
/
inflate.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
// from https://github.com/nodeca/pako
import { concatUint8Array } from "../utils/uint8.ts";
import * as zlibInflate from "./zlib/inflate.ts";
import STATUS from "./zlib/status.ts";
import { CODE, message as msg } from "./zlib/messages.ts";
import ZStream from "./zlib/zstream.ts";
import GZheader from "./zlib/gzheader.ts";
export interface InflateOptions {
windowBits?: number;
dictionary?: Uint8Array;
chunkSize?: number;
to?: string;
raw?: boolean;
}
export class Inflate {
err: STATUS = 0; // error code, if happens (0 = Z_OK)
msg = ""; // error message
ended = false; // used to avoid multiple onEnd() calls
strm: ZStream;
options: any;
header: GZheader;
constructor(options: InflateOptions) {
this.options = {
chunkSize: 16384,
windowBits: 0,
to: "",
...options,
};
const opt = this.options;
// Force window size for `raw` data, if not set directly,
// because we have no header for autodetect.
if (opt.raw && (opt.windowBits >= 0) && (opt.windowBits < 16)) {
opt.windowBits = -opt.windowBits;
if (opt.windowBits === 0) opt.windowBits = -15;
}
// If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate
if (
(opt.windowBits >= 0) && (opt.windowBits < 16) &&
!(options && options.windowBits)
) {
opt.windowBits += 32;
}
// Gzip header has no info about windows size, we can do autodetect only
// for deflate. So, if window size not set, force it to max when gzip possible
if ((opt.windowBits > 15) && (opt.windowBits < 48)) {
// bit 3 (16) -> gzipped data
// bit 4 (32) -> autodetect gzip/deflate
if ((opt.windowBits & 15) === 0) {
opt.windowBits |= 15;
}
}
this.strm = new ZStream();
this.strm.avail_out = 0;
var status = zlibInflate.inflateInit2(
this.strm,
opt.windowBits,
);
if (status !== STATUS.Z_OK) {
throw new Error(msg[status as CODE]);
}
this.header = new GZheader();
zlibInflate.inflateGetHeader(this.strm, this.header);
// Setup dictionary
if (opt.dictionary) {
if (opt.raw) { //In raw mode we need to set the dictionary early
status = zlibInflate.inflateSetDictionary(this.strm, opt.dictionary);
if (status !== STATUS.Z_OK) {
throw new Error(msg[status as CODE]);
}
}
}
}
push(data: Uint8Array, mode: boolean | number): Uint8Array {
const strm = this.strm;
const chunkSize = this.options.chunkSize;
const dictionary = this.options.dictionary;
const chunks: Uint8Array[] = [];
let status;
// Flag to properly process Z_BUF_ERROR on testing inflate call
// when we check that all output data was flushed.
var allowBufError = false;
if (this.ended) {
throw new Error("can not call after ended");
}
let _mode = (mode === ~~mode)
? mode
: ((mode === true) ? STATUS.Z_FINISH : STATUS.Z_NO_FLUSH);
strm.input = data;
strm.next_in = 0;
strm.avail_in = strm.input.length;
do {
if (strm.avail_out === 0) {
strm.output = new Uint8Array(chunkSize);
strm.next_out = 0;
strm.avail_out = chunkSize;
}
status = zlibInflate.inflate(
strm,
STATUS.Z_NO_FLUSH,
); /* no bad return value */
if (status === STATUS.Z_NEED_DICT && dictionary) {
status = zlibInflate.inflateSetDictionary(this.strm, dictionary);
}
if (status === STATUS.Z_BUF_ERROR && allowBufError === true) {
status = STATUS.Z_OK;
allowBufError = false;
}
if (status !== STATUS.Z_STREAM_END && status !== STATUS.Z_OK) {
this.ended = true;
throw new Error(this.strm.msg);
}
if (strm.next_out) {
if (
strm.avail_out === 0 || status === STATUS.Z_STREAM_END ||
(strm.avail_in === 0 &&
(_mode === STATUS.Z_FINISH || _mode === STATUS.Z_SYNC_FLUSH))
) {
chunks.push(strm.output!.subarray(0, strm.next_out));
}
}
// When no more input data, we should check that internal inflate buffers
// are flushed. The only way to do it when avail_out = 0 - run one more
// inflate pass. But if output data not exists, inflate return Z_BUF_ERROR.
// Here we set flag to process this error properly.
//
// NOTE. Deflate does not return error in this case and does not needs such
// logic.
if (strm.avail_in === 0 && strm.avail_out === 0) {
allowBufError = true;
}
} while (
(strm.avail_in > 0 || strm.avail_out === 0) &&
status !== STATUS.Z_STREAM_END
);
if (status === STATUS.Z_STREAM_END) {
_mode = STATUS.Z_FINISH;
}
// Finalize on the last chunk.
if (_mode === STATUS.Z_FINISH) {
status = zlibInflate.inflateEnd(this.strm);
this.ended = true;
if (status !== STATUS.Z_OK) throw new Error(this.strm.msg);
}
// callback interim results if Z_SYNC_FLUSH.
if (_mode === STATUS.Z_SYNC_FLUSH) {
strm.avail_out = 0;
}
return concatUint8Array(chunks);
}
}
export function inflate(input: Uint8Array, options: InflateOptions = {}) {
const inflator = new Inflate(options);
const result = inflator.push(input, true);
// That will never happens, if you don't cheat with options :)
if (inflator.err) throw inflator.msg || msg[inflator.err as CODE];
return result;
}
export function inflateRaw(input: Uint8Array, options: InflateOptions = {}) {
options.raw = true;
return inflate(input, options);
}
export const gunzip = inflate;