zip_tricks 4.5.2 → 4.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -78
- data/.travis.yml +8 -4
- data/CHANGELOG.md +14 -2
- data/CODE_OF_CONDUCT.md +46 -0
- data/CONTRIBUTING.md +151 -0
- data/README.md +1 -1
- data/bench/buffered_crc32_bench.rb +111 -0
- data/examples/rack_application.rb +1 -1
- data/lib/zip_tricks/file_reader.rb +104 -126
- data/lib/zip_tricks/remote_io.rb +2 -6
- data/lib/zip_tricks/stream_crc32.rb +2 -16
- data/lib/zip_tricks/streamer.rb +29 -16
- data/lib/zip_tricks/streamer/deflated_writer.rb +34 -11
- data/lib/zip_tricks/streamer/entry.rb +0 -1
- data/lib/zip_tricks/streamer/stored_writer.rb +21 -8
- data/lib/zip_tricks/version.rb +1 -1
- data/lib/zip_tricks/write_buffer.rb +49 -0
- data/lib/zip_tricks/zip_writer.rb +138 -132
- data/qa/README_QA.md +16 -0
- data/{testing → qa}/generate_test_files.rb +0 -0
- data/{testing → qa}/in/VTYL8830.jpg +0 -0
- data/{testing → qa}/in/war-and-peace.txt +0 -0
- data/{testing → qa}/support.rb +3 -3
- data/{testing → qa}/test-report-2016-07-28.txt +0 -0
- data/{testing → qa}/test-report-2016-12-12.txt +0 -0
- data/{testing → qa}/test-report-2017-04-2.txt +0 -0
- data/{testing → qa}/test-report.txt +0 -0
- data/zip_tricks.gemspec +2 -3
- metadata +33 -16
- data/.rubocop_todo.yml +0 -43
- data/testing/README_TESTING.md +0 -12
@@ -53,7 +53,7 @@ class ZipDownload
|
|
53
53
|
|
54
54
|
# Add a Content-Disposition so that the download has a .zip extension
|
55
55
|
# (this will not work well with UTF-8 filenames on Windows, but hey!)
|
56
|
-
content_disposition =
|
56
|
+
content_disposition = 'attachment; filename=%<filename>s.zip' % {filename: filename}
|
57
57
|
|
58
58
|
# and return the response, adding the Content-Length we have computed earlier
|
59
59
|
[
|
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
# rubocop:disable Naming/ConstantName
|
4
|
-
|
5
3
|
require 'stringio'
|
6
4
|
|
7
5
|
# A very barebones ZIP file reader. Is made for maximum interoperability, but at the same
|
@@ -146,8 +144,8 @@ class ZipTricks::FileReader
|
|
146
144
|
when 0
|
147
145
|
StoredReader.new(from_io, compressed_size)
|
148
146
|
else
|
149
|
-
raise UnsupportedFeature,
|
150
|
-
|
147
|
+
raise UnsupportedFeature, 'Unsupported storage mode for reading - %<storage_mode>d' %
|
148
|
+
{storage_mode: storage_mode}
|
151
149
|
end
|
152
150
|
end
|
153
151
|
|
@@ -208,7 +206,10 @@ class ZipTricks::FileReader
|
|
208
206
|
num_files_and_central_directory_offset(io, eocd_offset)
|
209
207
|
end
|
210
208
|
|
211
|
-
log
|
209
|
+
log do
|
210
|
+
'Located the central directory start at %<location>d' %
|
211
|
+
{location: cdir_location}
|
212
|
+
end
|
212
213
|
seek(io, cdir_location)
|
213
214
|
|
214
215
|
# Read the entire central directory AND anything behind it, in one fell swoop.
|
@@ -227,15 +228,15 @@ class ZipTricks::FileReader
|
|
227
228
|
central_directory_str = io.read # and not read_n(io, cdir_size), see above
|
228
229
|
central_directory_io = StringIO.new(central_directory_str)
|
229
230
|
log do
|
230
|
-
|
231
|
-
|
231
|
+
'Read %<byte_size>d bytes with central directory + EOCD record and locator' %
|
232
|
+
{byte_size: central_directory_str.bytesize}
|
232
233
|
end
|
233
234
|
|
234
235
|
entries = (0...num_files).map do |entry_n|
|
235
236
|
offset_location = cdir_location + central_directory_io.tell
|
236
237
|
log do
|
237
|
-
|
238
|
-
|
238
|
+
'Reading the central directory entry %<entry_n>d starting at offset %<offset>d' %
|
239
|
+
{entry_n: entry_n, offset: offset_location}
|
239
240
|
end
|
240
241
|
read_cdir_entry(central_directory_io)
|
241
242
|
end
|
@@ -272,16 +273,16 @@ class ZipTricks::FileReader
|
|
272
273
|
entries << entry
|
273
274
|
next_local_header_offset = entry.compressed_data_offset + entry.compressed_size
|
274
275
|
log do
|
275
|
-
|
276
|
-
|
276
|
+
'Recovered a local file file header at offset %<cur_offset>d, seeking to the next at %<header_offset>d' %
|
277
|
+
{cur_offset: cur_offset, header_offset: next_local_header_offset}
|
277
278
|
end
|
278
279
|
seek(io, next_local_header_offset) # Seek to the next entry, and raise if seek is impossible
|
279
280
|
end
|
280
281
|
entries
|
281
282
|
rescue ReadError
|
282
283
|
log do
|
283
|
-
|
284
|
-
|
284
|
+
'Got a read/seek error after reaching %<cur_offset>d, no more entries can be recovered' %
|
285
|
+
{cur_offset: cur_offset}
|
285
286
|
end
|
286
287
|
entries
|
287
288
|
end
|
@@ -332,8 +333,8 @@ class ZipTricks::FileReader
|
|
332
333
|
# the values fetched from the conventional header
|
333
334
|
zip64_extra = StringIO.new(zip64_extra_contents)
|
334
335
|
log do
|
335
|
-
|
336
|
-
|
336
|
+
'Will read Zip64 extra data from local header field for %<filename>s, %<size>d bytes' %
|
337
|
+
{filename: e.filename, size: zip64_extra.size}
|
337
338
|
end
|
338
339
|
# Now here be dragons. The APPNOTE specifies that
|
339
340
|
#
|
@@ -396,8 +397,8 @@ class ZipTricks::FileReader
|
|
396
397
|
def read_local_headers(entries, io)
|
397
398
|
entries.each_with_index do |entry, i|
|
398
399
|
log do
|
399
|
-
|
400
|
-
|
400
|
+
'Reading the local header for entry %<index>d at offset %<offset>d' %
|
401
|
+
{index: i, offset: entry.local_file_header_offset}
|
401
402
|
end
|
402
403
|
off = get_compressed_data_offset(io: io,
|
403
404
|
local_file_header_offset: entry.local_file_header_offset)
|
@@ -429,7 +430,6 @@ class ZipTricks::FileReader
|
|
429
430
|
|
430
431
|
def assert_signature(io, signature_magic_number)
|
431
432
|
readback = read_4b(io)
|
432
|
-
# Rubocop: Use a guard clause instead of wrapping the code inside a conditional expression
|
433
433
|
if readback != signature_magic_number
|
434
434
|
expected = '0x0' + signature_magic_number.to_s(16)
|
435
435
|
actual = '0x0' + readback.to_s(16)
|
@@ -460,24 +460,21 @@ class ZipTricks::FileReader
|
|
460
460
|
end
|
461
461
|
|
462
462
|
def read_2b(io)
|
463
|
-
read_n(io, 2).unpack(
|
463
|
+
read_n(io, 2).unpack(C_UINT2).shift
|
464
464
|
end
|
465
465
|
|
466
466
|
def read_4b(io)
|
467
|
-
read_n(io, 4).unpack(
|
467
|
+
read_n(io, 4).unpack(C_UINT4).shift
|
468
468
|
end
|
469
469
|
|
470
470
|
def read_8b(io)
|
471
|
-
read_n(io, 8).unpack(
|
471
|
+
read_n(io, 8).unpack(C_UINT8).shift
|
472
472
|
end
|
473
473
|
|
474
474
|
def read_cdir_entry(io)
|
475
|
-
# Rubocop: convention: Assignment Branch Condition size for
|
476
475
|
# read_cdir_entry is too high. [45.66/15]
|
477
|
-
# Rubocop: convention: Method has too many lines. [30/10]
|
478
476
|
assert_signature(io, 0x02014b50)
|
479
477
|
ZipEntry.new.tap do |e|
|
480
|
-
# Rubocop: convention: Block has too many lines. [27/25]
|
481
478
|
e.made_by = read_2b(io)
|
482
479
|
e.version_needed_to_extract = read_2b(io)
|
483
480
|
e.gp_flags = read_2b(io)
|
@@ -510,8 +507,8 @@ class ZipTricks::FileReader
|
|
510
507
|
# the values fetched from the conventional header
|
511
508
|
zip64_extra = StringIO.new(zip64_extra_contents)
|
512
509
|
log do
|
513
|
-
|
514
|
-
|
510
|
+
'Will read Zip64 extra data for %<filename>s, %<size>d bytes' %
|
511
|
+
{filename: e.filename, size: zip64_extra.size}
|
515
512
|
end
|
516
513
|
# Now here be dragons. The APPNOTE specifies that
|
517
514
|
#
|
@@ -522,16 +519,9 @@ class ZipTricks::FileReader
|
|
522
519
|
#
|
523
520
|
# It means that before we read this stuff we need to check if the previously-read
|
524
521
|
# values are at overflow, and only _then_ proceed to read them. Bah.
|
525
|
-
|
526
|
-
if e.
|
527
|
-
|
528
|
-
end
|
529
|
-
if e.compressed_size == 0xFFFFFFFF
|
530
|
-
e.compressed_size = read_8b(zip64_extra)
|
531
|
-
end
|
532
|
-
if e.local_file_header_offset == 0xFFFFFFFF
|
533
|
-
e.local_file_header_offset = read_8b(zip64_extra)
|
534
|
-
end
|
522
|
+
e.uncompressed_size = read_8b(zip64_extra) if e.uncompressed_size == 0xFFFFFFFF
|
523
|
+
e.compressed_size = read_8b(zip64_extra) if e.compressed_size == 0xFFFFFFFF
|
524
|
+
e.local_file_header_offset = read_8b(zip64_extra) if e.local_file_header_offset == 0xFFFFFFFF
|
535
525
|
# Disk number comes last and we can skip it anyway, since we do
|
536
526
|
# not support multi-disk archives
|
537
527
|
end
|
@@ -553,54 +543,56 @@ class ZipTricks::FileReader
|
|
553
543
|
raise MissingEOCD unless eocd_idx_in_buf
|
554
544
|
|
555
545
|
eocd_offset = implied_position_of_eocd_record + eocd_idx_in_buf
|
556
|
-
log
|
546
|
+
log do
|
547
|
+
'Found EOCD signature at offset %<offset>d' % {offset: eocd_offset}
|
548
|
+
end
|
557
549
|
|
558
550
|
eocd_offset
|
559
551
|
end
|
560
552
|
|
561
|
-
|
562
|
-
|
553
|
+
def all_indices_of_substr_in_str(of_substring, in_string)
|
554
|
+
last_i = 0
|
555
|
+
found_at_indices = []
|
556
|
+
while last_i = in_string.index(of_substring, last_i)
|
557
|
+
found_at_indices << last_i
|
558
|
+
last_i += of_substring.bytesize
|
559
|
+
end
|
560
|
+
found_at_indices
|
561
|
+
end
|
562
|
+
|
563
|
+
# We have to scan the maximum possible number
|
564
|
+
# of bytes that the EOCD can theoretically occupy including the comment after it,
|
563
565
|
# and we have to find a combination of:
|
564
|
-
# [EOCD signature, <some ZIP medatata>, comment byte size,
|
565
|
-
#
|
566
|
-
#
|
567
|
-
#
|
568
|
-
# Rubocop: convention: Assignment Branch Condition size for
|
569
|
-
# locate_eocd_signature is too high. [17.49/15]
|
570
|
-
# Rubocop: convention: Method has too many lines. [14/10]
|
566
|
+
# [EOCD signature, <some ZIP medatata>, comment byte size, comment of size]
|
567
|
+
# at the end. To do so, we first find all indices of the signature in the trailer
|
568
|
+
# string, and then check whether the bytestring starting at the signature and
|
569
|
+
# ending at the end of string satisfies that given pattern.
|
571
570
|
def locate_eocd_signature(in_str)
|
572
|
-
|
573
|
-
|
574
|
-
# a sliding window. Once our end offset matches the comment size we found our
|
575
|
-
# EOCD marker.
|
571
|
+
eocd_signature = 0x06054b50
|
572
|
+
eocd_signature_str = [eocd_signature].pack('V')
|
576
573
|
unpack_pattern = 'VvvvvVVv'
|
577
574
|
minimum_record_size = 22
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
#
|
583
|
-
break
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
assumed_eocd_location = in_str.bytesize - comment_size - minimum_record_size
|
591
|
-
# if the comment size is where we should be at - we found our EOCD
|
592
|
-
return assumed_eocd_location if assumed_eocd_location == window_location
|
575
|
+
str_size = in_str.bytesize
|
576
|
+
indices = all_indices_of_substr_in_str(eocd_signature_str, in_str)
|
577
|
+
indices.each do |check_at|
|
578
|
+
maybe_record = in_str[check_at..str_size]
|
579
|
+
# If the record is smaller than the minimum - we will never recover anything
|
580
|
+
break if maybe_record.bytesize < minimum_record_size
|
581
|
+
# Now we check if the record ends with the combination
|
582
|
+
# of the comment size and an arbitrary byte string of that size.
|
583
|
+
# If it does - we found our match
|
584
|
+
*_unused, comment_size = maybe_record.unpack(unpack_pattern)
|
585
|
+
if (maybe_record.bytesize - minimum_record_size) == comment_size
|
586
|
+
return check_at # Found the EOCD marker location
|
593
587
|
end
|
594
|
-
|
595
|
-
end_location -= 1 # Shift the window back, by one byte, and try again.
|
596
588
|
end
|
589
|
+
# If we haven't caught anything, return nil deliberately instead of returning the last statement
|
590
|
+
nil
|
597
591
|
end
|
598
592
|
|
599
593
|
# Find the Zip64 EOCD locator segment offset. Do this by seeking backwards from the
|
600
594
|
# EOCD record in the archive by fixed offsets
|
601
|
-
# Rubocop: convention: Assignment Branch Condition size for
|
602
595
|
# get_zip64_eocd_location is too high. [15.17/15]
|
603
|
-
# Rubocop: convention: Method has too many lines. [15/10]
|
604
596
|
def get_zip64_eocd_location(file_io, eocd_offset)
|
605
597
|
zip64_eocd_loc_offset = eocd_offset
|
606
598
|
zip64_eocd_loc_offset -= 4 # The signature
|
@@ -609,8 +601,8 @@ class ZipTricks::FileReader
|
|
609
601
|
zip64_eocd_loc_offset -= 4 # Total number of disks
|
610
602
|
|
611
603
|
log do
|
612
|
-
|
613
|
-
|
604
|
+
'Will look for the Zip64 EOCD locator signature at offset %<offset>d' %
|
605
|
+
{offset: zip64_eocd_loc_offset}
|
614
606
|
end
|
615
607
|
|
616
608
|
# If the offset is negative there is certainly no Zip64 EOCD locator here
|
@@ -619,7 +611,9 @@ class ZipTricks::FileReader
|
|
619
611
|
file_io.seek(zip64_eocd_loc_offset, IO::SEEK_SET)
|
620
612
|
assert_signature(file_io, 0x07064b50)
|
621
613
|
|
622
|
-
log
|
614
|
+
log do
|
615
|
+
'Found Zip64 EOCD locator at offset %<offset>d' % {offset: zip64_eocd_loc_offset}
|
616
|
+
end
|
623
617
|
|
624
618
|
disk_num = read_4b(file_io) # number of the disk
|
625
619
|
raise UnsupportedFeature, 'The archive spans multiple disks' if disk_num != 0
|
@@ -628,9 +622,7 @@ class ZipTricks::FileReader
|
|
628
622
|
nil
|
629
623
|
end
|
630
624
|
|
631
|
-
# Rubocop: convention: Assignment Branch Condition size for
|
632
625
|
# num_files_and_central_directory_offset_zip64 is too high. [21.12/15]
|
633
|
-
# Rubocop: convention: Method has too many lines. [17/10]
|
634
626
|
def num_files_and_central_directory_offset_zip64(io, zip64_end_of_cdir_location)
|
635
627
|
seek(io, zip64_end_of_cdir_location)
|
636
628
|
|
@@ -644,20 +636,16 @@ class ZipTricks::FileReader
|
|
644
636
|
|
645
637
|
disk_n = read_4b(zip64_eocdr) # number of this disk
|
646
638
|
disk_n_with_eocdr = read_4b(zip64_eocdr) # number of the disk with the EOCDR
|
647
|
-
if disk_n != disk_n_with_eocdr
|
648
|
-
raise UnsupportedFeature, 'The archive spans multiple disks'
|
649
|
-
end
|
639
|
+
raise UnsupportedFeature, 'The archive spans multiple disks' if disk_n != disk_n_with_eocdr
|
650
640
|
|
651
641
|
num_files_this_disk = read_8b(zip64_eocdr) # number of files on this disk
|
652
642
|
num_files_total = read_8b(zip64_eocdr) # files total in the central directory
|
653
643
|
|
654
|
-
if num_files_this_disk != num_files_total
|
655
|
-
raise UnsupportedFeature, 'The archive spans multiple disks'
|
656
|
-
end
|
644
|
+
raise UnsupportedFeature, 'The archive spans multiple disks' if num_files_this_disk != num_files_total
|
657
645
|
|
658
646
|
log do
|
659
|
-
|
660
|
-
|
647
|
+
'Zip64 EOCD record states there are %<amount>d files in the archive' %
|
648
|
+
{amount: num_files_total}
|
661
649
|
end
|
662
650
|
|
663
651
|
central_dir_size = read_8b(zip64_eocdr) # Size of the central directory
|
@@ -666,58 +654,48 @@ class ZipTricks::FileReader
|
|
666
654
|
[num_files_total, central_dir_offset, central_dir_size]
|
667
655
|
end
|
668
656
|
|
669
|
-
|
670
|
-
|
671
|
-
|
657
|
+
C_UINT4 = 'V'
|
658
|
+
C_UINT2 = 'v'
|
659
|
+
C_UINT8 = 'Q<'
|
672
660
|
|
673
661
|
# To prevent too many tiny reads, read the maximum possible size of end of
|
674
662
|
# central directory record upfront (all the fixed fields + at most 0xFFFF
|
675
663
|
# bytes of the archive comment)
|
676
|
-
MAX_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE =
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
2 + # The comment size
|
686
|
-
0xFFFF # Maximum comment size
|
687
|
-
end
|
664
|
+
MAX_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE = 4 + # Offset of the start of central directory
|
665
|
+
4 + # Size of the central directory
|
666
|
+
2 + # Number of files in the cdir
|
667
|
+
4 + # End-of-central-directory signature
|
668
|
+
2 + # Number of this disk
|
669
|
+
2 + # Number of disk with the start of cdir
|
670
|
+
2 + # Number of files in the cdir of this disk
|
671
|
+
2 + # The comment size
|
672
|
+
0xFFFF # Maximum comment size
|
688
673
|
|
689
674
|
# To prevent too many tiny reads, read the maximum possible size of the local file header upfront.
|
690
675
|
# The maximum size is all the usual items, plus the maximum size
|
691
676
|
# of the filename (0xFFFF bytes) and the maximum size of the extras (0xFFFF bytes)
|
692
|
-
MAX_LOCAL_HEADER_SIZE =
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
2 + # Number of the disk with the EOCD record
|
714
|
-
2 + # Number of entries in the central directory of this disk
|
715
|
-
2 + # Number of entries in the central directory total
|
716
|
-
4 + # Size of the central directory
|
717
|
-
4 # Start of the central directory offset
|
718
|
-
end
|
677
|
+
MAX_LOCAL_HEADER_SIZE = 4 + # signature
|
678
|
+
2 + # Version needed to extract
|
679
|
+
2 + # gp flags
|
680
|
+
2 + # storage mode
|
681
|
+
2 + # dos time
|
682
|
+
2 + # dos date
|
683
|
+
4 + # CRC32
|
684
|
+
4 + # Comp size
|
685
|
+
4 + # Uncomp size
|
686
|
+
2 + # Filename size
|
687
|
+
2 + # Extra fields size
|
688
|
+
0xFFFF + # Maximum filename size
|
689
|
+
0xFFFF # Maximum extra fields size
|
690
|
+
|
691
|
+
SIZE_OF_USABLE_EOCD_RECORD = 4 + # Signature
|
692
|
+
2 + # Number of this disk
|
693
|
+
2 + # Number of the disk with the EOCD record
|
694
|
+
2 + # Number of entries in the central directory of this disk
|
695
|
+
2 + # Number of entries in the central directory total
|
696
|
+
4 + # Size of the central directory
|
697
|
+
4 # Start of the central directory offset
|
719
698
|
|
720
|
-
# Rubocop: convention: Method has too many lines. [11/10]
|
721
699
|
def num_files_and_central_directory_offset(file_io, eocd_offset)
|
722
700
|
seek(file_io, eocd_offset)
|
723
701
|
|
@@ -735,7 +713,7 @@ class ZipTricks::FileReader
|
|
735
713
|
[num_files, cdir_offset, cdir_size]
|
736
714
|
end
|
737
715
|
|
738
|
-
private_constant :
|
716
|
+
private_constant :C_UINT4, :C_UINT2, :C_UINT8, :MAX_END_OF_CENTRAL_DIRECTORY_RECORD_SIZE,
|
739
717
|
:MAX_LOCAL_HEADER_SIZE, :SIZE_OF_USABLE_EOCD_RECORD
|
740
718
|
|
741
719
|
# Is provided as a stub to be overridden in a subclass if you need it. Will report
|
data/lib/zip_tricks/remote_io.rb
CHANGED
@@ -38,13 +38,11 @@ class ZipTricks::RemoteIO
|
|
38
38
|
#
|
39
39
|
# @param n_bytes[Fixnum, nil] how many bytes to read, or `nil` to read all the way to the end
|
40
40
|
# @return [String] the read bytes
|
41
|
-
# Rubocop: convention: Assignment Branch Condition size for read is too high. [17.92/15]
|
42
|
-
# Rubocop: convention: Method has too many lines. [13/10]
|
43
41
|
def read(n_bytes = nil)
|
44
42
|
@remote_size ||= request_object_size
|
45
43
|
|
46
44
|
# If the resource is empty there is nothing to read
|
47
|
-
return
|
45
|
+
return if @remote_size.zero?
|
48
46
|
|
49
47
|
maximum_avaialable = @remote_size - @pos
|
50
48
|
n_bytes ||= maximum_avaialable # nil == read to the end of file
|
@@ -54,9 +52,7 @@ class ZipTricks::RemoteIO
|
|
54
52
|
n_bytes = clamp(0, n_bytes, maximum_avaialable)
|
55
53
|
|
56
54
|
read_n_bytes_from_remote(@pos, n_bytes).tap do |data|
|
57
|
-
if data.bytesize != n_bytes
|
58
|
-
raise "Remote read returned #{data.bytesize} bytes instead of #{n_bytes} as requested"
|
59
|
-
end
|
55
|
+
raise "Remote read returned #{data.bytesize} bytes instead of #{n_bytes} as requested" if data.bytesize != n_bytes
|
60
56
|
@pos = clamp(0, @pos + data.bytesize, @remote_size)
|
61
57
|
end
|
62
58
|
end
|
@@ -2,21 +2,18 @@
|
|
2
2
|
|
3
3
|
# A simple stateful class for keeping track of a CRC32 value through multiple writes
|
4
4
|
class ZipTricks::StreamCRC32
|
5
|
-
BUFFER_SIZE = 1024 * 1024 * 5
|
6
|
-
|
7
5
|
# Compute a CRC32 value from an IO object. The object should respond to `read` and `eof?`
|
8
6
|
#
|
9
7
|
# @param io[IO] the IO to read the data from
|
10
8
|
# @return [Fixnum] the computed CRC32 value
|
11
9
|
def self.from_io(io)
|
12
10
|
crc = new
|
13
|
-
crc << io.read(
|
11
|
+
crc << io.read(1024 * 512) until io.eof?
|
14
12
|
crc.to_i
|
15
13
|
end
|
16
14
|
|
17
15
|
# Creates a new streaming CRC32 calculator
|
18
16
|
def initialize
|
19
|
-
@buf = StringIO.new
|
20
17
|
@crc = Zlib.crc32('')
|
21
18
|
end
|
22
19
|
|
@@ -25,8 +22,7 @@ class ZipTricks::StreamCRC32
|
|
25
22
|
# @param blob[String] the string to compute the CRC32 from
|
26
23
|
# @return [self]
|
27
24
|
def <<(blob)
|
28
|
-
@
|
29
|
-
buf_flush if @buf.size > BUFFER_SIZE
|
25
|
+
@crc = Zlib.crc32_combine(@crc, Zlib.crc32(blob), blob.bytesize)
|
30
26
|
self
|
31
27
|
end
|
32
28
|
|
@@ -34,7 +30,6 @@ class ZipTricks::StreamCRC32
|
|
34
30
|
#
|
35
31
|
# @return [Fixnum] the updated CRC32 value for all the blobs so far
|
36
32
|
def to_i
|
37
|
-
buf_flush if @buf.size > 0
|
38
33
|
@crc
|
39
34
|
end
|
40
35
|
|
@@ -45,15 +40,6 @@ class ZipTricks::StreamCRC32
|
|
45
40
|
# @param blob_size[Fixnum] the size of the daata the `crc32` is computed from
|
46
41
|
# @return [Fixnum] the updated CRC32 value for all the blobs so far
|
47
42
|
def append(crc32, blob_size)
|
48
|
-
buf_flush if @buf.size > 0
|
49
43
|
@crc = Zlib.crc32_combine(@crc, crc32, blob_size)
|
50
44
|
end
|
51
|
-
|
52
|
-
private
|
53
|
-
|
54
|
-
def buf_flush
|
55
|
-
@crc = Zlib.crc32_combine(@crc, Zlib.crc32(@buf.string), @buf.size)
|
56
|
-
@buf.truncate(0)
|
57
|
-
@buf.rewind
|
58
|
-
end
|
59
45
|
end
|