comparison tango/tango/io/archive/Zip.d @ 132:1700239cab2e trunk

[svn r136] MAJOR UNSTABLE UPDATE!!! Initial commit after moving to Tango instead of Phobos. Lots of bugfixes... This build is not suitable for most things.
author lindquist
date Fri, 11 Jan 2008 17:57:40 +0100
parents
children
comparison
equal deleted inserted replaced
131:5825d48b27d1 132:1700239cab2e
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