132
|
1 /*******************************************************************************
|
|
2 *
|
|
3 * copyright: Copyright © 2007 Daniel Keep. All rights reserved.
|
|
4 *
|
|
5 * license: BSD style: $(LICENSE)
|
|
6 *
|
|
7 * version: Initial release: December 2007
|
|
8 *
|
|
9 * author: Daniel Keep
|
|
10 *
|
|
11 ******************************************************************************/
|
|
12
|
|
13 module tango.io.archive.Zip;
|
|
14
|
|
15 /*
|
|
16
|
|
17 TODO
|
|
18 ====
|
|
19
|
|
20 * Disable UTF encoding until I've worked out what version of Zip that's
|
|
21 related to... (actually; it's entirely possible that's it's merely a
|
|
22 *proposal* at the moment.) (*Done*)
|
|
23
|
|
24 * Make ZipEntry safe: make them aware that their creating reader has been
|
|
25 destroyed.
|
|
26
|
|
27 */
|
|
28
|
|
29 import tango.core.ByteSwap : ByteSwap;
|
|
30 import tango.io.Buffer : Buffer;
|
|
31 import tango.io.FileConduit : FileConduit;
|
|
32 import tango.io.FilePath : FilePath, PathView;
|
|
33 import tango.io.MappedBuffer : MappedBuffer;
|
|
34 import tango.io.compress.ZlibStream : ZlibInput, ZlibOutput;
|
|
35 import tango.io.digest.Crc32 : Crc32;
|
|
36 import tango.io.model.IConduit : IConduit, InputStream, OutputStream;
|
|
37 import tango.io.stream.DigestStream : DigestInput;
|
|
38 import tango.time.Time : Time, TimeSpan;
|
|
39 import tango.time.WallClock : WallClock;
|
|
40 import tango.time.chrono.Gregorian : Gregorian;
|
|
41
|
|
42 import Integer = tango.text.convert.Integer;
|
|
43
|
|
44 debug(Zip) import tango.io.Stdout : Stderr;
|
|
45
|
|
46 //////////////////////////////////////////////////////////////////////////////
|
|
47 //////////////////////////////////////////////////////////////////////////////
|
|
48 //
|
|
49 // Implementation crap
|
|
50 //
|
|
51 // Why is this here, you ask? Because of bloody DMD forward reference bugs.
|
|
52 // For pete's sake, Walter, FIX THEM, please!
|
|
53 //
|
|
54 // To skip to the actual user-visible stuff, search for "Shared stuff".
|
|
55
|
|
56 private
|
|
57 {
|
|
58
|
|
59 //////////////////////////////////////////////////////////////////////////////
|
|
60 //////////////////////////////////////////////////////////////////////////////
|
|
61 //
|
|
62 // LocalFileHeader
|
|
63 //
|
|
64
|
|
65 align(1)
|
|
66 struct LocalFileHeaderData
|
|
67 {
|
|
68 ushort extract_version = ushort.max;
|
|
69 ushort general_flags = 0;
|
|
70 ushort compression_method = 0;
|
|
71 ushort modification_file_time = 0;
|
|
72 ushort modification_file_date = 0;
|
|
73 uint crc_32 = 0; // offsetof = 10
|
|
74 uint compressed_size = 0;
|
|
75 uint uncompressed_size = 0;
|
|
76 ushort file_name_length = 0;
|
|
77 ushort extra_field_length = 0;
|
|
78
|
|
79 debug(Zip) void dump()
|
|
80 {
|
|
81 Stderr
|
|
82 ("LocalFileHeader.Data {")("\n")
|
|
83 (" extract_version = ")(extract_version)("\n")
|
|
84 (" general_flags = ")(general_flags)("\n")
|
|
85 (" compression_method = ")(compression_method)("\n")
|
|
86 (" modification_file_time = ")(modification_file_time)("\n")
|
|
87 (" modification_file_date = ")(modification_file_date)("\n")
|
|
88 (" crc_32 = ")(crc_32)("\n")
|
|
89 (" compressed_size = ")(compressed_size)("\n")
|
|
90 (" uncompressed_size = ")(uncompressed_size)("\n")
|
|
91 (" file_name_length = ")(file_name_length)("\n")
|
|
92 (" extra_field_length = ")(extra_field_length)("\n")
|
|
93 ("}").newline;
|
|
94 }
|
|
95 }
|
|
96
|
|
97 struct LocalFileHeader
|
|
98 {
|
|
99 const uint signature = 0x04034b50;
|
|
100
|
|
101 alias LocalFileHeaderData Data;
|
|
102 Data data;
|
|
103 static assert( Data.sizeof == 26 );
|
|
104
|
|
105 char[] file_name;
|
|
106 ubyte[] extra_field;
|
|
107
|
|
108 void[] data_arr()
|
|
109 {
|
|
110 return (&data)[0..1];
|
|
111 }
|
|
112
|
|
113 void put(OutputStream output)
|
|
114 {
|
|
115 // Make sure var-length fields will fit.
|
|
116 if( file_name.length > ushort.max )
|
|
117 ZipException.fntoolong;
|
|
118
|
|
119 if( extra_field.length > ushort.max )
|
|
120 ZipException.eftoolong;
|
|
121
|
|
122 // Encode filename
|
|
123 auto file_name = utf8_to_cp437(this.file_name);
|
|
124 scope(exit) if( file_name !is cast(ubyte[])this.file_name )
|
|
125 delete file_name;
|
|
126
|
|
127 if( file_name is null )
|
|
128 ZipException.fnencode;
|
|
129
|
|
130 // Update lengths in data
|
|
131 Data data = this.data;
|
|
132 data.file_name_length = file_name.length;
|
|
133 data.extra_field_length = extra_field.length;
|
|
134
|
|
135 // Do it
|
|
136 version( BigEndian ) swapAll(data);
|
|
137 writeExact(output, (&data)[0..1]);
|
|
138 writeExact(output, file_name);
|
|
139 writeExact(output, extra_field);
|
|
140 }
|
|
141
|
|
142 void fill(InputStream src)
|
|
143 {
|
|
144 readExact(src, data_arr);
|
|
145 version( BigEndian ) swapAll(data);
|
|
146
|
|
147 //debug(Zip) data.dump;
|
|
148
|
|
149 auto tmp = new ubyte[data.file_name_length];
|
|
150 readExact(src, tmp);
|
|
151 file_name = cp437_to_utf8(tmp);
|
|
152 if( cast(char*) tmp.ptr !is file_name.ptr ) delete tmp;
|
|
153
|
|
154 extra_field = new ubyte[data.extra_field_length];
|
|
155 readExact(src, extra_field);
|
|
156 }
|
|
157
|
|
158 /*
|
|
159 * This method will check to make sure that the local and central headers
|
|
160 * are the same; if they're not, then that indicates that the archive is
|
|
161 * corrupt.
|
|
162 */
|
|
163 bool agrees_with(FileHeader h)
|
|
164 {
|
|
165 if( data.extract_version != h.data.extract_version
|
|
166 || data.general_flags != h.data.general_flags
|
|
167 || data.compression_method != h.data.compression_method
|
|
168 || data.modification_file_time != h.data.modification_file_time
|
|
169 || data.modification_file_date != h.data.modification_file_date
|
|
170 || data.crc_32 != h.data.crc_32
|
|
171 || data.compressed_size != h.data.compressed_size
|
|
172 || data.uncompressed_size != h.data.uncompressed_size
|
|
173 || file_name != h.file_name
|
|
174 || extra_field != h.extra_field )
|
|
175 return false;
|
|
176
|
|
177 // We need a separate check for the sizes and crc32, since these will
|
|
178 // be zero if a trailing descriptor was used.
|
|
179 if( !h.usingDataDescriptor && (
|
|
180 data.crc_32 != h.data.crc_32
|
|
181 || data.compressed_size != h.data.compressed_size
|
|
182 || data.uncompressed_size != h.data.uncompressed_size ) )
|
|
183 return false;
|
|
184
|
|
185 return true;
|
|
186 }
|
|
187 }
|
|
188
|
|
189 //////////////////////////////////////////////////////////////////////////////
|
|
190 //////////////////////////////////////////////////////////////////////////////
|
|
191 //
|
|
192 // FileHeader
|
|
193 //
|
|
194
|
|
195 align(1)
|
|
196 struct FileHeaderData
|
|
197 {
|
|
198 ubyte zip_version;
|
|
199 ubyte file_attribute_type;
|
|
200 ushort extract_version;
|
|
201 ushort general_flags;
|
|
202 ushort compression_method;
|
|
203 ushort modification_file_time;
|
|
204 ushort modification_file_date;
|
|
205 uint crc_32;
|
|
206 uint compressed_size;
|
|
207 uint uncompressed_size;
|
|
208 ushort file_name_length;
|
|
209 ushort extra_field_length;
|
|
210 ushort file_comment_length;
|
|
211 ushort disk_number_start;
|
|
212 ushort internal_file_attributes = 0;
|
|
213 uint external_file_attributes = 0;
|
|
214 int relative_offset_of_local_header;
|
|
215
|
|
216 debug(Zip) void dump()
|
|
217 {
|
|
218 Stderr
|
|
219 ("FileHeader.Data {\n")
|
|
220 (" zip_version = ")(zip_version)("\n")
|
|
221 (" file_attribute_type = ")(file_attribute_type)("\n")
|
|
222 (" extract_version = ")(extract_version)("\n")
|
|
223 (" general_flags = ")(general_flags)("\n")
|
|
224 (" compression_method = ")(compression_method)("\n")
|
|
225 (" modification_file_time = ")(modification_file_time)("\n")
|
|
226 (" modification_file_date = ")(modification_file_date)("\n")
|
|
227 (" crc_32 = ")(crc_32)("\n")
|
|
228 (" compressed_size = ")(compressed_size)("\n")
|
|
229 (" uncompressed_size = ")(uncompressed_size)("\n")
|
|
230 (" file_name_length = ")(file_name_length)("\n")
|
|
231 (" extra_field_length = ")(extra_field_length)("\n")
|
|
232 (" file_comment_length = ")(file_comment_length)("\n")
|
|
233 (" disk_number_start = ")(disk_number_start)("\n")
|
|
234 (" internal_file_attributes = ")(internal_file_attributes)("\n")
|
|
235 (" external_file_attributes = ")(external_file_attributes)("\n")
|
|
236 (" relative_offset_of_local_header = ")(relative_offset_of_local_header)
|
|
237 ("\n")
|
|
238 ("}").newline;
|
|
239 }
|
|
240
|
|
241 void fromLocal(LocalFileHeader.Data data)
|
|
242 {
|
|
243 extract_version = data.extract_version;
|
|
244 general_flags = data.general_flags;
|
|
245 compression_method = data.compression_method;
|
|
246 modification_file_time = data.modification_file_time;
|
|
247 modification_file_date = data.modification_file_date;
|
|
248 crc_32 = data.crc_32;
|
|
249 compressed_size = data.compressed_size;
|
|
250 uncompressed_size = data.uncompressed_size;
|
|
251 file_name_length = data.file_name_length;
|
|
252 extra_field_length = data.extra_field_length;
|
|
253 }
|
|
254 }
|
|
255
|
|
256 struct FileHeader
|
|
257 {
|
|
258 const uint signature = 0x02014b50;
|
|
259
|
|
260 alias FileHeaderData Data;
|
|
261 Data* data;
|
|
262 static assert( Data.sizeof == 42 );
|
|
263
|
|
264 char[] file_name;
|
|
265 ubyte[] extra_field;
|
|
266 char[] file_comment;
|
|
267
|
|
268 bool usingDataDescriptor()
|
|
269 {
|
|
270 return !!(data.general_flags & 1<<3);
|
|
271 }
|
|
272
|
|
273 uint compressionOptions()
|
|
274 {
|
|
275 return (data.general_flags >> 1) & 0b11;
|
|
276 }
|
|
277
|
|
278 bool usingUtf8()
|
|
279 {
|
|
280 //return !!(data.general_flags & 1<<11);
|
|
281 return false;
|
|
282 }
|
|
283
|
|
284 void[] data_arr()
|
|
285 {
|
|
286 return (cast(void*)data)[0 .. Data.sizeof];
|
|
287 }
|
|
288
|
|
289 void put(OutputStream output)
|
|
290 {
|
|
291 // Make sure the var-length fields will fit.
|
|
292 if( file_name.length > ushort.max )
|
|
293 ZipException.fntoolong;
|
|
294
|
|
295 if( extra_field.length > ushort.max )
|
|
296 ZipException.eftoolong;
|
|
297
|
|
298 if( file_comment.length > ushort.max )
|
|
299 ZipException.cotoolong;
|
|
300
|
|
301 // encode the filename and comment
|
|
302 auto file_name = utf8_to_cp437(this.file_name);
|
|
303 scope(exit) if( file_name !is cast(ubyte[])this.file_name )
|
|
304 delete file_name;
|
|
305 auto file_comment = utf8_to_cp437(this.file_comment);
|
|
306 scope(exit) if( file_comment !is cast(ubyte[])this.file_comment )
|
|
307 delete file_comment;
|
|
308
|
|
309 if( file_name is null )
|
|
310 ZipException.fnencode;
|
|
311
|
|
312 if( file_comment is null && this.file_comment !is null )
|
|
313 ZipException.coencode;
|
|
314
|
|
315 // Update the lengths
|
|
316 Data data = *(this.data);
|
|
317 data.file_name_length = file_name.length;
|
|
318 data.extra_field_length = extra_field.length;
|
|
319 data.file_comment_length = file_comment.length;
|
|
320
|
|
321 // Ok; let's do this!
|
|
322 version( BigEndian ) swapAll(data);
|
|
323 writeExact(output, (&data)[0..1]);
|
|
324 writeExact(output, file_name);
|
|
325 writeExact(output, extra_field);
|
|
326 writeExact(output, file_comment);
|
|
327 }
|
|
328
|
|
329 long map(void[] src)
|
|
330 {
|
|
331 //debug(Zip) Stderr.formatln("FileHeader.map([0..{}])",src.length);
|
|
332
|
|
333 auto old_ptr = src.ptr;
|
|
334
|
|
335 data = cast(Data*) src.ptr;
|
|
336 src = src[Data.sizeof..$];
|
|
337 version( BigEndian ) swapAll(*data);
|
|
338
|
|
339 //debug(Zip) data.dump;
|
|
340
|
|
341 char[] function(ubyte[]) conv_fn;
|
|
342 if( usingUtf8 )
|
|
343 conv_fn = &cp437_to_utf8;
|
|
344 else
|
|
345 conv_fn = &utf8_to_utf8;
|
|
346
|
|
347 file_name = conv_fn(
|
|
348 cast(ubyte[]) src[0..data.file_name_length]);
|
|
349 src = src[data.file_name_length..$];
|
|
350
|
|
351 extra_field = cast(ubyte[]) src[0..data.extra_field_length];
|
|
352 src = src[data.extra_field_length..$];
|
|
353
|
|
354 file_comment = conv_fn(
|
|
355 cast(ubyte[]) src[0..data.file_comment_length]);
|
|
356 src = src[data.file_comment_length..$];
|
|
357
|
|
358 // Return how many bytes we've eaten
|
|
359 //debug(Zip) Stderr.formatln(" . used {} bytes", cast(long)(src.ptr - old_ptr));
|
|
360 return cast(long)(src.ptr - old_ptr);
|
|
361 }
|
|
362 }
|
|
363
|
|
364 //////////////////////////////////////////////////////////////////////////////
|
|
365 //////////////////////////////////////////////////////////////////////////////
|
|
366 //
|
|
367 // EndOfCDRecord
|
|
368 //
|
|
369
|
|
370 align(1)
|
|
371 struct EndOfCDRecordData
|
|
372 {
|
|
373 ushort disk_number = 0;
|
|
374 ushort disk_with_start_of_central_directory = 0;
|
|
375 ushort central_directory_entries_on_this_disk;
|
|
376 ushort central_directory_entries_total;
|
|
377 uint size_of_central_directory;
|
|
378 uint offset_of_start_of_cd_from_starting_disk;
|
|
379 ushort file_comment_length;
|
|
380
|
|
381 debug(Zip) void dump()
|
|
382 {
|
|
383 Stderr
|
|
384 .formatln("EndOfCDRecord.Data {}","{")
|
|
385 .formatln(" disk_number = {}", disk_number)
|
|
386 .formatln(" disk_with_start_of_central_directory = {}",
|
|
387 disk_with_start_of_central_directory)
|
|
388 .formatln(" central_directory_entries_on_this_disk = {}",
|
|
389 central_directory_entries_on_this_disk)
|
|
390 .formatln(" central_directory_entries_total = {}",
|
|
391 central_directory_entries_total)
|
|
392 .formatln(" size_of_central_directory = {}",
|
|
393 size_of_central_directory)
|
|
394 .formatln(" offset_of_start_of_cd_from_starting_disk = {}",
|
|
395 offset_of_start_of_cd_from_starting_disk)
|
|
396 .formatln(" file_comment_length = {}", file_comment_length)
|
|
397 .formatln("}");
|
|
398 }
|
|
399 }
|
|
400
|
|
401 struct EndOfCDRecord
|
|
402 {
|
|
403 const uint signature = 0x06054b50;
|
|
404
|
|
405 alias EndOfCDRecordData Data;
|
|
406 Data data;
|
|
407 static assert( data.sizeof == 18 );
|
|
408
|
|
409 char[] file_comment;
|
|
410
|
|
411 void[] data_arr()
|
|
412 {
|
|
413 return (cast(void*)&data)[0 .. data.sizeof];
|
|
414 }
|
|
415
|
|
416 void put(OutputStream output)
|
|
417 {
|
|
418 // Set up the comment; check length, encode
|
|
419 if( file_comment.length > ushort.max )
|
|
420 ZipException.cotoolong;
|
|
421
|
|
422 auto file_comment = utf8_to_cp437(this.file_comment);
|
|
423 scope(exit) if( file_comment !is cast(ubyte[])this.file_comment )
|
|
424 delete file_comment;
|
|
425
|
|
426 // Set up data block
|
|
427 Data data = this.data;
|
|
428 data.file_comment_length = file_comment.length;
|
|
429
|
|
430 version( BigEndian ) swapAll(data);
|
|
431 writeExact(output, (&data)[0..1]);
|
|
432 }
|
|
433
|
|
434 void fill(void[] src)
|
|
435 {
|
|
436 //Stderr.formatln("EndOfCDRecord.fill([0..{}])",src.length);
|
|
437
|
|
438 auto _data = data_arr;
|
|
439 _data[] = src[0.._data.length];
|
|
440 src = src[_data.length..$];
|
|
441 version( BigEndian ) swapAll(data);
|
|
442
|
|
443 //data.dump;
|
|
444
|
|
445 file_comment = cast(char[]) src[0..data.file_comment_length].dup;
|
|
446 }
|
|
447 }
|
|
448
|
|
449 // End of implementation crap
|
|
450 }
|
|
451
|
|
452 //////////////////////////////////////////////////////////////////////////////
|
|
453 //////////////////////////////////////////////////////////////////////////////
|
|
454 //
|
|
455 // Shared stuff
|
|
456
|
|
457 public
|
|
458 {
|
|
459 /**
|
|
460 * This enumeration denotes the kind of compression used on a file.
|
|
461 */
|
|
462 enum Method
|
|
463 {
|
|
464 /// No compression should be used.
|
|
465 Store,
|
|
466 /// Deflate compression.
|
|
467 Deflate,
|
|
468 /**
|
|
469 * This is a special value used for unsupported or unrecognised
|
|
470 * compression methods. This value is only used internally.
|
|
471 */
|
|
472 Unsupported
|
|
473 }
|
|
474 }
|
|
475
|
|
476 private
|
|
477 {
|
|
478 const ushort ZIP_VERSION = 20;
|
|
479 const ushort MAX_EXTRACT_VERSION = 20;
|
|
480
|
|
481 /* compression flags
|
|
482 uses trailing descriptor |
|
|
483 utf-8 encoding | |
|
|
484 ^ ^ /\ */
|
|
485 const ushort SUPPORTED_FLAGS = 0b00_0_0_0_0000_0_0_0_1_11_0;
|
|
486 const ushort UNSUPPORTED_FLAGS = ~SUPPORTED_FLAGS;
|
|
487
|
|
488 Method toMethod(ushort method)
|
|
489 {
|
|
490 switch( method )
|
|
491 {
|
|
492 case 0: return Method.Store;
|
|
493 case 8: return Method.Deflate;
|
|
494 default: return Method.Unsupported;
|
|
495 }
|
|
496 }
|
|
497
|
|
498 ushort fromMethod(Method method)
|
|
499 {
|
|
500 switch( method )
|
|
501 {
|
|
502 case Method.Store: return 0;
|
|
503 case Method.Deflate: return 8;
|
|
504 default:
|
|
505 assert(false, "unsupported compression method");
|
|
506 }
|
|
507 }
|
|
508
|
|
509 /* NOTE: This doesn't actually appear to work. Using the default magic
|
|
510 * number with Tango's Crc32 digest works, however.
|
|
511 */
|
|
512 //const CRC_MAGIC = 0xdebb20e3u;
|
|
513 }
|
|
514
|
|
515 //////////////////////////////////////////////////////////////////////////////
|
|
516 //////////////////////////////////////////////////////////////////////////////
|
|
517 //
|
|
518 // ZipReader
|
|
519
|
|
520 interface ZipReader
|
|
521 {
|
|
522 bool streamed();
|
|
523 void close();
|
|
524 bool more();
|
|
525 ZipEntry get();
|
|
526 ZipEntry get(ZipEntry);
|
|
527 int opApply(int delegate(ref ZipEntry));
|
|
528 }
|
|
529
|
|
530 //////////////////////////////////////////////////////////////////////////////
|
|
531 //////////////////////////////////////////////////////////////////////////////
|
|
532 //
|
|
533 // ZipWriter
|
|
534
|
|
535 interface ZipWriter
|
|
536 {
|
|
537 void finish();
|
|
538 void putFile(ZipEntryInfo info, char[] path);
|
|
539 void putFile(ZipEntryInfo info, PathView path);
|
|
540 void putStream(ZipEntryInfo info, InputStream source);
|
|
541 void putEntry(ZipEntryInfo info, ZipEntry entry);
|
|
542 void putData(ZipEntryInfo info, void[] data);
|
|
543 Method method();
|
|
544 Method method(Method);
|
|
545 }
|
|
546
|
|
547 //////////////////////////////////////////////////////////////////////////////
|
|
548 //////////////////////////////////////////////////////////////////////////////
|
|
549 //
|
|
550 // ZipBlockReader
|
|
551
|
|
552 /**
|
|
553 * The ZipBlockReader class is used to parse a Zip archive. It exposes the
|
|
554 * contents of the archive via an iteration interface. For instance, to loop
|
|
555 * over all files in an archive, one can use either
|
|
556 *
|
|
557 * -----
|
|
558 * foreach( entry ; reader )
|
|
559 * ...
|
|
560 * -----
|
|
561 *
|
|
562 * Or
|
|
563 *
|
|
564 * -----
|
|
565 * while( reader.more )
|
|
566 * {
|
|
567 * auto entry = reader.get;
|
|
568 * ...
|
|
569 * }
|
|
570 * -----
|
|
571 *
|
|
572 * See the ZipEntry class for more information on the contents of entries.
|
|
573 *
|
|
574 * Note that this class can only be used with input sources which can be
|
|
575 * freely seeked. Also note that you may open a ZipEntry instance produced by
|
|
576 * this reader at any time until the ZipReader that created it is closed.
|
|
577 */
|
|
578 class ZipBlockReader : ZipReader
|
|
579 {
|
|
580 /**
|
|
581 * Creates a ZipBlockReader using the specified file on the local
|
|
582 * filesystem.
|
|
583 */
|
|
584 this(char[] path)
|
|
585 {
|
|
586 this(FilePath(path));
|
|
587 }
|
|
588
|
|
589 /// ditto
|
|
590 this(PathView path)
|
|
591 {
|
|
592 file_source = new FileConduit(path);
|
|
593 this(file_source);
|
|
594 }
|
|
595
|
|
596 version( none )
|
|
597 {
|
|
598 /**
|
|
599 * Creates a ZipBlockReader using the provided FileConduit instance. Where
|
|
600 * possible, the conduit will be wrapped in a memory-mapped buffer for
|
|
601 * optimum performance. If you do not want the FileConduit memory-mapped,
|
|
602 * either cast it to an InputStream first, or pass source.input to the
|
|
603 * constructor.
|
|
604 */
|
|
605 this(FileConduit source)
|
|
606 {
|
|
607 // BUG: MappedBuffer doesn't implement IConduit.Seek
|
|
608 //mm_source = new MappedBuffer(source);
|
|
609 //this(mm_source);
|
|
610 this(source.input);
|
|
611 }
|
|
612 }
|
|
613
|
|
614 /**
|
|
615 * Creates a ZipBlockReader using the provided InputStream. Please note
|
|
616 * that this InputStream must also implement the IConduit.Seek interface.
|
|
617 */
|
|
618 this(InputStream source)
|
|
619 in
|
|
620 {
|
|
621 assert( cast(IConduit.Seek) source, "source stream must be seekable" );
|
|
622 }
|
|
623 body
|
|
624 {
|
|
625 this.source = source;
|
|
626 this.seeker = cast(IConduit.Seek) source;
|
|
627 }
|
|
628
|
|
629 bool streamed() { return false; }
|
|
630
|
|
631 /**
|
|
632 * Closes the reader, and releases all resources. After this operation,
|
|
633 * all ZipEntry instances created by this ZipReader are invalid and should
|
|
634 * not be used.
|
|
635 */
|
|
636 void close()
|
|
637 {
|
|
638 state = State.Done;
|
|
639 source = null;
|
|
640 seeker = null;
|
|
641 delete headers;
|
|
642 delete cd_data;
|
|
643
|
|
644 if( file_source !is null ) delete file_source;
|
|
645 if( mm_source !is null ) delete mm_source;
|
|
646 }
|
|
647
|
|
648 /**
|
|
649 * Returns true if and only if there are additional files in the archive
|
|
650 * which have not been read via the get method. This returns true before
|
|
651 * the first call to get (assuming the opened archive is non-empty), and
|
|
652 * false after the last file has been accessed.
|
|
653 */
|
|
654 bool more()
|
|
655 {
|
|
656 switch( state )
|
|
657 {
|
|
658 case State.Init:
|
|
659 read_cd;
|
|
660 assert( state == State.Open );
|
|
661 return more;
|
|
662
|
|
663 case State.Open:
|
|
664 return (current_index < headers.length);
|
|
665
|
|
666 case State.Done:
|
|
667 return false;
|
|
668 }
|
|
669 }
|
|
670
|
|
671 /**
|
|
672 * Retrieves the next file from the archive. Note that although this does
|
|
673 * perform IO operations, it will not read the contents of the file.
|
|
674 *
|
|
675 * The optional reuse argument can be used to instruct the reader to reuse
|
|
676 * an existing ZipEntry instance. If passed a null reference, it will
|
|
677 * create a new ZipEntry instance.
|
|
678 */
|
|
679 ZipEntry get()
|
|
680 {
|
|
681 if( !more )
|
|
682 ZipExhaustedException();
|
|
683
|
|
684 return new ZipEntry(headers[current_index++], &open_file);
|
|
685 }
|
|
686
|
|
687 /// ditto
|
|
688 ZipEntry get(ZipEntry reuse)
|
|
689 {
|
|
690 if( !more )
|
|
691 ZipExhaustedException();
|
|
692
|
|
693 if( reuse is null )
|
|
694 return new ZipEntry(headers[current_index++], &open_file);
|
|
695 else
|
|
696 return reuse.reset(headers[current_index++], &open_file);
|
|
697 }
|
|
698
|
|
699 /**
|
|
700 * This is used to iterate over the contents of an archive using a foreach
|
|
701 * loop. Please note that the iteration will reuse the ZipEntry instance
|
|
702 * passed to your loop. If you wish to keep the instance and re-use it
|
|
703 * later, you $(B must) use the dup member to create a copy.
|
|
704 */
|
|
705 int opApply(int delegate(ref ZipEntry) dg)
|
|
706 {
|
|
707 int result = 0;
|
|
708 ZipEntry entry;
|
|
709
|
|
710 while( more )
|
|
711 {
|
|
712 entry = get(entry);
|
|
713
|
|
714 result = dg(entry);
|
|
715 if( result )
|
|
716 break;
|
|
717 }
|
|
718
|
|
719 if( entry !is null )
|
|
720 delete entry;
|
|
721
|
|
722 return result;
|
|
723 }
|
|
724
|
|
725 private:
|
|
726 InputStream source;
|
|
727 IConduit.Seek seeker;
|
|
728
|
|
729 enum State { Init, Open, Done }
|
|
730 State state;
|
|
731 size_t current_index = 0;
|
|
732 FileHeader[] headers;
|
|
733
|
|
734 // These should be killed when the reader is closed.
|
|
735 ubyte[] cd_data;
|
|
736 FileConduit file_source = null;
|
|
737 MappedBuffer mm_source = null;
|
|
738
|
|
739 /*
|
|
740 * This function will read the contents of the central directory. Split
|
|
741 * or spanned archives aren't supported.
|
|
742 */
|
|
743 void read_cd()
|
|
744 in
|
|
745 {
|
|
746 assert( state == State.Init );
|
|
747 assert( headers is null );
|
|
748 assert( cd_data is null );
|
|
749 }
|
|
750 out
|
|
751 {
|
|
752 assert( state == State.Open );
|
|
753 assert( headers !is null );
|
|
754 assert( cd_data !is null );
|
|
755 assert( current_index == 0 );
|
|
756 }
|
|
757 body
|
|
758 {
|
|
759 //Stderr.formatln("ZipReader.read_cd()");
|
|
760
|
|
761 // First, we need to locate the end of cd record, so that we know
|
|
762 // where the cd itself is, and how big it is.
|
|
763 auto eocdr = read_eocd_record;
|
|
764
|
|
765 // Now, make sure the archive is all in one file.
|
|
766 if( eocdr.data.disk_number !=
|
|
767 eocdr.data.disk_with_start_of_central_directory
|
|
768 || eocdr.data.central_directory_entries_on_this_disk !=
|
|
769 eocdr.data.central_directory_entries_total )
|
|
770 ZipNotSupportedException.spanned;
|
|
771
|
|
772 // Ok, read the whole damn thing in one go.
|
|
773 cd_data = new ubyte[eocdr.data.size_of_central_directory];
|
|
774 long cd_offset = eocdr.data.offset_of_start_of_cd_from_starting_disk;
|
|
775 seeker.seek(cd_offset, IConduit.Seek.Anchor.Begin);
|
|
776 readExact(source, cd_data);
|
|
777
|
|
778 // Cake. Now, we need to break it up into records.
|
|
779 headers = new FileHeader[
|
|
780 eocdr.data.central_directory_entries_total];
|
|
781
|
|
782 long cdr_offset = cd_offset;
|
|
783
|
|
784 // Ok, map the CD data into file headers.
|
|
785 foreach( i,ref header ; headers )
|
|
786 {
|
|
787 //Stderr.formatln(" . reading header {}...", i);
|
|
788
|
|
789 // Check signature
|
|
790 {
|
|
791 uint sig = (cast(uint[])(cd_data[0..4]))[0];
|
|
792 version( BigEndian ) swap(sig);
|
|
793 if( sig != FileHeader.signature )
|
|
794 ZipException.badsig("file header");
|
|
795 }
|
|
796
|
|
797 auto used = header.map(cd_data[4..$]);
|
|
798 cd_data = cd_data[4+used..$];
|
|
799
|
|
800 // Update offset for next record
|
|
801 cdr_offset += 4 /* for sig. */ + used;
|
|
802 }
|
|
803
|
|
804 // Done!
|
|
805 state = State.Open;
|
|
806 }
|
|
807
|
|
808 /*
|
|
809 * This will locate the end of CD record in the open stream.
|
|
810 *
|
|
811 * This code sucks, but that's because Zip sucks.
|
|
812 *
|
|
813 * Basically, the EOCD record is stuffed somewhere at the end of the file.
|
|
814 * In a brilliant move, the record is *variably sized*, which means we
|
|
815 * have to do a linear backwards search to find it.
|
|
816 *
|
|
817 * The header itself (including the signature) is at minimum 22 bytes
|
|
818 * long, plus anywhere between 0 and 2^16-1 bytes of comment. That means
|
|
819 * we need to read the last 2^16-1 + 22 bytes from the file, and look for
|
|
820 * the signature [0x50,0x4b,0x05,0x06] in [0 .. $-18].
|
|
821 *
|
|
822 * If we find the EOCD record, we'll return its contents. If we couldn't
|
|
823 * find it, we'll throw an exception.
|
|
824 */
|
|
825 EndOfCDRecord read_eocd_record()
|
|
826 in
|
|
827 {
|
|
828 assert( state == State.Init );
|
|
829 }
|
|
830 body
|
|
831 {
|
|
832 //Stderr.formatln("read_eocd_record()");
|
|
833
|
|
834 // Signature + record + max. comment length
|
|
835 const max_chunk_len = 4 + EndOfCDRecord.Data.sizeof + ushort.max;
|
|
836
|
|
837 auto file_len = seeker.seek(0, IConduit.Seek.Anchor.End);
|
|
838
|
|
839 // We're going to need min(max_chunk_len, file_len) bytes.
|
|
840 long chunk_len = max_chunk_len;
|
|
841 if( file_len < max_chunk_len )
|
|
842 chunk_len = file_len;
|
|
843 //Stderr.formatln(" . chunk_len = {}", chunk_len);
|
|
844
|
|
845 // Seek back and read in the chunk. Don't forget to clean up after
|
|
846 // ourselves.
|
|
847 seeker.seek(-chunk_len, IConduit.Seek.Anchor.End);
|
|
848 auto chunk_offset = seeker.seek(0, IConduit.Seek.Anchor.Current);
|
|
849 //Stderr.formatln(" . chunk_offset = {}", chunk_offset);
|
|
850 auto chunk = new ubyte[chunk_len];
|
|
851 scope(exit) delete chunk;
|
|
852 readExact(source, chunk);
|
|
853
|
|
854 // Now look for our magic number. Don't forget that on big-endian
|
|
855 // machines, we need to byteswap the value we're looking for.
|
|
856 version( BigEndian )
|
|
857 uint eocd_magic = swap(EndOfCDRecord.signature);
|
|
858 else
|
|
859 uint eocd_magic = EndOfCDRecord.signature;
|
|
860
|
|
861 size_t eocd_loc = -1;
|
|
862
|
|
863 for( size_t i=chunk_len-18; i>=0; --i )
|
|
864 {
|
|
865 if( *(cast(uint*)(chunk.ptr+i)) == eocd_magic )
|
|
866 {
|
|
867 // Found the bugger! Make sure we skip the signature (forgot
|
|
868 // to do that originally; talk about weird errors :P)
|
|
869 eocd_loc = i+4;
|
|
870 break;
|
|
871 }
|
|
872 }
|
|
873
|
|
874 // If we didn't find it, then we'll assume that this is not a valid
|
|
875 // archive.
|
|
876 if( eocd_loc == -1 )
|
|
877 ZipException.missingdir;
|
|
878
|
|
879 // Ok, so we found it; now what? Now we need to read the record
|
|
880 // itself in. eocd_loc is the offset within the chunk where the eocd
|
|
881 // record was found, so slice it out.
|
|
882 EndOfCDRecord eocdr;
|
|
883 eocdr.fill(chunk[eocd_loc..$]);
|
|
884
|
|
885 // Excellent. We're done here.
|
|
886 return eocdr;
|
|
887 }
|
|
888
|
|
889 /*
|
|
890 * Opens the specified file for reading. If the raw argument passed is
|
|
891 * true, then the file is *not* decompressed.
|
|
892 */
|
|
893 InputStream open_file(FileHeader header, bool raw)
|
|
894 {
|
|
895 // Check to make sure that we actually *can* open this file.
|
|
896 if( header.data.extract_version > MAX_EXTRACT_VERSION )
|
|
897 ZipNotSupportedException.zipver(header.data.extract_version);
|
|
898
|
|
899 if( header.data.general_flags & UNSUPPORTED_FLAGS )
|
|
900 ZipNotSupportedException.flags;
|
|
901
|
|
902 if( toMethod(header.data.compression_method) == Method.Unsupported )
|
|
903 ZipNotSupportedException.method(header.data.compression_method);
|
|
904
|
|
905 // Open a raw stream
|
|
906 InputStream stream = open_file_raw(header);
|
|
907
|
|
908 // If that's all they wanted, pass it back.
|
|
909 if( raw )
|
|
910 return stream;
|
|
911
|
|
912 // Next up, wrap in an appropriate decompression stream
|
|
913 switch( toMethod(header.data.compression_method) )
|
|
914 {
|
|
915 case Method.Store:
|
|
916 // Do nothing: \o/
|
|
917 break;
|
|
918
|
|
919 case Method.Deflate:
|
|
920 // Wrap in a zlib stream. -15 means to use a 32KB window, and
|
|
921 // not to look for the normal zlib header and trailer.
|
|
922 stream = new ZlibInput(stream, -15);
|
|
923 break;
|
|
924
|
|
925 default:
|
|
926 assert(false);
|
|
927 }
|
|
928
|
|
929 // We done, yo!
|
|
930 return stream;
|
|
931 }
|
|
932
|
|
933 /*
|
|
934 * Opens a file's raw input stream. Basically, this returns a slice of
|
|
935 * the archive's input stream.
|
|
936 */
|
|
937 InputStream open_file_raw(FileHeader header)
|
|
938 {
|
|
939 // Seek to and parse the local file header
|
|
940 seeker.seek(header.data.relative_offset_of_local_header,
|
|
941 IConduit.Seek.Anchor.Begin);
|
|
942
|
|
943 {
|
|
944 uint sig;
|
|
945 readExact(source, (&sig)[0..1]);
|
|
946 version( BigEndian ) swap(sig);
|
|
947 if( sig != LocalFileHeader.signature )
|
|
948 ZipException.badsig("local file header");
|
|
949 }
|
|
950
|
|
951 LocalFileHeader lheader; lheader.fill(source);
|
|
952
|
|
953 if( !lheader.agrees_with(header) )
|
|
954 ZipException.incons(header.file_name);
|
|
955
|
|
956 // Ok; get a slice stream for the file
|
|
957 return new SliceSeekInputStream(
|
|
958 source, seeker.seek(0, IConduit.Seek.Anchor.Current),
|
|
959 header.data.compressed_size);
|
|
960 }
|
|
961 }
|
|
962
|
|
963 //////////////////////////////////////////////////////////////////////////////
|
|
964 //////////////////////////////////////////////////////////////////////////////
|
|
965 //
|
|
966 // ZipBlockWriter
|
|
967
|
|
968 /**
|
|
969 * The ZipBlockWriter class is used to create a Zip archive. It uses a
|
|
970 * writing iterator interface.
|
|
971 *
|
|
972 * Note that this class can only be used with output streams which can be
|
|
973 * freely seeked.
|
|
974 */
|
|
975
|
|
976 class ZipBlockWriter : ZipWriter
|
|
977 {
|
|
978 /**
|
|
979 * Creates a ZipBlockWriter using the specified file on the local
|
|
980 * filesystem.
|
|
981 */
|
|
982 this(char[] path)
|
|
983 {
|
|
984 this(FilePath(path));
|
|
985 }
|
|
986
|
|
987 /// ditto
|
|
988 this(PathView path)
|
|
989 {
|
|
990 file_output = new FileConduit(path, FileConduit.WriteCreate);
|
|
991 this(file_output);
|
|
992 }
|
|
993
|
|
994 /**
|
|
995 * Creates a ZipBlockWriter using the provided OutputStream. Please note
|
|
996 * that this OutputStream must also implement the IConduit.Seek interface.
|
|
997 */
|
|
998 this(OutputStream output)
|
|
999 in
|
|
1000 {
|
|
1001 assert( output !is null );
|
|
1002 assert( (cast(IConduit.Seek) output) !is null );
|
|
1003 }
|
|
1004 body
|
|
1005 {
|
|
1006 this.output = output;
|
|
1007 this.seeker = cast(IConduit.Seek) output;
|
|
1008
|
|
1009 // Default to Deflate compression
|
|
1010 method = Method.Deflate;
|
|
1011 }
|
|
1012
|
|
1013 /**
|
|
1014 * Finalises the archive, writes out the central directory, and closes the
|
|
1015 * output stream.
|
|
1016 */
|
|
1017 void finish()
|
|
1018 {
|
|
1019 put_cd;
|
|
1020 output.close();
|
|
1021 output = null;
|
|
1022 seeker = null;
|
|
1023
|
|
1024 if( file_output !is null ) delete file_output;
|
|
1025 }
|
|
1026
|
|
1027 /**
|
|
1028 * Adds a file from the local filesystem to the archive.
|
|
1029 */
|
|
1030 void putFile(ZipEntryInfo info, char[] path)
|
|
1031 {
|
|
1032 putFile(info, FilePath(path));
|
|
1033 }
|
|
1034
|
|
1035 /// ditto
|
|
1036 void putFile(ZipEntryInfo info, PathView path)
|
|
1037 {
|
|
1038 scope file = new FileConduit(path);
|
|
1039 scope(exit) file.close();
|
|
1040 putStream(info, file);
|
|
1041 }
|
|
1042
|
|
1043 /**
|
|
1044 * Adds a file using the contents of the given InputStream to the archive.
|
|
1045 */
|
|
1046 void putStream(ZipEntryInfo info, InputStream source)
|
|
1047 {
|
|
1048 put_compressed(info, source);
|
|
1049 }
|
|
1050
|
|
1051 /**
|
|
1052 * Transfers a file from another archive into this archive. Note that
|
|
1053 * this method will not perform any compression: whatever compression was
|
|
1054 * applied to the file originally will be preserved.
|
|
1055 */
|
|
1056 void putEntry(ZipEntryInfo info, ZipEntry entry)
|
|
1057 {
|
|
1058 put_raw(info, entry);
|
|
1059 }
|
|
1060
|
|
1061 /**
|
|
1062 * Adds a file using the contents of the given array to the archive.
|
|
1063 */
|
|
1064 void putData(ZipEntryInfo info, void[] data)
|
|
1065 {
|
|
1066 //scope mc = new MemoryConduit(data);
|
|
1067 scope mc = new Buffer(data);
|
|
1068 scope(exit) mc.close;
|
|
1069 put_compressed(info, mc);
|
|
1070 }
|
|
1071
|
|
1072 /**
|
|
1073 * This property allows you to control what compression method should be
|
|
1074 * used for files being added to the archive.
|
|
1075 */
|
|
1076 Method method() { return _method; }
|
|
1077 Method method(Method v) { return _method = v; } /// ditto
|
|
1078
|
|
1079 private:
|
|
1080 OutputStream output;
|
|
1081 IConduit.Seek seeker;
|
|
1082 FileConduit file_output;
|
|
1083
|
|
1084 Method _method;
|
|
1085
|
|
1086 struct Entry
|
|
1087 {
|
|
1088 FileHeaderData data;
|
|
1089 long header_position;
|
|
1090 char[] filename;
|
|
1091 char[] comment;
|
|
1092 ubyte[] extra;
|
|
1093 }
|
|
1094 Entry[] entries;
|
|
1095
|
|
1096 void put_cd()
|
|
1097 {
|
|
1098 // check that there aren't too many CD entries
|
|
1099 if( entries.length > ushort.max )
|
|
1100 ZipException.toomanyentries;
|
|
1101
|
|
1102 auto cd_pos = seeker.seek(0, IConduit.Seek.Anchor.Current);
|
|
1103 if( cd_pos > uint.max )
|
|
1104 ZipException.toolong;
|
|
1105
|
|
1106 foreach( entry ; entries )
|
|
1107 {
|
|
1108 FileHeader header;
|
|
1109 header.data = &entry.data;
|
|
1110 header.file_name = entry.filename;
|
|
1111 header.extra_field = entry.extra;
|
|
1112 header.file_comment = entry.comment;
|
|
1113
|
|
1114 write(output, FileHeader.signature);
|
|
1115 header.put(output);
|
|
1116 }
|
|
1117
|
|
1118 auto cd_len = seeker.seek(0, IConduit.Seek.Anchor.Current) - cd_pos;
|
|
1119
|
|
1120 if( cd_len > uint.max )
|
|
1121 ZipException.cdtoolong;
|
|
1122
|
|
1123 {
|
|
1124 EndOfCDRecord eocdr;
|
|
1125 eocdr.data.central_directory_entries_on_this_disk =
|
|
1126 entries.length;
|
|
1127 eocdr.data.central_directory_entries_total = entries.length;
|
|
1128 eocdr.data.size_of_central_directory = cd_len;
|
|
1129 eocdr.data.offset_of_start_of_cd_from_starting_disk = cd_pos;
|
|
1130
|
|
1131 write(output, EndOfCDRecord.signature);
|
|
1132 eocdr.put(output);
|
|
1133 }
|
|
1134 }
|
|
1135
|
|
1136 void put_raw(ZipEntryInfo info, ZipEntry entry)
|
|
1137 {
|
|
1138 // Write out local file header
|
|
1139 LocalFileHeader.Data lhdata;
|
|
1140 auto chdata = entry.header.data;
|
|
1141 lhdata.extract_version = chdata.extract_version;
|
|
1142 lhdata.general_flags = chdata.general_flags;
|
|
1143 lhdata.compression_method = chdata.compression_method;
|
|
1144 lhdata.crc_32 = chdata.crc_32;
|
|
1145 lhdata.compressed_size = chdata.compressed_size;
|
|
1146 lhdata.uncompressed_size = chdata.uncompressed_size;
|
|
1147
|
|
1148 timeToDos(info.modified, lhdata.modification_file_time,
|
|
1149 lhdata.modification_file_date);
|
|
1150
|
|
1151 put_local_header(lhdata, info.name);
|
|
1152
|
|
1153 // Store comment
|
|
1154 entries[$-1].comment = info.comment;
|
|
1155
|
|
1156 // Output file contents
|
|
1157 {
|
|
1158 auto input = entry.open_raw;
|
|
1159 scope(exit) input.close;
|
|
1160 output.copy(input).flush();
|
|
1161 }
|
|
1162 }
|
|
1163
|
|
1164 void put_compressed(ZipEntryInfo info, InputStream source)
|
|
1165 {
|
|
1166 debug(Zip) Stderr.formatln("ZipBlockWriter.put_compressed()");
|
|
1167
|
|
1168 // Write out partial local file header
|
|
1169 auto header_pos = seeker.seek(0, IConduit.Seek.Anchor.Current);
|
|
1170 debug(Zip) Stderr.formatln(" . header for {} at {}", info.name, header_pos);
|
|
1171 put_local_header(info, _method);
|
|
1172
|
|
1173 // Store comment
|
|
1174 entries[$-1].comment = info.comment;
|
|
1175
|
|
1176 uint crc;
|
|
1177 uint compressed_size;
|
|
1178 uint uncompressed_size;
|
|
1179
|
|
1180 // Output file contents
|
|
1181 {
|
|
1182 // Input/output chains
|
|
1183 InputStream in_chain = source;
|
|
1184 OutputStream out_chain = new WrapSeekOutputStream(output);
|
|
1185
|
|
1186 // Count number of bytes coming in from the source file
|
|
1187 scope in_counter = new CounterInput(in_chain);
|
|
1188 in_chain = in_counter;
|
|
1189 scope(success) uncompressed_size = in_counter.count;
|
|
1190
|
|
1191 // Count the number of bytes going out to the archive
|
|
1192 scope out_counter = new CounterOutput(out_chain);
|
|
1193 out_chain = out_counter;
|
|
1194 scope(success) compressed_size = out_counter.count;
|
|
1195
|
|
1196 // Add crc
|
|
1197 scope crc_d = new Crc32(/*CRC_MAGIC*/);
|
|
1198 scope crc_s = new DigestInput(in_chain, crc_d);
|
|
1199 in_chain = crc_s;
|
|
1200 scope(success)
|
|
1201 {
|
|
1202 debug(Zip) Stderr.formatln(" . Success: storing CRC.");
|
|
1203 crc = crc_d.crc32Digest;
|
|
1204 }
|
|
1205
|
|
1206 // Add compression
|
|
1207 ZlibOutput compress;
|
|
1208 scope(exit) if( compress !is null ) delete compress;
|
|
1209
|
|
1210 switch( _method )
|
|
1211 {
|
|
1212 case Method.Store:
|
|
1213 break;
|
|
1214
|
|
1215 case Method.Deflate:
|
|
1216 compress = new ZlibOutput(out_chain,
|
|
1217 ZlibOutput.Level.init, -15);
|
|
1218 out_chain = compress;
|
|
1219 break;
|
|
1220 }
|
|
1221
|
|
1222 // All done.
|
|
1223 scope(exit) in_chain.close();
|
|
1224 scope(success) in_chain.clear();
|
|
1225 scope(exit) out_chain.close();
|
|
1226
|
|
1227 out_chain.copy(in_chain).flush;
|
|
1228
|
|
1229 debug(Zip) if( compress !is null )
|
|
1230 {
|
|
1231 Stderr.formatln(" . compressed to {} bytes", compress.written);
|
|
1232 }
|
|
1233
|
|
1234 debug(Zip) Stderr.formatln(" . wrote {} bytes", out_counter.count);
|
|
1235 debug(Zip) Stderr.formatln(" . contents written");
|
|
1236 }
|
|
1237
|
|
1238 debug(Zip) Stderr.formatln(" . CRC for \"{}\": 0x{:x8}", info.name, crc);
|
|
1239
|
|
1240 // Rewind, and patch the header
|
|
1241 auto final_pos = seeker.seek(0, IConduit.Seek.Anchor.Current);
|
|
1242 seeker.seek(header_pos);
|
|
1243 patch_local_header(crc, compressed_size, uncompressed_size);
|
|
1244
|
|
1245 // Seek back to the end of the file, and we're done!
|
|
1246 seeker.seek(final_pos);
|
|
1247 }
|
|
1248
|
|
1249 /*
|
|
1250 * Patches the local file header starting at the current output location
|
|
1251 * with updated crc and size information. Also updates the current last
|
|
1252 * Entry.
|
|
1253 */
|
|
1254 void patch_local_header(uint crc_32, uint compressed_size,
|
|
1255 uint uncompressed_size)
|
|
1256 {
|
|
1257 /* BUG: For some reason, this code won't compile. No idea why... if
|
|
1258 * you instantiate LFHD, it says that there is no "offsetof" property.
|
|
1259 */
|
|
1260 /+
|
|
1261 alias LocalFileHeaderData LFHD;
|
|
1262 static assert( LFHD.compressed_size.offsetof
|
|
1263 == LFHD.crc_32.offsetof + 4 );
|
|
1264 static assert( LFHD.uncompressed_size.offsetof
|
|
1265 == LFHD.compressed_size.offsetof + 4 );
|
|
1266 +/
|
|
1267
|
|
1268 // Don't forget we have to seek past the signature, too
|
|
1269 // BUG: .offsetof is broken here
|
|
1270 /+seeker.seek(LFHD.crc_32.offsetof+4, IConduit.Seek.Anchor.Current);+/
|
|
1271 seeker.seek(10+4, IConduit.Seek.Anchor.Current);
|
|
1272 write(output, crc_32);
|
|
1273 write(output, compressed_size);
|
|
1274 write(output, uncompressed_size);
|
|
1275
|
|
1276 with( entries[$-1] )
|
|
1277 {
|
|
1278 data.crc_32 = crc_32;
|
|
1279 data.compressed_size = compressed_size;
|
|
1280 data.uncompressed_size = uncompressed_size;
|
|
1281 }
|
|
1282 }
|
|
1283
|
|
1284 /*
|
|
1285 * Generates and outputs a local file header from the given info block and
|
|
1286 * compression method. Note that the crc_32, compressed_size and
|
|
1287 * uncompressed_size header fields will be set to zero, and must be
|
|
1288 * patched.
|
|
1289 */
|
|
1290 void put_local_header(ZipEntryInfo info, Method method)
|
|
1291 {
|
|
1292 LocalFileHeader.Data data;
|
|
1293
|
|
1294 data.compression_method = fromMethod(method);
|
|
1295 timeToDos(info.modified, data.modification_file_time,
|
|
1296 data.modification_file_date);
|
|
1297
|
|
1298 put_local_header(data, info.name);
|
|
1299 }
|
|
1300
|
|
1301 /*
|
|
1302 * Writes the given local file header data and filename out to the output
|
|
1303 * stream. It also appends a new Entry with the data and filename.
|
|
1304 */
|
|
1305 void put_local_header(LocalFileHeaderData data,
|
|
1306 char[] file_name)
|
|
1307 {
|
|
1308 // Compute Zip version
|
|
1309 if( data.extract_version == data.extract_version.max )
|
|
1310 {
|
|
1311 ushort zipver = 10;
|
|
1312 void minver(ushort v) { zipver = v>zipver ? v : zipver; }
|
|
1313
|
|
1314 {
|
|
1315 // Compression method
|
|
1316 switch( data.compression_method )
|
|
1317 {
|
|
1318 case 0: minver(10); break;
|
|
1319 case 8: minver(20); break;
|
|
1320 }
|
|
1321
|
|
1322 // File is a folder
|
|
1323 if( file_name.length > 0 && file_name[$-1] == '/'
|
|
1324 || file_name[$-1] == '\\' )
|
|
1325 // Is a directory, not a real file
|
|
1326 minver(20);
|
|
1327 }
|
|
1328 data.extract_version = zipver;
|
|
1329 }
|
|
1330
|
|
1331 /+// Encode filename
|
|
1332 auto file_name_437 = utf8_to_cp437(file_name);
|
|
1333 if( file_name_437 is null )
|
|
1334 ZipException.fnencode;+/
|
|
1335
|
|
1336 /+// Set up file name length
|
|
1337 if( file_name_437.length > ushort.max )
|
|
1338 ZipException.fntoolong;
|
|
1339
|
|
1340 data.file_name_length = file_name_437.length;+/
|
|
1341
|
|
1342 LocalFileHeader header;
|
|
1343 header.data = data;
|
|
1344 header.file_name = file_name;
|
|
1345
|
|
1346 // Write out the header and the filename
|
|
1347 auto header_pos = seeker.seek(0, IConduit.Seek.Anchor.Current);
|
|
1348
|
|
1349 write(output, LocalFileHeader.signature);
|
|
1350 header.put(output);
|
|
1351
|
|
1352 // Save the header
|
|
1353 Entry entry;
|
|
1354 entry.data.fromLocal(header.data);
|
|
1355 entry.filename = file_name;
|
|
1356 entry.header_position = header_pos;
|
|
1357 entry.data.relative_offset_of_local_header = header_pos;
|
|
1358 entries ~= entry;
|
|
1359 }
|
|
1360 }
|
|
1361
|
|
1362 //////////////////////////////////////////////////////////////////////////////
|
|
1363 //////////////////////////////////////////////////////////////////////////////
|
|
1364 //
|
|
1365 // ZipEntry
|
|
1366
|
|
1367 /**
|
|
1368 * This class is used to represent a single entry in an archive.
|
|
1369 * Specifically, it combines meta-data about the file (see the info field)
|
|
1370 * along with the two basic operations on an entry: open and verify.
|
|
1371 */
|
|
1372 class ZipEntry
|
|
1373 {
|
|
1374 /**
|
|
1375 * Header information on the file. See the ZipEntryInfo structure for
|
|
1376 * more information.
|
|
1377 */
|
|
1378 ZipEntryInfo info;
|
|
1379
|
|
1380 /**
|
|
1381 * Size (in bytes) of the file's uncompressed contents.
|
|
1382 */
|
|
1383 uint size()
|
|
1384 {
|
|
1385 return header.data.uncompressed_size;;
|
|
1386 }
|
|
1387
|
|
1388 /**
|
|
1389 * Opens a stream for reading from the file. The contents of this stream
|
|
1390 * represent the decompressed contents of the file stored in the archive.
|
|
1391 *
|
|
1392 * You should not assume that the returned stream is seekable.
|
|
1393 *
|
|
1394 * Note that the returned stream may be safely closed without affecting
|
|
1395 * the underlying archive stream.
|
|
1396 *
|
|
1397 * If the file has not yet been verified, then the stream will be checked
|
|
1398 * as you read from it. When the stream is either exhausted or closed,
|
|
1399 * then the integrity of the file's data will be checked. This means that
|
|
1400 * if the file is corrupt, an exception will be thrown only after you have
|
|
1401 * finished reading from the stream. If you wish to make sure the data is
|
|
1402 * valid before you read from the file, call the verify method.
|
|
1403 */
|
|
1404 InputStream open()
|
|
1405 {
|
|
1406 // If we haven't verified yet, wrap the stream in the appropriate
|
|
1407 // decorators.
|
|
1408 if( !verified )
|
|
1409 return new ZipEntryVerifier(this, open_dg(header, false));
|
|
1410
|
|
1411 else
|
|
1412 return open_dg(header, false);
|
|
1413 }
|
|
1414
|
|
1415 /**
|
|
1416 * Verifies the contents of this file by computing the CRC32 checksum,
|
|
1417 * and comparing it against the stored one. Throws an exception if the
|
|
1418 * checksums do not match.
|
|
1419 *
|
|
1420 * Not valid on streamed Zip archives.
|
|
1421 */
|
|
1422 void verify()
|
|
1423 {
|
|
1424 // If we haven't verified the contents yet, just read everything in
|
|
1425 // to trigger it.
|
|
1426 scope s = open;
|
|
1427 auto buffer = new ubyte[s.conduit.bufferSize];
|
|
1428 while( s.read(buffer) != s.Eof )
|
|
1429 {/*Do nothing*/}
|
|
1430 s.close;
|
|
1431 }
|
|
1432
|
|
1433 /**
|
|
1434 * Creates a new, independent copy of this instance.
|
|
1435 */
|
|
1436 ZipEntry dup()
|
|
1437 {
|
|
1438 return new ZipEntry(header, open_dg);
|
|
1439 }
|
|
1440
|
|
1441 private:
|
|
1442 /*
|
|
1443 * Callback used to open the file.
|
|
1444 */
|
|
1445 alias InputStream delegate(FileHeader, bool raw) open_dg_t;
|
|
1446 open_dg_t open_dg;
|
|
1447
|
|
1448 /*
|
|
1449 * Raw ZIP header.
|
|
1450 */
|
|
1451 FileHeader header;
|
|
1452
|
|
1453 /*
|
|
1454 * The flag used to keep track of whether the file's contents have been
|
|
1455 * verified.
|
|
1456 */
|
|
1457 bool verified = false;
|
|
1458
|
|
1459 /*
|
|
1460 * Opens a stream that does not perform any decompression or
|
|
1461 * transformation of the file contents. This is used internally by
|
|
1462 * ZipWriter to perform fast zip to zip transfers without having to
|
|
1463 * decompress and then recompress the contents.
|
|
1464 *
|
|
1465 * Note that because zip stores CRCs for the *uncompressed* data, this
|
|
1466 * method currently does not do any verification.
|
|
1467 */
|
|
1468 InputStream open_raw()
|
|
1469 {
|
|
1470 return open_dg(header, true);
|
|
1471 }
|
|
1472
|
|
1473 /*
|
|
1474 * Creates a new ZipEntry from the FileHeader.
|
|
1475 */
|
|
1476 this(FileHeader header, open_dg_t open_dg)
|
|
1477 {
|
|
1478 this.reset(header, open_dg);
|
|
1479 }
|
|
1480
|
|
1481 /*
|
|
1482 * Resets the current instance with new values.
|
|
1483 */
|
|
1484 ZipEntry reset(FileHeader header, open_dg_t open_dg)
|
|
1485 {
|
|
1486 this.header = header;
|
|
1487 this.open_dg = open_dg;
|
|
1488 with( info )
|
|
1489 {
|
|
1490 name = header.file_name.dup;
|
|
1491 dosToTime(header.data.modification_file_time,
|
|
1492 header.data.modification_file_date,
|
|
1493 modified);
|
|
1494 comment = header.file_comment.dup;
|
|
1495 }
|
|
1496
|
|
1497 this.verified = false;
|
|
1498
|
|
1499 return this;
|
|
1500 }
|
|
1501 }
|
|
1502
|
|
1503 /**
|
|
1504 * This structure contains various pieces of meta-data on a file. The
|
|
1505 * contents of this structure may be safely mutated.
|
|
1506 *
|
|
1507 * This structure is also used to specify meta-data about a file when adding
|
|
1508 * it to an archive.
|
|
1509 */
|
|
1510 struct ZipEntryInfo
|
|
1511 {
|
|
1512 /// Full path and file name of this file.
|
|
1513 char[] name;
|
|
1514 /// Modification timestamp. If this is left uninitialised when passed to
|
|
1515 /// a ZipWriter, it will be reset to the current system time.
|
|
1516 Time modified = Time.min;
|
|
1517 /// Comment on the file.
|
|
1518 char[] comment;
|
|
1519 }
|
|
1520
|
|
1521 //////////////////////////////////////////////////////////////////////////////
|
|
1522 //////////////////////////////////////////////////////////////////////////////
|
|
1523 //////////////////////////////////////////////////////////////////////////////
|
|
1524 //
|
|
1525 // Exceptions
|
|
1526 //
|
|
1527
|
|
1528 import tango.core.Exception : TracedException;
|
|
1529
|
|
1530 /**
|
|
1531 * This is the base class from which all exceptions generated by this module
|
|
1532 * derive from.
|
|
1533 */
|
|
1534 class ZipException : TracedException
|
|
1535 {
|
|
1536 this(char[] msg) { super(msg); }
|
|
1537
|
|
1538 private:
|
|
1539 alias typeof(this) thisT;
|
|
1540 static void opCall(char[] msg) { throw new ZipException(msg); }
|
|
1541
|
|
1542 static void badsig()
|
|
1543 {
|
|
1544 thisT("corrupt signature or unexpected section found");
|
|
1545 }
|
|
1546
|
|
1547 static void badsig(char[] type)
|
|
1548 {
|
|
1549 thisT("corrupt "~type~" signature or unexpected section found");
|
|
1550 }
|
|
1551
|
|
1552 static void incons(char[] name)
|
|
1553 {
|
|
1554 thisT("inconsistent headers for file \""~name~"\"; "
|
|
1555 "archive is likely corrupted");
|
|
1556 }
|
|
1557
|
|
1558 static void missingdir()
|
|
1559 {
|
|
1560 thisT("could not locate central archive directory; "
|
|
1561 "file is corrupt or possibly not a Zip archive");
|
|
1562 }
|
|
1563
|
|
1564 static void toomanyentries()
|
|
1565 {
|
|
1566 thisT("too many archive entries");
|
|
1567 }
|
|
1568
|
|
1569 static void toolong()
|
|
1570 {
|
|
1571 thisT("archive is too long; limited to 4GB total");
|
|
1572 }
|
|
1573
|
|
1574 static void cdtoolong()
|
|
1575 {
|
|
1576 thisT("central directory is too long; limited to 4GB total");
|
|
1577 }
|
|
1578
|
|
1579 static void fntoolong()
|
|
1580 {
|
|
1581 thisT("file name too long; limited to 65,535 characters");
|
|
1582 }
|
|
1583
|
|
1584 static void eftoolong()
|
|
1585 {
|
|
1586 thisT("extra field too long; limited to 65,535 characters");
|
|
1587 }
|
|
1588
|
|
1589 static void cotoolong()
|
|
1590 {
|
|
1591 thisT("extra field too long; limited to 65,535 characters");
|
|
1592 }
|
|
1593
|
|
1594 static void fnencode()
|
|
1595 {
|
|
1596 thisT("could not encode filename into codepage 437");
|
|
1597 }
|
|
1598
|
|
1599 static void coencode()
|
|
1600 {
|
|
1601 thisT("could not encode comment into codepage 437");
|
|
1602 }
|
|
1603
|
|
1604 static void tooold()
|
|
1605 {
|
|
1606 thisT("cannot represent dates before January 1, 1980");
|
|
1607 }
|
|
1608 }
|
|
1609
|
|
1610 /**
|
|
1611 * This exception is thrown if a ZipReader detects that a file's contents do
|
|
1612 * not match the stored checksum.
|
|
1613 */
|
|
1614 class ZipChecksumException : ZipException
|
|
1615 {
|
|
1616 this(char[] name)
|
|
1617 {
|
|
1618 super("checksum failed on zip entry \""~name~"\"");
|
|
1619 }
|
|
1620
|
|
1621 private:
|
|
1622 static void opCall(char[] name) { throw new ZipChecksumException(name); }
|
|
1623 }
|
|
1624
|
|
1625 /**
|
|
1626 * This exception is thrown if you call get reader method when there are no
|
|
1627 * more files in the archive.
|
|
1628 */
|
|
1629 class ZipExhaustedException : ZipException
|
|
1630 {
|
|
1631 this() { super("no more entries in archive"); }
|
|
1632
|
|
1633 private:
|
|
1634 static void opCall() { throw new ZipExhaustedException; }
|
|
1635 }
|
|
1636
|
|
1637 /**
|
|
1638 * This exception is thrown if you attempt to read an archive that uses
|
|
1639 * features not supported by the reader.
|
|
1640 */
|
|
1641 class ZipNotSupportedException : ZipException
|
|
1642 {
|
|
1643 this(char[] msg) { super(msg); }
|
|
1644
|
|
1645 private:
|
|
1646 alias ZipNotSupportedException thisT;
|
|
1647
|
|
1648 static void opCall(char[] msg)
|
|
1649 {
|
|
1650 throw new thisT(msg ~ " not supported");
|
|
1651 }
|
|
1652
|
|
1653 static void spanned()
|
|
1654 {
|
|
1655 thisT("split and multi-disk archives");
|
|
1656 }
|
|
1657
|
|
1658 static void zipver(ushort ver)
|
|
1659 {
|
|
1660 throw new thisT("zip format version "
|
|
1661 ~Integer.toString(ver / 10)
|
|
1662 ~"."
|
|
1663 ~Integer.toString(ver % 10)
|
|
1664 ~" not supported; maximum of version "
|
|
1665 ~Integer.toString(MAX_EXTRACT_VERSION / 10)
|
|
1666 ~"."
|
|
1667 ~Integer.toString(MAX_EXTRACT_VERSION % 10)
|
|
1668 ~" supported.");
|
|
1669 }
|
|
1670
|
|
1671 static void flags()
|
|
1672 {
|
|
1673 throw new thisT("unknown or unsupported file flags enabled");
|
|
1674 }
|
|
1675
|
|
1676 static void method(ushort m)
|
|
1677 {
|
|
1678 // Cheat here and work out what the method *actually* is
|
|
1679 char[] ms;
|
|
1680 switch( m )
|
|
1681 {
|
|
1682 case 0:
|
|
1683 case 8: assert(false); // supported
|
|
1684
|
|
1685 case 1: ms = "Shrink"; break;
|
|
1686 case 2: ms = "Reduce (factor 1)"; break;
|
|
1687 case 3: ms = "Reduce (factor 2)"; break;
|
|
1688 case 4: ms = "Reduce (factor 3)"; break;
|
|
1689 case 5: ms = "Reduce (factor 4)"; break;
|
|
1690 case 6: ms = "Implode"; break;
|
|
1691
|
|
1692 case 9: ms = "Deflate64"; break;
|
|
1693 case 10: ms = "TERSE (old)"; break;
|
|
1694
|
|
1695 case 12: ms = "Bzip2"; break;
|
|
1696 case 14: ms = "LZMA"; break;
|
|
1697
|
|
1698 case 18: ms = "TERSE (new)"; break;
|
|
1699 case 19: ms = "LZ77"; break;
|
|
1700
|
|
1701 case 97: ms = "WavPack"; break;
|
|
1702 case 98: ms = "PPMd"; break;
|
|
1703
|
|
1704 default: ms = "unknown";
|
|
1705 }
|
|
1706
|
|
1707 thisT(ms ~ " compression method");
|
|
1708 }
|
|
1709 }
|
|
1710
|
|
1711 //////////////////////////////////////////////////////////////////////////////
|
|
1712 //////////////////////////////////////////////////////////////////////////////
|
|
1713 //
|
|
1714 // Convenience methods
|
|
1715
|
|
1716 void createArchive(char[] archive, Method method, char[][] files...)
|
|
1717 {
|
|
1718 scope zw = new ZipBlockWriter(archive);
|
|
1719 zw.method = method;
|
|
1720
|
|
1721 foreach( file ; files )
|
|
1722 zw.putFile(ZipEntryInfo(file), file);
|
|
1723
|
|
1724 zw.finish;
|
|
1725 }
|
|
1726
|
|
1727 void createArchive(PathView archive, Method method, PathView[] files...)
|
|
1728 {
|
|
1729 scope zw = new ZipBlockWriter(archive);
|
|
1730 zw.method = method;
|
|
1731
|
|
1732 foreach( file ; files )
|
|
1733 zw.putFile(ZipEntryInfo(file.toString), file);
|
|
1734
|
|
1735 zw.finish;
|
|
1736 }
|
|
1737
|
|
1738 void extractArchive(char[] archive, char[] folder)
|
|
1739 {
|
|
1740 extractArchive(FilePath(archive), FilePath(folder));
|
|
1741 }
|
|
1742
|
|
1743 void extractArchive(PathView archive, PathView dest)
|
|
1744 {
|
|
1745 scope folder = FilePath(dest.toString);
|
|
1746 scope zr = new ZipBlockReader(archive);
|
|
1747
|
|
1748 foreach( entry ; zr )
|
|
1749 {
|
|
1750 // Skip directories
|
|
1751 if( entry.info.name[$-1] == '/' ) continue;
|
|
1752
|
|
1753 auto path = folder.dup.append(entry.info.name);
|
|
1754 scope fout = new FileConduit(path, FileConduit.WriteCreate);
|
|
1755 fout.output.copy(entry.open);
|
|
1756 }
|
|
1757 }
|
|
1758
|
|
1759 //////////////////////////////////////////////////////////////////////////////
|
|
1760 //////////////////////////////////////////////////////////////////////////////
|
|
1761 //////////////////////////////////////////////////////////////////////////////
|
|
1762 //
|
|
1763 // Private implementation stuff
|
|
1764 //
|
|
1765
|
|
1766 private:
|
|
1767
|
|
1768 //////////////////////////////////////////////////////////////////////////////
|
|
1769 //////////////////////////////////////////////////////////////////////////////
|
|
1770 //
|
|
1771 // Verification stuff
|
|
1772
|
|
1773 /*
|
|
1774 * This class wraps an input stream, and computes the CRC as it passes
|
|
1775 * through. On the event of either a close or EOF, it checks the CRC against
|
|
1776 * the one in the provided ZipEntry. If they don't match, it throws an
|
|
1777 * exception.
|
|
1778 */
|
|
1779
|
|
1780 class ZipEntryVerifier : InputStream
|
|
1781 {
|
|
1782 this(ZipEntry entry, InputStream source)
|
|
1783 in
|
|
1784 {
|
|
1785 assert( entry !is null );
|
|
1786 assert( source !is null );
|
|
1787 }
|
|
1788 body
|
|
1789 {
|
|
1790 this.entry = entry;
|
|
1791 this.digest = new Crc32;
|
|
1792 this.source = new DigestInput(source, digest);
|
|
1793 }
|
|
1794
|
|
1795 IConduit conduit()
|
|
1796 {
|
|
1797 return source.conduit;
|
|
1798 }
|
|
1799
|
|
1800 void close()
|
|
1801 {
|
|
1802 check;
|
|
1803
|
|
1804 this.source.close;
|
|
1805 this.entry = null;
|
|
1806 this.digest = null;
|
|
1807 this.source = null;
|
|
1808 }
|
|
1809
|
|
1810 uint read(void[] dst)
|
|
1811 {
|
|
1812 auto bytes = source.read(dst);
|
|
1813 if( bytes == IConduit.Eof )
|
|
1814 check;
|
|
1815 return bytes;
|
|
1816 }
|
|
1817
|
|
1818 InputStream clear()
|
|
1819 {
|
|
1820 this.source.clear;
|
|
1821 return this;
|
|
1822 }
|
|
1823
|
|
1824 private:
|
|
1825 Crc32 digest;
|
|
1826 InputStream source;
|
|
1827 ZipEntry entry;
|
|
1828
|
|
1829 void check()
|
|
1830 {
|
|
1831 if( digest is null ) return;
|
|
1832
|
|
1833 auto crc = digest.crc32Digest;
|
|
1834 delete digest;
|
|
1835
|
|
1836 if( crc != entry.header.data.crc_32 )
|
|
1837 ZipChecksumException(entry.info.name);
|
|
1838
|
|
1839 else
|
|
1840 entry.verified = true;
|
|
1841 }
|
|
1842 }
|
|
1843
|
|
1844 //////////////////////////////////////////////////////////////////////////////
|
|
1845 //////////////////////////////////////////////////////////////////////////////
|
|
1846 //
|
|
1847 // IO functions
|
|
1848
|
|
1849 /*
|
|
1850 * Really, seriously, read some bytes without having to go through a sodding
|
|
1851 * buffer.
|
|
1852 */
|
|
1853 void readExact(InputStream s, void[] dst)
|
|
1854 {
|
|
1855 //Stderr.formatln("readExact(s, [0..{}])", dst.length);
|
|
1856 while( dst.length > 0 )
|
|
1857 {
|
|
1858 auto octets = s.read(dst);
|
|
1859 //Stderr.formatln(" . octets = {}", octets);
|
|
1860 if( octets == -1 ) // Beware the dangers of MAGICAL THINKING
|
|
1861 throw new Exception("unexpected end of stream");
|
|
1862 dst = dst[octets..$];
|
|
1863 }
|
|
1864 }
|
|
1865
|
|
1866 /*
|
|
1867 * Really, seriously, write some bytes.
|
|
1868 */
|
|
1869 void writeExact(OutputStream s, void[] src)
|
|
1870 {
|
|
1871 while( src.length > 0 )
|
|
1872 {
|
|
1873 auto octets = s.write(src);
|
|
1874 if( octets == -1 )
|
|
1875 throw new Exception("unexpected end of stream");
|
|
1876 src = src[octets..$];
|
|
1877 }
|
|
1878 }
|
|
1879
|
|
1880 void write(T)(OutputStream s, T value)
|
|
1881 {
|
|
1882 version( BigEndian ) swap(value);
|
|
1883 writeExact(s, (&value)[0..1]);
|
|
1884 }
|
|
1885
|
|
1886 //////////////////////////////////////////////////////////////////////////////
|
|
1887 //////////////////////////////////////////////////////////////////////////////
|
|
1888 //
|
|
1889 // Endian garbage
|
|
1890
|
|
1891 void swapAll(T)(inout T data)
|
|
1892 {
|
|
1893 static if( is(typeof(T.record_fields)) )
|
|
1894 const fields = T.record_fields;
|
|
1895 else
|
|
1896 const fields = data.tupleof.length;
|
|
1897
|
|
1898 foreach( i,_ ; data.tupleof )
|
|
1899 {
|
|
1900 if( i == fields ) break;
|
|
1901 swap(data.tupleof[i]);
|
|
1902 }
|
|
1903 }
|
|
1904
|
|
1905 void swap(T)(inout T data)
|
|
1906 {
|
|
1907 static if( T.sizeof == 1 )
|
|
1908 {}
|
|
1909 else static if( T.sizeof == 2 )
|
|
1910 ByteSwap.swap16(&data, 2);
|
|
1911 else static if( T.sizeof == 4 )
|
|
1912 ByteSwap.swap32(&data, 4);
|
|
1913 else static if( T.sizeof == 8 )
|
|
1914 ByteSwap.swap64(&data, 8);
|
|
1915 else static if( T.sizeof == 10 )
|
|
1916 ByteSwap.swap80(&data, 10);
|
|
1917 else
|
|
1918 static assert(false, "Can't swap "~T.stringof~"s.");
|
|
1919 }
|
|
1920
|
|
1921 //////////////////////////////////////////////////////////////////////////////
|
|
1922 //////////////////////////////////////////////////////////////////////////////
|
|
1923 //
|
|
1924 // IBM Code Page 437 stuff
|
|
1925 //
|
|
1926
|
|
1927 const char[][] cp437_to_utf8_map_low = [
|
|
1928 "\u0000"[], "\u263a", "\u263b", "\u2665",
|
|
1929 "\u2666", "\u2663", "\u2660", "\u2022",
|
|
1930 "\u25d8", "\u25cb", "\u25d9", "\u2642",
|
|
1931 "\u2640", "\u266a", "\u266b", "\u263c",
|
|
1932
|
|
1933 "\u25b6", "\u25c0", "\u2195", "\u203c",
|
|
1934 "\u00b6", "\u00a7", "\u25ac", "\u21a8",
|
|
1935 "\u2191", "\u2193", "\u2192", "\u2190",
|
|
1936 "\u221f", "\u2194", "\u25b2", "\u25bc"
|
|
1937 ];
|
|
1938
|
|
1939 const char[][] cp437_to_utf8_map_high = [
|
|
1940 "\u00c7"[], "\u00fc", "\u00e9", "\u00e2",
|
|
1941 "\u00e4", "\u00e0", "\u00e5", "\u00e7",
|
|
1942 "\u00ea", "\u00eb", "\u00e8", "\u00ef",
|
|
1943 "\u00ee", "\u00ec", "\u00c4", "\u00c5",
|
|
1944
|
|
1945 "\u00c9", "\u00e6", "\u00c6", "\u00f4",
|
|
1946 "\u00f6", "\u00f2", "\u00fb", "\u00f9",
|
|
1947 "\u00ff", "\u00d6", "\u00dc", "\u00f8",
|
|
1948 "\u00a3", "\u00a5", "\u20a7", "\u0192",
|
|
1949
|
|
1950 "\u00e1", "\u00ed", "\u00f3", "\u00fa",
|
|
1951 "\u00f1", "\u00d1", "\u00aa", "\u00ba",
|
|
1952 "\u00bf", "\u2310", "\u00ac", "\u00bd",
|
|
1953 "\u00bc", "\u00a1", "\u00ab", "\u00bb",
|
|
1954
|
|
1955 "\u2591", "\u2592", "\u2593", "\u2502",
|
|
1956 "\u2524", "\u2561", "\u2562", "\u2556",
|
|
1957 "\u2555", "\u2563", "\u2551", "\u2557",
|
|
1958 "\u255d", "\u255c", "\u255b", "\u2510",
|
|
1959
|
|
1960 "\u2514", "\u2534", "\u252c", "\u251c",
|
|
1961 "\u2500", "\u253c", "\u255e", "\u255f",
|
|
1962 "\u255a", "\u2554", "\u2569", "\u2566",
|
|
1963 "\u2560", "\u2550", "\u256c", "\u2567",
|
|
1964
|
|
1965 "\u2568", "\u2564", "\u2565", "\u2559",
|
|
1966 "\u2558", "\u2552", "\u2553", "\u256b",
|
|
1967 "\u256a", "\u2518", "\u250c", "\u2588",
|
|
1968 "\u2584", "\u258c", "\u2590", "\u2580",
|
|
1969 "\u03b1", "\u00df", "\u0393", "\u03c0",
|
|
1970 "\u03a3", "\u03c3", "\u00b5", "\u03c4",
|
|
1971 "\u03a6", "\u0398", "\u03a9", "\u03b4",
|
|
1972 "\u221e", "\u03c6", "\u03b5", "\u2229",
|
|
1973
|
|
1974 "\u2261", "\u00b1", "\u2265", "\u2264",
|
|
1975 "\u2320", "\u2321", "\u00f7", "\u2248",
|
|
1976 "\u00b0", "\u2219", "\u00b7", "\u221a",
|
|
1977 "\u207f", "\u00b2", "\u25a0", "\u00a0"
|
|
1978 ];
|
|
1979
|
|
1980 char[] cp437_to_utf8(ubyte[] s)
|
|
1981 {
|
|
1982 foreach( i,c ; s )
|
|
1983 {
|
|
1984 if( (1 <= c && c <= 31) || c >= 127 )
|
|
1985 {
|
|
1986 /* Damn; we got a character not in ASCII. Since this is the first
|
|
1987 * non-ASCII character we found, copy everything up to this point
|
|
1988 * into the output verbatim. We'll allocate twice as much space
|
|
1989 * as there are remaining characters to ensure we don't need to do
|
|
1990 * any further allocations.
|
|
1991 */
|
|
1992 auto r = new char[i+2*(s.length-i)];
|
|
1993 r[0..i] = cast(char[]) s[0..i];
|
|
1994 size_t k=i; // current length
|
|
1995
|
|
1996 // We insert new characters at r[i+j+k]
|
|
1997
|
|
1998 foreach( d ; s[i..$] )
|
|
1999 {
|
|
2000 if( 32 <= d && d <= 126 || d == 0 )
|
|
2001 {
|
|
2002 r[k++] = d;
|
|
2003 }
|
|
2004 else if( 1 <= d && d <= 31 )
|
|
2005 {
|
|
2006 char[] repl = cp437_to_utf8_map_low[d];
|
|
2007 r[k..k+repl.length] = repl[];
|
|
2008 k += repl.length;
|
|
2009 }
|
|
2010 else if( d == 127 )
|
|
2011 {
|
|
2012 char[] repl = "\u2302";
|
|
2013 r[k..k+repl.length] = repl[];
|
|
2014 k += repl.length;
|
|
2015 }
|
|
2016 else if( d > 127 )
|
|
2017 {
|
|
2018 char[] repl = cp437_to_utf8_map_high[d-128];
|
|
2019 r[k..k+repl.length] = repl[];
|
|
2020 k += repl.length;
|
|
2021 }
|
|
2022 else
|
|
2023 assert(false);
|
|
2024 }
|
|
2025
|
|
2026 return r[0..k];
|
|
2027 }
|
|
2028 }
|
|
2029
|
|
2030 /* If we got here, then all the characters in s are also in ASCII, which
|
|
2031 * means it's also valid UTF-8; return the string unmodified.
|
|
2032 */
|
|
2033 return cast(char[]) s;
|
|
2034 }
|
|
2035
|
|
2036 debug( UnitTest )
|
|
2037 {
|
|
2038 unittest
|
|
2039 {
|
|
2040 char[] c(char[] s) { return cp437_to_utf8(cast(ubyte[]) s); }
|
|
2041
|
|
2042 auto s = c("Hi there \x01 old \x0c!");
|
|
2043 assert( s == "Hi there \u263a old \u2640!", "\""~s~"\"" );
|
|
2044 s = c("Marker \x7f and divide \xf6.");
|
|
2045 assert( s == "Marker \u2302 and divide \u00f7.", "\""~s~"\"" );
|
|
2046 }
|
|
2047 }
|
|
2048
|
|
2049 const char[dchar] utf8_to_cp437_map;
|
|
2050
|
|
2051 static this()
|
|
2052 {
|
|
2053 utf8_to_cp437_map = [
|
|
2054 '\u0000': '\x00', '\u263a': '\x01', '\u263b': '\x02', '\u2665': '\x03',
|
|
2055 '\u2666': '\x04', '\u2663': '\x05', '\u2660': '\x06', '\u2022': '\x07',
|
|
2056 '\u25d8': '\x08', '\u25cb': '\x09', '\u25d9': '\x0a', '\u2642': '\x0b',
|
|
2057 '\u2640': '\x0c', '\u266a': '\x0d', '\u266b': '\x0e', '\u263c': '\x0f',
|
|
2058
|
|
2059 '\u25b6': '\x10', '\u25c0': '\x11', '\u2195': '\x12', '\u203c': '\x13',
|
|
2060 '\u00b6': '\x14', '\u00a7': '\x15', '\u25ac': '\x16', '\u21a8': '\x17',
|
|
2061 '\u2191': '\x18', '\u2193': '\x19', '\u2192': '\x1a', '\u2190': '\x1b',
|
|
2062 '\u221f': '\x1c', '\u2194': '\x1d', '\u25b2': '\x1e', '\u25bc': '\x1f',
|
|
2063
|
|
2064 /*
|
|
2065 * Printable ASCII range (well, most of it) is handled specially.
|
|
2066 */
|
|
2067
|
|
2068 '\u00c7': '\x80', '\u00fc': '\x81', '\u00e9': '\x82', '\u00e2': '\x83',
|
|
2069 '\u00e4': '\x84', '\u00e0': '\x85', '\u00e5': '\x86', '\u00e7': '\x87',
|
|
2070 '\u00ea': '\x88', '\u00eb': '\x89', '\u00e8': '\x8a', '\u00ef': '\x8b',
|
|
2071 '\u00ee': '\x8c', '\u00ec': '\x8d', '\u00c4': '\x8e', '\u00c5': '\x8f',
|
|
2072
|
|
2073 '\u00c9': '\x90', '\u00e6': '\x91', '\u00c6': '\x92', '\u00f4': '\x93',
|
|
2074 '\u00f6': '\x94', '\u00f2': '\x95', '\u00fb': '\x96', '\u00f9': '\x97',
|
|
2075 '\u00ff': '\x98', '\u00d6': '\x99', '\u00dc': '\x9a', '\u00f8': '\x9b',
|
|
2076 '\u00a3': '\x9c', '\u00a5': '\x9d', '\u20a7': '\x9e', '\u0192': '\x9f',
|
|
2077
|
|
2078 '\u00e1': '\xa0', '\u00ed': '\xa1', '\u00f3': '\xa2', '\u00fa': '\xa3',
|
|
2079 '\u00f1': '\xa4', '\u00d1': '\xa5', '\u00aa': '\xa6', '\u00ba': '\xa7',
|
|
2080 '\u00bf': '\xa8', '\u2310': '\xa9', '\u00ac': '\xaa', '\u00bd': '\xab',
|
|
2081 '\u00bc': '\xac', '\u00a1': '\xad', '\u00ab': '\xae', '\u00bb': '\xaf',
|
|
2082
|
|
2083 '\u2591': '\xb0', '\u2592': '\xb1', '\u2593': '\xb2', '\u2502': '\xb3',
|
|
2084 '\u2524': '\xb4', '\u2561': '\xb5', '\u2562': '\xb6', '\u2556': '\xb7',
|
|
2085 '\u2555': '\xb8', '\u2563': '\xb9', '\u2551': '\xba', '\u2557': '\xbb',
|
|
2086 '\u255d': '\xbc', '\u255c': '\xbd', '\u255b': '\xbe', '\u2510': '\xbf',
|
|
2087
|
|
2088 '\u2514': '\xc0', '\u2534': '\xc1', '\u252c': '\xc2', '\u251c': '\xc3',
|
|
2089 '\u2500': '\xc4', '\u253c': '\xc5', '\u255e': '\xc6', '\u255f': '\xc7',
|
|
2090 '\u255a': '\xc8', '\u2554': '\xc9', '\u2569': '\xca', '\u2566': '\xcb',
|
|
2091 '\u2560': '\xcc', '\u2550': '\xcd', '\u256c': '\xce', '\u2567': '\xcf',
|
|
2092
|
|
2093 '\u2568': '\xd0', '\u2564': '\xd1', '\u2565': '\xd2', '\u2559': '\xd3',
|
|
2094 '\u2558': '\xd4', '\u2552': '\xd5', '\u2553': '\xd6', '\u256b': '\xd7',
|
|
2095 '\u256a': '\xd8', '\u2518': '\xd9', '\u250c': '\xda', '\u2588': '\xdb',
|
|
2096 '\u2584': '\xdc', '\u258c': '\xdd', '\u2590': '\xde', '\u2580': '\xdf',
|
|
2097
|
|
2098 '\u03b1': '\xe0', '\u00df': '\xe1', '\u0393': '\xe2', '\u03c0': '\xe3',
|
|
2099 '\u03a3': '\xe4', '\u03c3': '\xe5', '\u00b5': '\xe6', '\u03c4': '\xe7',
|
|
2100 '\u03a6': '\xe8', '\u0398': '\xe9', '\u03a9': '\xea', '\u03b4': '\xeb',
|
|
2101 '\u221e': '\xec', '\u03c6': '\xed', '\u03b5': '\xee', '\u2229': '\xef',
|
|
2102
|
|
2103 '\u2261': '\xf0', '\u00b1': '\xf1', '\u2265': '\xf2', '\u2264': '\xf3',
|
|
2104 '\u2320': '\xf4', '\u2321': '\xf5', '\u00f7': '\xf6', '\u2248': '\xf7',
|
|
2105 '\u00b0': '\xf8', '\u2219': '\xf9', '\u00b7': '\xfa', '\u221a': '\xfb',
|
|
2106 '\u207f': '\xfc', '\u00b2': '\xfd', '\u25a0': '\xfe', '\u00a0': '\xff'
|
|
2107 ];
|
|
2108 }
|
|
2109
|
|
2110 ubyte[] utf8_to_cp437(char[] s)
|
|
2111 {
|
|
2112 foreach( i,dchar c ; s )
|
|
2113 {
|
|
2114 if( !((32 <= c && c <= 126) || c == 0) )
|
|
2115 {
|
|
2116 /* We got a character not in CP 437: we need to create a buffer to
|
|
2117 * hold the new string. Since UTF-8 is *always* larger than CP
|
|
2118 * 437, we need, at most, an array of the same number of elements.
|
|
2119 */
|
|
2120 auto r = new ubyte[s.length];
|
|
2121 r[0..i] = cast(ubyte[]) s[0..i];
|
|
2122 size_t k=i;
|
|
2123
|
|
2124 foreach( dchar d ; s[i..$] )
|
|
2125 {
|
|
2126 if( 32 <= d && d <= 126 || d == 0 )
|
|
2127 r[k++] = d;
|
|
2128
|
|
2129 else if( d == '\u2302' )
|
|
2130 r[k++] = '\x7f';
|
|
2131
|
|
2132 else if( auto e_ptr = d in utf8_to_cp437_map )
|
|
2133 r[k++] = *e_ptr;
|
|
2134
|
|
2135 else
|
|
2136 {
|
|
2137 throw new Exception("cannot encode character \""
|
|
2138 ~ Integer.toString(cast(uint)d)
|
|
2139 ~ "\" in codepage 437.");
|
|
2140 }
|
|
2141 }
|
|
2142
|
|
2143 return r[0..k];
|
|
2144 }
|
|
2145 }
|
|
2146
|
|
2147 // If we got here, then the entire string is printable ASCII, which just
|
|
2148 // happens to *also* be valid CP 437! Huzzah!
|
|
2149 return cast(ubyte[]) s;
|
|
2150 }
|
|
2151
|
|
2152 debug( UnitTest )
|
|
2153 {
|
|
2154 unittest
|
|
2155 {
|
|
2156 alias cp437_to_utf8 x;
|
|
2157 alias utf8_to_cp437 y;
|
|
2158
|
|
2159 ubyte[256] s;
|
|
2160 foreach( i,ref c ; s )
|
|
2161 c = i;
|
|
2162
|
|
2163 auto a = x(s);
|
|
2164 auto b = y(a);
|
|
2165 if(!( b == s ))
|
|
2166 {
|
|
2167 // Display list of characters that failed to convert as expected,
|
|
2168 // and what value we got.
|
|
2169 auto hex = "0123456789abcdef";
|
|
2170 auto msg = "".dup;
|
|
2171 foreach( i,ch ; b )
|
|
2172 {
|
|
2173 if( ch != i )
|
|
2174 {
|
|
2175 msg ~= hex[i>>4];
|
|
2176 msg ~= hex[i&15];
|
|
2177 msg ~= " (";
|
|
2178 msg ~= hex[ch>>4];
|
|
2179 msg ~= hex[ch&15];
|
|
2180 msg ~= "), ";
|
|
2181 }
|
|
2182 }
|
|
2183 msg ~= "failed.";
|
|
2184
|
|
2185 assert( false, msg );
|
|
2186 }
|
|
2187 }
|
|
2188 }
|
|
2189
|
|
2190 /*
|
|
2191 * This is here to simplify the code elsewhere.
|
|
2192 */
|
|
2193 char[] utf8_to_utf8(ubyte[] s) { return cast(char[]) s; }
|
|
2194 ubyte[] utf8_to_utf8(char[] s) { return cast(ubyte[]) s; }
|
|
2195
|
|
2196 //////////////////////////////////////////////////////////////////////////////
|
|
2197 //////////////////////////////////////////////////////////////////////////////
|
|
2198 //
|
|
2199 // Date/time stuff
|
|
2200
|
|
2201 void dosToTime(ushort dostime, ushort dosdate, out Time time)
|
|
2202 {
|
|
2203 uint sec, min, hour, day, mon, year;
|
|
2204 sec = (dostime & 0b00000_000000_11111) * 2;
|
|
2205 min = (dostime & 0b00000_111111_00000) >> 5;
|
|
2206 hour= (dostime & 0b11111_000000_00000) >> 11;
|
|
2207 day = (dosdate & 0b0000000_0000_11111);
|
|
2208 mon = (dosdate & 0b0000000_1111_00000) >> 5;
|
|
2209 year=((dosdate & 0b1111111_0000_00000) >> 9) + 1980;
|
|
2210
|
|
2211 // This code sucks.
|
|
2212 scope cal = new Gregorian;
|
|
2213 time = Time.epoch
|
|
2214 + TimeSpan.days(cal.getDaysInYear(year, 0))
|
|
2215 + TimeSpan.days(cal.getDaysInMonth(year, mon, 0))
|
|
2216 + TimeSpan.days(day)
|
|
2217 + TimeSpan.hours(hour)
|
|
2218 + TimeSpan.minutes(min)
|
|
2219 + TimeSpan.seconds(sec);
|
|
2220 }
|
|
2221
|
|
2222 void timeToDos(Time time, out ushort dostime, out ushort dosdate)
|
|
2223 {
|
|
2224 // Treat Time.min specially
|
|
2225 if( time == Time.min )
|
|
2226 time = WallClock.now;
|
|
2227
|
|
2228 // *muttering angrily*
|
|
2229 scope cal = new Gregorian;
|
|
2230
|
|
2231 if( cal.getYear(time) < 1980 )
|
|
2232 ZipException.tooold;
|
|
2233
|
|
2234 auto span = time.span;
|
|
2235 dostime =
|
|
2236 (span.seconds / 2)
|
|
2237 | (span.minutes << 5)
|
|
2238 | (span.hours << 11);
|
|
2239
|
|
2240 dosdate =
|
|
2241 (cal.getDayOfMonth(time))
|
|
2242 | (cal.getMonth(time) << 5)
|
|
2243 |((cal.getYear(time) - 1980) << 9);
|
|
2244 }
|
|
2245
|
|
2246 // ************************************************************************** //
|
|
2247 // ************************************************************************** //
|
|
2248 // ************************************************************************** //
|
|
2249
|
|
2250 // Dependencies
|
|
2251 private:
|
|
2252
|
|
2253 import tango.io.Conduit : Conduit;
|
|
2254
|
|
2255 /*******************************************************************************
|
|
2256
|
|
2257 copyright: Copyright © 2007 Daniel Keep. All rights reserved.
|
|
2258
|
|
2259 license: BSD style: $(LICENSE)
|
|
2260
|
|
2261 version: Prerelease
|
|
2262
|
|
2263 author: Daniel Keep
|
|
2264
|
|
2265 *******************************************************************************/
|
|
2266
|
|
2267 //module tangox.io.stream.CounterStream;
|
|
2268
|
|
2269 //import tango.io.Conduit : Conduit;
|
|
2270 //import tango.io.model.IConduit : IConduit, InputStream, OutputStream;
|
|
2271
|
|
2272 /**
|
|
2273 * The counter stream classes are used to keep track of how many bytes flow
|
|
2274 * through a stream.
|
|
2275 *
|
|
2276 * To use them, simply wrap it around an existing stream. The number of bytes
|
|
2277 * that have flowed through the wrapped stream may be accessed using the
|
|
2278 * count member.
|
|
2279 */
|
|
2280 class CounterInput : InputStream
|
|
2281 {
|
|
2282 ///
|
|
2283 this(InputStream input)
|
|
2284 in
|
|
2285 {
|
|
2286 assert( input !is null );
|
|
2287 }
|
|
2288 body
|
|
2289 {
|
|
2290 this.input = input;
|
|
2291 }
|
|
2292
|
|
2293 override IConduit conduit()
|
|
2294 {
|
|
2295 return input.conduit;
|
|
2296 }
|
|
2297
|
|
2298 override void close()
|
|
2299 {
|
|
2300 input.close();
|
|
2301 input = null;
|
|
2302 }
|
|
2303
|
|
2304 override uint read(void[] dst)
|
|
2305 {
|
|
2306 auto read = input.read(dst);
|
|
2307 if( read != IConduit.Eof )
|
|
2308 _count += read;
|
|
2309 return read;
|
|
2310 }
|
|
2311
|
|
2312 override InputStream clear()
|
|
2313 {
|
|
2314 input.clear();
|
|
2315 return this;
|
|
2316 }
|
|
2317
|
|
2318 ///
|
|
2319 long count() { return _count; }
|
|
2320
|
|
2321 private:
|
|
2322 InputStream input;
|
|
2323 long _count;
|
|
2324 }
|
|
2325
|
|
2326 /// ditto
|
|
2327 class CounterOutput : OutputStream
|
|
2328 {
|
|
2329 ///
|
|
2330 this(OutputStream output)
|
|
2331 in
|
|
2332 {
|
|
2333 assert( output !is null );
|
|
2334 }
|
|
2335 body
|
|
2336 {
|
|
2337 this.output = output;
|
|
2338 }
|
|
2339
|
|
2340 override IConduit conduit()
|
|
2341 {
|
|
2342 return output.conduit;
|
|
2343 }
|
|
2344
|
|
2345 override void close()
|
|
2346 {
|
|
2347 output.close();
|
|
2348 output = null;
|
|
2349 }
|
|
2350
|
|
2351 override uint write(void[] dst)
|
|
2352 {
|
|
2353 auto wrote = output.write(dst);
|
|
2354 if( wrote != IConduit.Eof )
|
|
2355 _count += wrote;
|
|
2356 return wrote;
|
|
2357 }
|
|
2358
|
|
2359 override OutputStream copy(InputStream src)
|
|
2360 {
|
|
2361 return Conduit.transfer(src, this);
|
|
2362 }
|
|
2363
|
|
2364 override OutputStream flush()
|
|
2365 {
|
|
2366 output.flush();
|
|
2367 return this;
|
|
2368 }
|
|
2369
|
|
2370 ///
|
|
2371 long count() { return _count; }
|
|
2372
|
|
2373 private:
|
|
2374 OutputStream output;
|
|
2375 long _count;
|
|
2376 }
|
|
2377
|
|
2378 /*******************************************************************************
|
|
2379
|
|
2380 copyright: Copyright © 2007 Daniel Keep. All rights reserved.
|
|
2381
|
|
2382 license: BSD style: $(LICENSE)
|
|
2383
|
|
2384 version: Prerelease
|
|
2385
|
|
2386 author: Daniel Keep
|
|
2387
|
|
2388 *******************************************************************************/
|
|
2389
|
|
2390 //module tangox.io.stream.SliceStream;
|
|
2391
|
|
2392 //import tango.io.Conduit : Conduit;
|
|
2393 //import tango.io.model.IConduit : IConduit, InputStream, OutputStream;
|
|
2394
|
|
2395 /**
|
|
2396 * This stream can be used to provide stream-based access to a subset of
|
|
2397 * another stream. It is akin to slicing an array.
|
|
2398 *
|
|
2399 * This stream fully supports seeking, and as such requires that the
|
|
2400 * underlying stream also support seeking.
|
|
2401 */
|
|
2402 class SliceSeekInputStream : InputStream, IConduit.Seek
|
|
2403 {
|
|
2404 alias IConduit.Seek.Anchor Anchor;
|
|
2405
|
|
2406 /**
|
|
2407 * Create a new slice stream from the given source, covering the content
|
|
2408 * starting at position begin, for length bytes.
|
|
2409 */
|
|
2410 this(InputStream source, long begin, long length)
|
|
2411 in
|
|
2412 {
|
|
2413 assert( source !is null );
|
|
2414 assert( (cast(IConduit.Seek) source) !is null );
|
|
2415 assert( begin >= 0 );
|
|
2416 assert( length >= 0 );
|
|
2417 }
|
|
2418 body
|
|
2419 {
|
|
2420 this.source = source;
|
|
2421 this.seeker = cast(IConduit.Seek) source;
|
|
2422 this.begin = begin;
|
|
2423 this.length = length;
|
|
2424 }
|
|
2425
|
|
2426 override IConduit conduit()
|
|
2427 {
|
|
2428 return source.conduit;
|
|
2429 }
|
|
2430
|
|
2431 override void close()
|
|
2432 {
|
|
2433 source = null;
|
|
2434 seeker = null;
|
|
2435 }
|
|
2436
|
|
2437 override uint read(void[] dst)
|
|
2438 {
|
|
2439 // If we're at the end of the slice, return eof
|
|
2440 if( _position >= length )
|
|
2441 return IConduit.Eof;
|
|
2442
|
|
2443 // Otherwise, make sure we don't try to read past the end of the slice
|
|
2444 if( _position+dst.length > length )
|
|
2445 dst.length = length-_position;
|
|
2446
|
|
2447 // Seek source stream to the appropriate location.
|
|
2448 if( seeker.seek(0, Anchor.Current) != begin+_position )
|
|
2449 seeker.seek(begin+_position, Anchor.Begin);
|
|
2450
|
|
2451 // Do the read
|
|
2452 auto read = source.read(dst);
|
|
2453 if( read == IConduit.Eof )
|
|
2454 // If we got an Eof, we'll consider that a bug for the moment.
|
|
2455 // TODO: proper exception
|
|
2456 throw new Exception("unexpected end-of-stream");
|
|
2457
|
|
2458 _position += read;
|
|
2459 return read;
|
|
2460 }
|
|
2461
|
|
2462 override InputStream clear()
|
|
2463 {
|
|
2464 source.clear();
|
|
2465 return this;
|
|
2466 }
|
|
2467
|
|
2468 override long seek(long offset, Anchor anchor = cast(Anchor)0)
|
|
2469 {
|
|
2470 switch( anchor )
|
|
2471 {
|
|
2472 case Anchor.Begin:
|
|
2473 _position = offset;
|
|
2474 break;
|
|
2475
|
|
2476 case Anchor.Current:
|
|
2477 _position += offset;
|
|
2478 if( _position < 0 ) _position = 0;
|
|
2479 break;
|
|
2480
|
|
2481 case Anchor.End:
|
|
2482 _position = length+offset;
|
|
2483 if( _position < 0 ) _position = 0;
|
|
2484 break;
|
|
2485 }
|
|
2486
|
|
2487 return _position;
|
|
2488 }
|
|
2489
|
|
2490 private:
|
|
2491 InputStream source;
|
|
2492 IConduit.Seek seeker;
|
|
2493
|
|
2494 long _position, begin, length;
|
|
2495
|
|
2496 invariant
|
|
2497 {
|
|
2498 assert( cast(Object) source is cast(Object) seeker );
|
|
2499 assert( begin >= 0 );
|
|
2500 assert( length >= 0 );
|
|
2501 assert( _position >= 0 );
|
|
2502 }
|
|
2503 }
|
|
2504
|
|
2505 /**
|
|
2506 * This stream can be used to provide stream-based access to a subset of
|
|
2507 * another stream. It is akin to slicing an array.
|
|
2508 */
|
|
2509 class SliceInputStream : InputStream
|
|
2510 {
|
|
2511 /**
|
|
2512 * Create a new slice stream from the given source, covering the content
|
|
2513 * starting at the current seek position for length bytes.
|
|
2514 */
|
|
2515 this(InputStream source, long length)
|
|
2516 in
|
|
2517 {
|
|
2518 assert( source !is null );
|
|
2519 assert( length >= 0 );
|
|
2520 }
|
|
2521 body
|
|
2522 {
|
|
2523 this.source = source;
|
|
2524 this._length = length;
|
|
2525 }
|
|
2526
|
|
2527 override IConduit conduit()
|
|
2528 {
|
|
2529 return source.conduit;
|
|
2530 }
|
|
2531
|
|
2532 override void close()
|
|
2533 {
|
|
2534 source = null;
|
|
2535 }
|
|
2536
|
|
2537 override uint read(void[] dst)
|
|
2538 {
|
|
2539 // If we're at the end of the slice, return eof
|
|
2540 if( _length <= 0 )
|
|
2541 return IConduit.Eof;
|
|
2542
|
|
2543 // Otherwise, make sure we don't try to read past the end of the slice
|
|
2544 if( dst.length > _length )
|
|
2545 dst.length = _length;
|
|
2546
|
|
2547 // Do the read
|
|
2548 auto read = source.read(dst);
|
|
2549 if( read == IConduit.Eof )
|
|
2550 // If we got an Eof, we'll consider that a bug for the moment.
|
|
2551 // TODO: proper exception
|
|
2552 throw new Exception("unexpected end-of-stream");
|
|
2553
|
|
2554 _length -= read;
|
|
2555 return read;
|
|
2556 }
|
|
2557
|
|
2558 override InputStream clear()
|
|
2559 {
|
|
2560 source.clear();
|
|
2561 return this;
|
|
2562 }
|
|
2563
|
|
2564 private:
|
|
2565 InputStream source;
|
|
2566 long _length;
|
|
2567
|
|
2568 invariant
|
|
2569 {
|
|
2570 if( _length > 0 ) assert( source !is null );
|
|
2571 }
|
|
2572 }
|
|
2573
|
|
2574 /**
|
|
2575 * This stream can be used to provide stream-based access to a subset of
|
|
2576 * another stream. It is akin to slicing an array.
|
|
2577 *
|
|
2578 * This stream fully supports seeking, and as such requires that the
|
|
2579 * underlying stream also support seeking.
|
|
2580 */
|
|
2581 class SliceSeekOutputStream : OutputStream, IConduit.Seek
|
|
2582 {
|
|
2583 alias IConduit.Seek.Anchor Anchor;
|
|
2584
|
|
2585 /**
|
|
2586 * Create a new slice stream from the given source, covering the content
|
|
2587 * starting at position begin, for length bytes.
|
|
2588 */
|
|
2589 this(OutputStream source, long begin, long length)
|
|
2590 in
|
|
2591 {
|
|
2592 assert( (cast(IConduit.Seek) source) !is null );
|
|
2593 assert( begin >= 0 );
|
|
2594 assert( length >= 0 );
|
|
2595 }
|
|
2596 body
|
|
2597 {
|
|
2598 this.source = source;
|
|
2599 this.seeker = cast(IConduit.Seek) source;
|
|
2600 this.begin = begin;
|
|
2601 this.length = length;
|
|
2602 }
|
|
2603
|
|
2604 override IConduit conduit()
|
|
2605 {
|
|
2606 return source.conduit;
|
|
2607 }
|
|
2608
|
|
2609 override void close()
|
|
2610 {
|
|
2611 source = null;
|
|
2612 seeker = null;
|
|
2613 }
|
|
2614
|
|
2615 uint write(void[] src)
|
|
2616 {
|
|
2617 // If we're at the end of the slice, return eof
|
|
2618 if( _position >= length )
|
|
2619 return IConduit.Eof;
|
|
2620
|
|
2621 // Otherwise, make sure we don't try to write past the end of the
|
|
2622 // slice
|
|
2623 if( _position+src.length > length )
|
|
2624 src.length = length-_position;
|
|
2625
|
|
2626 // Seek source stream to the appropriate location.
|
|
2627 if( seeker.seek(0, Anchor.Current) != begin+_position )
|
|
2628 seeker.seek(begin+_position, Anchor.Begin);
|
|
2629
|
|
2630 // Do the write
|
|
2631 auto wrote = source.write(src);
|
|
2632 if( wrote == IConduit.Eof )
|
|
2633 // If we got an Eof, we'll consider that a bug for the moment.
|
|
2634 // TODO: proper exception
|
|
2635 throw new Exception("unexpected end-of-stream");
|
|
2636
|
|
2637 _position += wrote;
|
|
2638 return wrote;
|
|
2639 }
|
|
2640
|
|
2641 override OutputStream copy(InputStream src)
|
|
2642 {
|
|
2643 return Conduit.transfer(src, this);
|
|
2644 }
|
|
2645
|
|
2646 override OutputStream flush()
|
|
2647 {
|
|
2648 source.flush();
|
|
2649 return this;
|
|
2650 }
|
|
2651
|
|
2652 override long seek(long offset, Anchor anchor = cast(Anchor)0)
|
|
2653 {
|
|
2654 switch( anchor )
|
|
2655 {
|
|
2656 case Anchor.Begin:
|
|
2657 _position = offset;
|
|
2658 break;
|
|
2659
|
|
2660 case Anchor.Current:
|
|
2661 _position += offset;
|
|
2662 if( _position < 0 ) _position = 0;
|
|
2663 break;
|
|
2664
|
|
2665 case Anchor.End:
|
|
2666 _position = length+offset;
|
|
2667 if( _position < 0 ) _position = 0;
|
|
2668 break;
|
|
2669 }
|
|
2670
|
|
2671 return _position;
|
|
2672 }
|
|
2673
|
|
2674 private:
|
|
2675 OutputStream source;
|
|
2676 IConduit.Seek seeker;
|
|
2677
|
|
2678 long _position, begin, length;
|
|
2679
|
|
2680 invariant
|
|
2681 {
|
|
2682 assert( cast(Object) source is cast(Object) seeker );
|
|
2683 assert( begin >= 0 );
|
|
2684 assert( length >= 0 );
|
|
2685 assert( _position >= 0 );
|
|
2686 }
|
|
2687 }
|
|
2688
|
|
2689 /*******************************************************************************
|
|
2690
|
|
2691 copyright: Copyright © 2007 Daniel Keep. All rights reserved.
|
|
2692
|
|
2693 license: BSD style: $(LICENSE)
|
|
2694
|
|
2695 version: Prerelease
|
|
2696
|
|
2697 author: Daniel Keep
|
|
2698
|
|
2699 *******************************************************************************/
|
|
2700
|
|
2701 //module tangox.io.stream.WrapStream;
|
|
2702
|
|
2703 //import tango.io.Conduit : Conduit;
|
|
2704 //import tango.io.model.IConduit : IConduit, InputStream, OutputStream;
|
|
2705
|
|
2706 /**
|
|
2707 * This stream can be used to provide access to another stream.
|
|
2708 * Its distinguishing feature is that users cannot close the underlying
|
|
2709 * stream.
|
|
2710 *
|
|
2711 * This stream fully supports seeking, and as such requires that the
|
|
2712 * underlying stream also support seeking.
|
|
2713 */
|
|
2714 class WrapSeekInputStream : InputStream, IConduit.Seek
|
|
2715 {
|
|
2716 alias IConduit.Seek.Anchor Anchor;
|
|
2717
|
|
2718 /**
|
|
2719 * Create a new wrap stream from the given source.
|
|
2720 */
|
|
2721 this(InputStream source)
|
|
2722 in
|
|
2723 {
|
|
2724 assert( source !is null );
|
|
2725 assert( (cast(IConduit.Seek) source) !is null );
|
|
2726 }
|
|
2727 body
|
|
2728 {
|
|
2729 this.source = source;
|
|
2730 this.seeker = cast(IConduit.Seek) source;
|
|
2731 this._position = seeker.seek(0, Anchor.Current);
|
|
2732 }
|
|
2733
|
|
2734 /// ditto
|
|
2735 this(InputStream source, long position)
|
|
2736 in
|
|
2737 {
|
|
2738 assert( position >= 0 );
|
|
2739 }
|
|
2740 body
|
|
2741 {
|
|
2742 this(source);
|
|
2743 this._position = position;
|
|
2744 }
|
|
2745
|
|
2746 override IConduit conduit()
|
|
2747 {
|
|
2748 return source.conduit;
|
|
2749 }
|
|
2750
|
|
2751 override void close()
|
|
2752 {
|
|
2753 source = null;
|
|
2754 seeker = null;
|
|
2755 }
|
|
2756
|
|
2757 override uint read(void[] dst)
|
|
2758 {
|
|
2759 if( seeker.seek(0, Anchor.Current) != _position )
|
|
2760 seeker.seek(_position, Anchor.Begin);
|
|
2761
|
|
2762 auto read = source.read(dst);
|
|
2763 if( read != IConduit.Eof )
|
|
2764 _position += read;
|
|
2765
|
|
2766 return read;
|
|
2767 }
|
|
2768
|
|
2769 override InputStream clear()
|
|
2770 {
|
|
2771 source.clear();
|
|
2772 return this;
|
|
2773 }
|
|
2774
|
|
2775 override long seek(long offset, Anchor anchor = cast(Anchor)0)
|
|
2776 {
|
|
2777 seeker.seek(_position, Anchor.Begin);
|
|
2778 return (_position = seeker.seek(offset, anchor));
|
|
2779 }
|
|
2780
|
|
2781 private:
|
|
2782 InputStream source;
|
|
2783 IConduit.Seek seeker;
|
|
2784 long _position;
|
|
2785
|
|
2786 invariant
|
|
2787 {
|
|
2788 assert( cast(Object) source is cast(Object) seeker );
|
|
2789 assert( _position >= 0 );
|
|
2790 }
|
|
2791 }
|
|
2792
|
|
2793 /**
|
|
2794 * This stream can be used to provide access to another stream.
|
|
2795 * Its distinguishing feature is that the users cannot close the underlying
|
|
2796 * stream.
|
|
2797 *
|
|
2798 * This stream fully supports seeking, and as such requires that the
|
|
2799 * underlying stream also support seeking.
|
|
2800 */
|
|
2801 class WrapSeekOutputStream : OutputStream, IConduit.Seek
|
|
2802 {
|
|
2803 alias IConduit.Seek.Anchor Anchor;
|
|
2804
|
|
2805 /**
|
|
2806 * Create a new wrap stream from the given source.
|
|
2807 */
|
|
2808 this(OutputStream source)
|
|
2809 in
|
|
2810 {
|
|
2811 assert( (cast(IConduit.Seek) source) !is null );
|
|
2812 }
|
|
2813 body
|
|
2814 {
|
|
2815 this.source = source;
|
|
2816 this.seeker = cast(IConduit.Seek) source;
|
|
2817 this._position = seeker.seek(0, Anchor.Current);
|
|
2818 }
|
|
2819
|
|
2820 /// ditto
|
|
2821 this(OutputStream source, long position)
|
|
2822 in
|
|
2823 {
|
|
2824 assert( position >= 0 );
|
|
2825 }
|
|
2826 body
|
|
2827 {
|
|
2828 this(source);
|
|
2829 this._position = position;
|
|
2830 }
|
|
2831
|
|
2832 override IConduit conduit()
|
|
2833 {
|
|
2834 return source.conduit;
|
|
2835 }
|
|
2836
|
|
2837 override void close()
|
|
2838 {
|
|
2839 source = null;
|
|
2840 seeker = null;
|
|
2841 }
|
|
2842
|
|
2843 uint write(void[] src)
|
|
2844 {
|
|
2845 if( seeker.seek(0, Anchor.Current) != _position )
|
|
2846 seeker.seek(_position, Anchor.Begin);
|
|
2847
|
|
2848 auto wrote = source.write(src);
|
|
2849 if( wrote != IConduit.Eof )
|
|
2850 _position += wrote;
|
|
2851 return wrote;
|
|
2852 }
|
|
2853
|
|
2854 override OutputStream copy(InputStream src)
|
|
2855 {
|
|
2856 return Conduit.transfer(src, this);
|
|
2857 }
|
|
2858
|
|
2859 override OutputStream flush()
|
|
2860 {
|
|
2861 source.flush();
|
|
2862 return this;
|
|
2863 }
|
|
2864
|
|
2865 override long seek(long offset, Anchor anchor = cast(Anchor)0)
|
|
2866 {
|
|
2867 seeker.seek(_position, Anchor.Begin);
|
|
2868 return (_position = seeker.seek(offset, anchor));
|
|
2869 }
|
|
2870
|
|
2871 private:
|
|
2872 OutputStream source;
|
|
2873 IConduit.Seek seeker;
|
|
2874 long _position;
|
|
2875
|
|
2876 invariant
|
|
2877 {
|
|
2878 assert( cast(Object) source is cast(Object) seeker );
|
|
2879 assert( _position >= 0 );
|
|
2880 }
|
|
2881 }
|
|
2882
|