Mercurial > projects > ldc
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 |