UNPKG

6.38 kBJavaScriptView Raw
1var stream = require('stream');
2
3var crc32 = require('buffer-crc32');
4
5var liner = new stream.Transform ({objectMode: true});
6
7function XZFormat (chunk) {
8 this.buf = new Buffer (chunk, "binary");
9}
10
11if (!Buffer.prototype.equals)
12Buffer.prototype.equals = function (a) {
13 if (!Buffer.isBuffer(a)) return undefined;
14 if (a.length !== this.length) return false;
15
16 for (var i = 0; i < a.length; i++) {
17 if (a[i] !== this[i]) return false;
18 }
19
20 return true;
21};
22
23function encMultiInt (num) {
24 var buf = new Buffer (9);
25 if (num > Math.pow (2, 52)) // max int size in js without losing precision
26 return null;
27
28 var i = 0;
29
30 while (num >= 0x80) {
31 buf.writeUInt8 (i, num & 127);
32 num >>= 7;
33 i++;
34 }
35
36 buf.writeUInt8 (i, num);
37
38 return buf.slice (0, i);
39}
40
41function decMultiInt (buf) {
42
43 var i = 0;
44 var num;
45 var byte;
46
47 while (i < buf.length) {
48 byte = buf.readUInt8 (i);
49 num |= byte << i * 7;
50 if (byte | 127 === 0) {
51 break;
52 }
53 }
54
55 return num;
56}
57
58//+-+-+-+-+-+-+-+-+-+-+-+-+=======+=======+ +=======+=======+-+-+-+-+-+-+-+-+-+-+-+-+
59//| Stream Header | Block | Block | ... | Block | Index | Stream Footer |
60//+-+-+-+-+-+-+-+-+-+-+-+-+=======+=======+ +=======+=======+-+-+-+-+-+-+-+-+-+-+-+-+
61
62// we need to read at least 12 bytes, 6 — for magic, 2 for flags and 4 for CRC
63
64//+---+---+---+---+---+---+-------+------+--+--+--+--+
65//| Header Magic Bytes | Stream Flags | CRC32 |
66//+---+---+---+---+---+---+-------+------+--+--+--+--+
67
68XZFormat.prototype.headerMagic = new Buffer ("\xFD7zXZ\x00", "binary");
69
70XZFormat.prototype.readHeader = function () {
71 if (this.buf.length < 12) {
72 return;
73 }
74
75 console.log (this.buf);
76
77 var magicOk = this.headerMagic.equals (this.buf.slice (0, 6));
78 if (!magicOk) {
79 throw "wrong magic!";
80 }
81
82 var nullByte = this.buf.readUInt8(6);
83 var flags = this.buf.readUInt8(7);
84
85 // The first byte of Stream Flags is always a null byte. In the
86 // future, this byte may be used to indicate a new Stream version
87 // or other Stream properties.
88
89 // The second byte of Stream Flags is a bit field:
90
91 // Bit(s) Mask Description
92 // 0-3 0x0F Type of Check (see Section 3.4):
93 // 4-7 0xF0 Reserved for future use; MUST be zero for now.
94 var streamHasCRC32 = flags & 0x1 ? true: false;
95 var streamHasCRC64 = flags & 0x4 ? true: false;
96 var streamHasSHA256 = flags & 0xA ? true: false;
97
98 var flagsB = new Buffer(2);
99 flagsB.writeUInt8 (nullByte, 0);
100 flagsB.writeUInt8 (flags, 1);
101 var flagsCRC = crc32 (flagsB);
102
103 var headerCRC = this.buf.slice (8, 12);
104
105 // TODO: throw if reserved bits is set
106 console.log (magicOk, flags, {
107 CRC32: streamHasCRC32,
108 CRC64: streamHasCRC64,
109 SHA256: streamHasSHA256,
110 }, flagsCRC, headerCRC);
111
112 this.buf = this.buf.slice (12);
113
114 this.streamHeaderRead = true;
115}
116
117//+-+-+-+-+---+---+---+---+-------+------+----------+---------+
118//| CRC32 | Backward Size | Stream Flags | Footer Magic Bytes |
119//+-+-+-+-+---+---+---+---+-------+------+----------+---------+
120
121XZFormat.prototype.footerMagic = new Buffer ("YZ", "binary");
122
123XZFormat.prototype.readFooter = function () {
124
125}
126
127//+==============+=================+===============+=======+
128//| Block Header | Compressed Data | Block Padding | Check |
129//+==============+=================+===============+=======+
130
131function XZBlock (buf) {
132 this.buf = buf;
133}
134
135//+-------------------+-------------+#################+
136//| Block Header Size | Block Flags | Compressed Size |
137//+-------------------+-------------+#################+
138
139//---> +###################+======================+
140//---> | Uncompressed Size | List of Filter Flags |
141//---> +###################+======================+
142
143//---> +================+--+--+--+--+
144//---> | Header Padding | CRC32 |
145//---> +================+--+--+--+--+
146
147XZBlock.prototype.read = function () {
148 if (!this.headerRead && this.buf.length >= 20) {
149 this.readHeader ()
150 return;
151 }
152}
153
154XZBlock.prototype.readHeader = function () {
155 var blockHeaderSizeField = this.buf.readUInt8 (0)
156 var blockHeaderSize = (blockHeaderSizeField + 1) * 4;
157 // Bit(s) Mask Description
158 // 0-1 0x03 Number of filters (1-4)
159 // 2-5 0x3C Reserved for future use; MUST be zero for now.
160 // 6 0x40 The Compressed Size field is present.
161 // 7 0x80 The Uncompressed Size field is present.
162 var blockFlags = this.buf.readUInt8 (1);
163 var blockFilters = blockFlags & 0x03;
164 var blockReserved = blockFlags & 0x3c;
165 var haveComprSize = blockFlags & 0x40;
166 var haveUncomprSize = blockFlags & 0x80;
167 var uncomprOffset = 0;
168
169 var blockHeaderCRC = this.buf.slice (blockHeaderSize - 4, blockHeaderSize);
170
171 if (blockReserved) {
172 throw "reserved must be 0";
173 }
174
175 console.log (this.buf, blockHeaderSize, blockFlags, blockReserved, haveComprSize, haveUncomprSize);
176
177 //if (haveComprSize) {
178 // at most 9 bytes
179 var comprSizeBuf = this.buf.slice (2, 11);
180 console.log (comprSizeBuf);
181 var comprSize = decMultiInt (comprSizeBuf);
182 uncomprOffset = comprSizeBuf.length;
183 //}
184
185 console.log (uncomprOffset);
186
187 //if (haveUncomprSize) {
188 var uncomprSizeBuf = this.buf.slice (2 + uncomprOffset, 11 + uncomprOffset);
189 var uncomprSize = decMultiInt (uncomprSizeBuf);
190 //}
191
192 if (blockHeaderSizeField === 0) {
193 // TODO: handle index
194 throw "index not supported";
195 }
196
197 if (blockFilters) {
198 throw "filters not supported";
199 }
200
201 // TODO: header padding bytes must be 0x00, if not, throw error
202
203 console.log (
204 "block header size:", blockHeaderSize,
205 "compressed size:", comprSize,
206 "uncompressed size:", uncomprSize,
207 "header crc:", blockHeaderCRC
208 );
209
210}
211
212
213XZFormat.prototype.append = function (chunk) {
214 this.buf += chunk;
215}
216
217XZFormat.prototype.readBlock = function () {
218 this.currentBlock = new XZBlock (this.buf);
219
220 this.currentBlock.read();
221}
222
223liner._transform = function (chunk, encoding, done) {
224
225 if (!this.xz) {
226 this.xz = new XZFormat (chunk);
227 } else {
228 this.xz.append (chunk);
229 }
230
231 if (!this.xz.streamHeaderRead) {
232 this.xz.readHeader();
233 this.xz.readBlock();
234 return;
235 } else {
236
237 }
238
239 var data = chunk.toString();
240 if (this._lastLineData) data = this._lastLineData + data;
241
242 var lines = data.split('\n');
243 this._lastLineData = lines.splice(lines.length-1,1)[0];
244
245 lines.forEach(this.push.bind(this));
246 done();
247}
248
249liner._flush = function (done) {
250 if (this._lastLineData) this.push(this._lastLineData);
251 this._lastLineData = null;
252 done();
253}
254
255module.exports = liner;