ziperu 0.1.1-aarch64-linux
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 +7 -0
- data/CHANGELOG.md +24 -0
- data/LICENSE-rubyzip.txt +24 -0
- data/LICENSE.txt +21 -0
- data/README.md +276 -0
- data/lib/rubyzip.rb +4 -0
- data/lib/zip/central_directory.rb +5 -0
- data/lib/zip/compressor.rb +5 -0
- data/lib/zip/constants.rb +5 -0
- data/lib/zip/crypto/aes_encryption.rb +5 -0
- data/lib/zip/crypto/decrypted_io.rb +5 -0
- data/lib/zip/crypto/encryption.rb +5 -0
- data/lib/zip/crypto/null_encryption.rb +5 -0
- data/lib/zip/crypto/traditional_encryption.rb +5 -0
- data/lib/zip/crypto.rb +5 -0
- data/lib/zip/decompressor.rb +5 -0
- data/lib/zip/deflater.rb +5 -0
- data/lib/zip/dirtyable.rb +5 -0
- data/lib/zip/dos_time.rb +5 -0
- data/lib/zip/entry.rb +5 -0
- data/lib/zip/entry_set.rb +5 -0
- data/lib/zip/errors.rb +5 -0
- data/lib/zip/extra_field/aes.rb +5 -0
- data/lib/zip/extra_field/generic.rb +5 -0
- data/lib/zip/extra_field/ntfs.rb +5 -0
- data/lib/zip/extra_field/old_unix.rb +5 -0
- data/lib/zip/extra_field/universal_time.rb +5 -0
- data/lib/zip/extra_field/unix.rb +5 -0
- data/lib/zip/extra_field/unknown.rb +5 -0
- data/lib/zip/extra_field/zip64.rb +5 -0
- data/lib/zip/extra_field.rb +5 -0
- data/lib/zip/file.rb +5 -0
- data/lib/zip/file_split.rb +5 -0
- data/lib/zip/filesystem/dir.rb +5 -0
- data/lib/zip/filesystem/directory_iterator.rb +5 -0
- data/lib/zip/filesystem/file.rb +5 -0
- data/lib/zip/filesystem/file_stat.rb +5 -0
- data/lib/zip/filesystem/zip_file_name_mapper.rb +5 -0
- data/lib/zip/filesystem.rb +5 -0
- data/lib/zip/inflater.rb +5 -0
- data/lib/zip/input_stream.rb +5 -0
- data/lib/zip/ioextras/abstract_input_stream.rb +5 -0
- data/lib/zip/ioextras/abstract_output_stream.rb +5 -0
- data/lib/zip/ioextras/fake_io.rb +5 -0
- data/lib/zip/ioextras.rb +5 -0
- data/lib/zip/null_compressor.rb +5 -0
- data/lib/zip/null_decompressor.rb +5 -0
- data/lib/zip/null_input_stream.rb +5 -0
- data/lib/zip/output_stream.rb +5 -0
- data/lib/zip/pass_thru_compressor.rb +5 -0
- data/lib/zip/pass_thru_decompressor.rb +5 -0
- data/lib/zip/streamable_directory.rb +5 -0
- data/lib/zip/streamable_stream.rb +5 -0
- data/lib/zip/version.rb +5 -0
- data/lib/zip.rb +19 -0
- data/lib/ziperu/3.1/ziperu.so +0 -0
- data/lib/ziperu/3.2/ziperu.so +0 -0
- data/lib/ziperu/3.3/ziperu.so +0 -0
- data/lib/ziperu/3.4/ziperu.so +0 -0
- data/lib/ziperu/4.0/ziperu.so +0 -0
- data/lib/ziperu/central_directory.rb +136 -0
- data/lib/ziperu/compressor.rb +14 -0
- data/lib/ziperu/constants.rb +148 -0
- data/lib/ziperu/crypto.rb +72 -0
- data/lib/ziperu/decompressor.rb +35 -0
- data/lib/ziperu/deflater.rb +41 -0
- data/lib/ziperu/dirtyable.rb +48 -0
- data/lib/ziperu/dos_time.rb +87 -0
- data/lib/ziperu/entry.rb +760 -0
- data/lib/ziperu/entry_set.rb +147 -0
- data/lib/ziperu/errors.rb +154 -0
- data/lib/ziperu/extra_field/aes.rb +52 -0
- data/lib/ziperu/extra_field/generic.rb +45 -0
- data/lib/ziperu/extra_field/ntfs.rb +98 -0
- data/lib/ziperu/extra_field/old_unix.rb +52 -0
- data/lib/ziperu/extra_field/universal_time.rb +81 -0
- data/lib/ziperu/extra_field/unix.rb +45 -0
- data/lib/ziperu/extra_field/unknown.rb +37 -0
- data/lib/ziperu/extra_field/zip64.rb +86 -0
- data/lib/ziperu/extra_field.rb +109 -0
- data/lib/ziperu/file.rb +614 -0
- data/lib/ziperu/file_split.rb +93 -0
- data/lib/ziperu/filesystem/dir.rb +88 -0
- data/lib/ziperu/filesystem/directory_iterator.rb +50 -0
- data/lib/ziperu/filesystem/file.rb +265 -0
- data/lib/ziperu/filesystem/file_stat.rb +112 -0
- data/lib/ziperu/filesystem/zip_file_name_mapper.rb +83 -0
- data/lib/ziperu/filesystem.rb +76 -0
- data/lib/ziperu/inflater.rb +60 -0
- data/lib/ziperu/input_stream.rb +296 -0
- data/lib/ziperu/ioextras/abstract_input_stream.rb +137 -0
- data/lib/ziperu/ioextras/abstract_output_stream.rb +57 -0
- data/lib/ziperu/ioextras/fake_io.rb +41 -0
- data/lib/ziperu/ioextras.rb +42 -0
- data/lib/ziperu/null_compressor.rb +20 -0
- data/lib/ziperu/null_decompressor.rb +25 -0
- data/lib/ziperu/null_input_stream.rb +55 -0
- data/lib/ziperu/output_stream.rb +223 -0
- data/lib/ziperu/pass_thru_compressor.rb +29 -0
- data/lib/ziperu/pass_thru_decompressor.rb +37 -0
- data/lib/ziperu/streamable_directory.rb +15 -0
- data/lib/ziperu/streamable_stream.rb +59 -0
- data/lib/ziperu/version.rb +7 -0
- data/lib/ziperu.rb +116 -0
- metadata +156 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Ported from rubyzip (Copyright (C) 2002-2026 the rubyzip authors).
|
|
4
|
+
|
|
5
|
+
module Ziperu
|
|
6
|
+
class EntrySet # :nodoc:all
|
|
7
|
+
include Enumerable
|
|
8
|
+
|
|
9
|
+
attr_reader :entry_set
|
|
10
|
+
protected :entry_set
|
|
11
|
+
|
|
12
|
+
def initialize(an_enumerable = [])
|
|
13
|
+
super()
|
|
14
|
+
# Keyed by name for lookup, as in rubyzip; `@all` keeps every entry
|
|
15
|
+
# in order, which only differs when an archive holds several records
|
|
16
|
+
# under one name (see `push_record`).
|
|
17
|
+
@entry_set = {}
|
|
18
|
+
@all = []
|
|
19
|
+
@shadowed = 0
|
|
20
|
+
an_enumerable.each { |o| push(o) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def include?(entry)
|
|
24
|
+
@entry_set.include?(to_key(entry))
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def find_entry(entry)
|
|
28
|
+
@entry_set[to_key(entry)]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def <<(entry)
|
|
32
|
+
return unless entry
|
|
33
|
+
|
|
34
|
+
key = to_key(entry)
|
|
35
|
+
old = @entry_set[key]
|
|
36
|
+
return if old.equal?(entry)
|
|
37
|
+
|
|
38
|
+
remove_from_all(old) if old
|
|
39
|
+
@entry_set[key] = entry
|
|
40
|
+
@all << entry
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
alias push <<
|
|
44
|
+
|
|
45
|
+
# Adds a record read from an archive. Unlike `<<`, an earlier record
|
|
46
|
+
# of the very same name stays listed (and readable); the lookup by name
|
|
47
|
+
# finds the later one, as rubyzip's does. Names that only share a key
|
|
48
|
+
# (`Ziperu.case_insensitive_match`, or a trailing slash) collapse to
|
|
49
|
+
# the later one, as in rubyzip.
|
|
50
|
+
def push_record(entry)
|
|
51
|
+
key = to_key(entry)
|
|
52
|
+
old = @entry_set[key]
|
|
53
|
+
return self << entry if old.nil? || old.name.b != entry.name.b
|
|
54
|
+
|
|
55
|
+
@shadowed += 1
|
|
56
|
+
@entry_set[key] = entry
|
|
57
|
+
@all << entry
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# True for a record that a later one of the same name hides from the
|
|
61
|
+
# lookup by name: it is listed and readable, but not written back.
|
|
62
|
+
def shadowed?(entry)
|
|
63
|
+
return false if @shadowed.zero?
|
|
64
|
+
|
|
65
|
+
current = @entry_set[to_key(entry)]
|
|
66
|
+
!current.nil? && !current.equal?(entry)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def size
|
|
70
|
+
@all.size
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
alias length size
|
|
74
|
+
|
|
75
|
+
def delete(entry)
|
|
76
|
+
key = to_key(entry)
|
|
77
|
+
removed = @entry_set.delete(key)
|
|
78
|
+
return if removed.nil?
|
|
79
|
+
|
|
80
|
+
remove_from_all(removed)
|
|
81
|
+
# A record this one shadowed takes over its name.
|
|
82
|
+
if @shadowed.positive? && (shadowed = @all.reverse_each.find { |e| to_key(e) == key })
|
|
83
|
+
@shadowed -= 1
|
|
84
|
+
@entry_set[key] = shadowed
|
|
85
|
+
end
|
|
86
|
+
entry
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def each(&block)
|
|
90
|
+
entries.each(&block)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def entries
|
|
94
|
+
sorted_entries
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# deep clone
|
|
98
|
+
def dup
|
|
99
|
+
copy = EntrySet.new
|
|
100
|
+
@all.each { |e| copy.push_record(e.dup) }
|
|
101
|
+
copy
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def ==(other)
|
|
105
|
+
return false unless other.kind_of?(EntrySet)
|
|
106
|
+
|
|
107
|
+
entries == other.entries
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def parent(entry)
|
|
111
|
+
@entry_set[to_key(entry.parent_as_string)]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def glob(pattern, flags = ::File::FNM_PATHNAME | ::File::FNM_DOTMATCH | ::File::FNM_EXTGLOB)
|
|
115
|
+
entries.filter_map do |entry|
|
|
116
|
+
next nil unless ::File.fnmatch(pattern, entry.name.chomp('/'), flags)
|
|
117
|
+
|
|
118
|
+
yield(entry) if block_given?
|
|
119
|
+
entry
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
protected
|
|
124
|
+
|
|
125
|
+
# A copy: callers rename or remove entries while iterating.
|
|
126
|
+
def sorted_entries
|
|
127
|
+
::Ziperu.sort_entries ? @all.sort_by { |e| to_key(e) } : @all.dup
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
# Removes `entry` itself (not another of the same name) from `@all`.
|
|
133
|
+
def remove_from_all(entry)
|
|
134
|
+
i = @all.index { |e| e.equal?(entry) }
|
|
135
|
+
@all.delete_at(i) if i
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Keyed by name bytes: entry names are binary strings (as in rubyzip),
|
|
139
|
+
# and a lookup with the same bytes in another encoding should find
|
|
140
|
+
# them (which in rubyzip it does not).
|
|
141
|
+
def to_key(entry)
|
|
142
|
+
k = entry.to_s.b.chomp('/')
|
|
143
|
+
k.downcase! if ::Ziperu.case_insensitive_match
|
|
144
|
+
k
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The rubyzip 3.x exception hierarchy, with rubyzip's constructor signatures
|
|
4
|
+
# and messages so rescue clauses and error output are unchanged.
|
|
5
|
+
#
|
|
6
|
+
# The native core looks these up by name, so every class here must exist
|
|
7
|
+
# before the extension raises.
|
|
8
|
+
module Ziperu
|
|
9
|
+
# The superclass for all error types. Simply rescue this one if you don't
|
|
10
|
+
# need to know what sort of error has been raised.
|
|
11
|
+
class Error < StandardError; end
|
|
12
|
+
|
|
13
|
+
# Error raised if an unsupported compression method is used.
|
|
14
|
+
class CompressionMethodError < Error
|
|
15
|
+
attr_reader :compression_method
|
|
16
|
+
|
|
17
|
+
def initialize(method)
|
|
18
|
+
super()
|
|
19
|
+
@compression_method = method
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def message
|
|
23
|
+
"Unsupported compression method: #{COMPRESSION_METHODS[@compression_method]}."
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Error raised if there is a problem while decompressing an archive entry.
|
|
28
|
+
class DecompressionError < Error
|
|
29
|
+
attr_reader :zlib_error
|
|
30
|
+
|
|
31
|
+
def initialize(zlib_error)
|
|
32
|
+
super()
|
|
33
|
+
@zlib_error = zlib_error
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def message
|
|
37
|
+
"Zlib error ('#{@zlib_error.message}') while inflating."
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Error raised when trying to extract an archive entry over an existing file.
|
|
42
|
+
class DestinationExistsError < Error
|
|
43
|
+
def initialize(destination)
|
|
44
|
+
super()
|
|
45
|
+
@destination = destination
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def message
|
|
49
|
+
"Cannot create file or directory '#{@destination}'. " \
|
|
50
|
+
'A file already exists with that name.'
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Error raised when adding an entry whose name already exists.
|
|
55
|
+
class EntryExistsError < Error
|
|
56
|
+
def initialize(source, name)
|
|
57
|
+
super()
|
|
58
|
+
@source = source
|
|
59
|
+
@name = name
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def message
|
|
63
|
+
"'#{@source}' failed. Entry #{@name} already exists."
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Error raised when an entry name is invalid.
|
|
68
|
+
class EntryNameError < Error
|
|
69
|
+
def initialize(name = nil)
|
|
70
|
+
super()
|
|
71
|
+
@name = name
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def message
|
|
75
|
+
if @name.nil?
|
|
76
|
+
'Illegal entry name. Names must have fewer than 65,536 characters.'
|
|
77
|
+
else
|
|
78
|
+
"Illegal entry name '#{@name}'. Names must not start with '/'."
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Error raised if an entry is larger on extraction than advertised.
|
|
84
|
+
class EntrySizeError < Error
|
|
85
|
+
attr_reader :entry
|
|
86
|
+
|
|
87
|
+
def initialize(entry)
|
|
88
|
+
super()
|
|
89
|
+
@entry = entry
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def message
|
|
93
|
+
"Entry '#{@entry.name}' should be #{@entry.size}B, but is larger when inflated."
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
class EntryNumberMismatchError < Error
|
|
98
|
+
def initialize(size, size_in_bytes)
|
|
99
|
+
super()
|
|
100
|
+
@size = size
|
|
101
|
+
@size_in_bytes = size_in_bytes
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def message
|
|
105
|
+
"Zip consistency problem: an impossibly high number of entries (#{@size}) " \
|
|
106
|
+
'is declared in this file, compared to the size of the central directory ' \
|
|
107
|
+
"(#{@size_in_bytes} bytes)."
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Error raised if a split archive is read.
|
|
112
|
+
class SplitArchiveError < Error
|
|
113
|
+
def message
|
|
114
|
+
'Rubyzip cannot extract from split archives at this time.'
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Error raised if there is not enough metadata for the entry to be streamed.
|
|
119
|
+
class StreamingError < Error
|
|
120
|
+
attr_reader :entry
|
|
121
|
+
|
|
122
|
+
def initialize(entry)
|
|
123
|
+
super()
|
|
124
|
+
@entry = entry
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def message
|
|
128
|
+
"The local header of this entry ('#{@entry.name}') does not contain " \
|
|
129
|
+
'the correct metadata for `Ziperu::InputStream` to be able to ' \
|
|
130
|
+
'uncompress it. Please use `Ziperu::File` instead of `Ziperu::InputStream`.'
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
class GPFBit3Error < Error; end
|
|
135
|
+
class InternalError < Error; end
|
|
136
|
+
|
|
137
|
+
# Raised when an archive exceeds `Ziperu.max_entries` or
|
|
138
|
+
# `Ziperu.max_total_size` on open.
|
|
139
|
+
class LimitError < Error
|
|
140
|
+
def initialize(what, actual, limit)
|
|
141
|
+
super("archive has #{what} of #{actual}, over the limit of #{limit}")
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
PASSWORD_REQUIRED_MESSAGE = 'A password is required to decode this zip file.'
|
|
146
|
+
|
|
147
|
+
# Raised by the Rust core where it lacks the domain object a rubyzip
|
|
148
|
+
# exception wants; the Ruby layer re-raises the rubyzip-shaped error.
|
|
149
|
+
module Native
|
|
150
|
+
class SizeLimitError < Ziperu::Error; end
|
|
151
|
+
class PasswordError < Ziperu::Error; end
|
|
152
|
+
class UnsupportedError < Ziperu::Error; end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Ported verbatim from rubyzip (Copyright (C) 2002-2026 the rubyzip authors).
|
|
4
|
+
|
|
5
|
+
module Ziperu
|
|
6
|
+
# Info-ZIP Extra for AES encryption
|
|
7
|
+
class ExtraField::AES < ExtraField::Generic # :nodoc:
|
|
8
|
+
attr_accessor :vendor_version, :vendor_id, :encryption_strength, :compression_method
|
|
9
|
+
|
|
10
|
+
HEADER_ID = [0x9901].pack('v')
|
|
11
|
+
register_map
|
|
12
|
+
|
|
13
|
+
def initialize(binstr = nil)
|
|
14
|
+
@vendor_version = nil
|
|
15
|
+
@vendor_id = nil
|
|
16
|
+
@encryption_strength = nil
|
|
17
|
+
@compression_method = nil
|
|
18
|
+
binstr && merge(binstr)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def ==(other)
|
|
22
|
+
@vendor_version == other.vendor_version &&
|
|
23
|
+
@vendor_id == other.vendor_id &&
|
|
24
|
+
@encryption_strength == other.encryption_strength &&
|
|
25
|
+
@compression_method == other.compression_method
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def merge(binstr)
|
|
29
|
+
return if binstr.empty?
|
|
30
|
+
|
|
31
|
+
size, content = initial_parse(binstr)
|
|
32
|
+
(size && content) || return
|
|
33
|
+
|
|
34
|
+
@vendor_version, @vendor_id,
|
|
35
|
+
@encryption_strength, @compression_method = content.unpack('va2Cv')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# We can never suppress the AES extra field as it is needed to read the file.
|
|
39
|
+
def suppress?
|
|
40
|
+
false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def pack_for_local
|
|
44
|
+
[@vendor_version, @vendor_id,
|
|
45
|
+
@encryption_strength, @compression_method].pack('va2Cv')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def pack_for_c_dir
|
|
49
|
+
pack_for_local
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Ported verbatim from rubyzip (Copyright (C) 2002-2026 the rubyzip authors).
|
|
4
|
+
|
|
5
|
+
module Ziperu
|
|
6
|
+
class ExtraField::Generic # :nodoc:
|
|
7
|
+
def self.register_map
|
|
8
|
+
return unless const_defined?(:HEADER_ID)
|
|
9
|
+
|
|
10
|
+
::Ziperu::ExtraField::ID_MAP[const_get(:HEADER_ID)] = self
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.name
|
|
14
|
+
@name ||= to_s.split('::').last.downcase.to_sym
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# return field [size, content] or false
|
|
18
|
+
def initial_parse(binstr)
|
|
19
|
+
return false unless binstr
|
|
20
|
+
|
|
21
|
+
if binstr[0, 2] != self.class.const_get(:HEADER_ID)
|
|
22
|
+
warn 'WARNING: weird extra field header ID. Skip parsing it.'
|
|
23
|
+
return false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
[binstr[2, 2].unpack1('v'), binstr[4..]]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Default strategy is to suppress all extra fields if we're asked to.
|
|
30
|
+
# Specific extra field types can override this if they need to be kept.
|
|
31
|
+
def suppress?
|
|
32
|
+
true
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def to_local_bin
|
|
36
|
+
s = pack_for_local
|
|
37
|
+
(self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v')) << s
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def to_c_dir_bin
|
|
41
|
+
s = pack_for_c_dir
|
|
42
|
+
(self.class.const_get(:HEADER_ID) + [s.bytesize].pack('v')) << s
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Ported verbatim from rubyzip (Copyright (C) 2002-2026 the rubyzip authors).
|
|
4
|
+
|
|
5
|
+
module Ziperu
|
|
6
|
+
# PKWARE NTFS Extra Field (0x000a)
|
|
7
|
+
# Only Tag 0x0001 is supported
|
|
8
|
+
class ExtraField::NTFS < ExtraField::Generic # :nodoc:
|
|
9
|
+
HEADER_ID = [0x000A].pack('v')
|
|
10
|
+
register_map
|
|
11
|
+
|
|
12
|
+
WINDOWS_TICK = 10_000_000.0
|
|
13
|
+
SEC_TO_UNIX_EPOCH = 11_644_473_600
|
|
14
|
+
|
|
15
|
+
def initialize(binstr = nil)
|
|
16
|
+
@ctime = nil
|
|
17
|
+
@mtime = nil
|
|
18
|
+
@atime = nil
|
|
19
|
+
binstr && merge(binstr)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
attr_accessor :atime, :ctime, :mtime
|
|
23
|
+
|
|
24
|
+
def merge(binstr)
|
|
25
|
+
return if binstr.empty?
|
|
26
|
+
|
|
27
|
+
size, content = initial_parse(binstr)
|
|
28
|
+
(size && content) || return
|
|
29
|
+
|
|
30
|
+
content = content[4..]
|
|
31
|
+
tags = parse_tags(content)
|
|
32
|
+
|
|
33
|
+
tag1 = tags[1]
|
|
34
|
+
return unless tag1
|
|
35
|
+
|
|
36
|
+
ntfs_mtime, ntfs_atime, ntfs_ctime = tag1.unpack('Q<Q<Q<')
|
|
37
|
+
ntfs_mtime && @mtime ||= from_ntfs_time(ntfs_mtime)
|
|
38
|
+
ntfs_atime && @atime ||= from_ntfs_time(ntfs_atime)
|
|
39
|
+
ntfs_ctime && @ctime ||= from_ntfs_time(ntfs_ctime)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def ==(other)
|
|
43
|
+
@mtime == other.mtime &&
|
|
44
|
+
@atime == other.atime &&
|
|
45
|
+
@ctime == other.ctime
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Info-ZIP note states this extra field is stored at local header
|
|
49
|
+
def pack_for_local
|
|
50
|
+
pack_for_c_dir
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# But 7-zip for Windows only stores at central dir
|
|
54
|
+
def pack_for_c_dir
|
|
55
|
+
# reserved 0 and tag 1
|
|
56
|
+
s = [0, 1].pack('Vv')
|
|
57
|
+
|
|
58
|
+
tag1 = (+'').force_encoding(Encoding::BINARY)
|
|
59
|
+
if @mtime
|
|
60
|
+
tag1 << [to_ntfs_time(@mtime)].pack('Q<')
|
|
61
|
+
if @atime
|
|
62
|
+
tag1 << [to_ntfs_time(@atime)].pack('Q<')
|
|
63
|
+
tag1 << [to_ntfs_time(@ctime)].pack('Q<') if @ctime
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
s << [tag1.bytesize].pack('v') << tag1
|
|
67
|
+
s
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def parse_tags(content)
|
|
73
|
+
return {} if content.nil?
|
|
74
|
+
|
|
75
|
+
tags = {}
|
|
76
|
+
i = 0
|
|
77
|
+
while i < content.bytesize
|
|
78
|
+
tag, size = content[i, 4].unpack('vv')
|
|
79
|
+
i += 4
|
|
80
|
+
break unless tag && size
|
|
81
|
+
|
|
82
|
+
value = content[i, size]
|
|
83
|
+
i += size
|
|
84
|
+
tags[tag] = value
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
tags
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def from_ntfs_time(ntfs_time)
|
|
91
|
+
::Ziperu::DOSTime.at((ntfs_time / WINDOWS_TICK) - SEC_TO_UNIX_EPOCH)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def to_ntfs_time(time)
|
|
95
|
+
((time.to_f + SEC_TO_UNIX_EPOCH) * WINDOWS_TICK).to_i
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Ported verbatim from rubyzip (Copyright (C) 2002-2026 the rubyzip authors).
|
|
4
|
+
|
|
5
|
+
module Ziperu
|
|
6
|
+
# Olf Info-ZIP Extra for UNIX uid/gid and file timestampes
|
|
7
|
+
class ExtraField::OldUnix < ExtraField::Generic # :nodoc:
|
|
8
|
+
HEADER_ID = 'UX'
|
|
9
|
+
register_map
|
|
10
|
+
|
|
11
|
+
def initialize(binstr = nil)
|
|
12
|
+
@uid = nil
|
|
13
|
+
@gid = nil
|
|
14
|
+
@atime = nil
|
|
15
|
+
@mtime = nil
|
|
16
|
+
binstr && merge(binstr)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_accessor :uid, :gid, :atime, :mtime
|
|
20
|
+
|
|
21
|
+
def merge(binstr)
|
|
22
|
+
return if binstr.empty?
|
|
23
|
+
|
|
24
|
+
size, content = initial_parse(binstr)
|
|
25
|
+
# size: 0 for central directory. 4 for local header
|
|
26
|
+
return if !size || size == 0
|
|
27
|
+
|
|
28
|
+
atime, mtime, uid, gid = content.unpack('VVvv')
|
|
29
|
+
@uid ||= uid
|
|
30
|
+
@gid ||= gid
|
|
31
|
+
@atime ||= atime
|
|
32
|
+
@mtime ||= mtime # rubocop:disable Naming/MemoizedInstanceVariableName
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def ==(other)
|
|
36
|
+
@uid == other.uid &&
|
|
37
|
+
@gid == other.gid &&
|
|
38
|
+
@atime == other.atime &&
|
|
39
|
+
@mtime == other.mtime
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Fields are nil when only the central directory's (empty) form was
|
|
43
|
+
# seen; they pack as zero rather than failing.
|
|
44
|
+
def pack_for_local
|
|
45
|
+
[@atime, @mtime, @uid, @gid].map(&:to_i).pack('VVvv')
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def pack_for_c_dir
|
|
49
|
+
[@atime, @mtime].map(&:to_i).pack('VV')
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Ported verbatim from rubyzip (Copyright (C) 2002-2026 the rubyzip authors).
|
|
4
|
+
|
|
5
|
+
module Ziperu
|
|
6
|
+
# Info-ZIP Additional timestamp field
|
|
7
|
+
class ExtraField::UniversalTime < ExtraField::Generic # :nodoc:
|
|
8
|
+
HEADER_ID = 'UT'
|
|
9
|
+
register_map
|
|
10
|
+
|
|
11
|
+
ATIME_MASK = 0b010
|
|
12
|
+
CTIME_MASK = 0b100
|
|
13
|
+
MTIME_MASK = 0b001
|
|
14
|
+
|
|
15
|
+
def initialize(binstr = nil)
|
|
16
|
+
@ctime = nil
|
|
17
|
+
@mtime = nil
|
|
18
|
+
@atime = nil
|
|
19
|
+
@flag = 0
|
|
20
|
+
|
|
21
|
+
merge(binstr) unless binstr.nil?
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
attr_reader :atime, :ctime, :mtime, :flag
|
|
25
|
+
|
|
26
|
+
def atime=(time)
|
|
27
|
+
@flag = time.nil? ? @flag & ~ATIME_MASK : @flag | ATIME_MASK
|
|
28
|
+
@atime = time
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def ctime=(time)
|
|
32
|
+
@flag = time.nil? ? @flag & ~CTIME_MASK : @flag | CTIME_MASK
|
|
33
|
+
@ctime = time
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def mtime=(time)
|
|
37
|
+
@flag = time.nil? ? @flag & ~MTIME_MASK : @flag | MTIME_MASK
|
|
38
|
+
@mtime = time
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def merge(binstr)
|
|
42
|
+
return if binstr.empty?
|
|
43
|
+
|
|
44
|
+
size, content = initial_parse(binstr)
|
|
45
|
+
return if !size || size <= 0
|
|
46
|
+
|
|
47
|
+
@flag, *times = content.unpack('Cl<l<l<')
|
|
48
|
+
|
|
49
|
+
# Parse the timestamps, in order, based on which flags are set.
|
|
50
|
+
return if times[0].nil?
|
|
51
|
+
|
|
52
|
+
@mtime ||= ::Ziperu::DOSTime.at(times.shift) unless @flag & MTIME_MASK == 0
|
|
53
|
+
return if times[0].nil?
|
|
54
|
+
|
|
55
|
+
@atime ||= ::Ziperu::DOSTime.at(times.shift) unless @flag & ATIME_MASK == 0
|
|
56
|
+
return if times[0].nil?
|
|
57
|
+
|
|
58
|
+
@ctime ||= ::Ziperu::DOSTime.at(times.shift) unless @flag & CTIME_MASK == 0
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def ==(other)
|
|
62
|
+
@mtime == other.mtime &&
|
|
63
|
+
@atime == other.atime &&
|
|
64
|
+
@ctime == other.ctime
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def pack_for_local
|
|
68
|
+
s = [@flag].pack('C')
|
|
69
|
+
s << [@mtime.to_i].pack('l<') unless @flag & MTIME_MASK == 0
|
|
70
|
+
s << [@atime.to_i].pack('l<') unless @flag & ATIME_MASK == 0
|
|
71
|
+
s << [@ctime.to_i].pack('l<') unless @flag & CTIME_MASK == 0
|
|
72
|
+
s
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def pack_for_c_dir
|
|
76
|
+
s = [@flag].pack('C')
|
|
77
|
+
s << [@mtime.to_i].pack('l<') unless @flag & MTIME_MASK == 0
|
|
78
|
+
s
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Ported verbatim from rubyzip (Copyright (C) 2002-2026 the rubyzip authors).
|
|
4
|
+
|
|
5
|
+
module Ziperu
|
|
6
|
+
# Info-ZIP Extra for UNIX uid/gid
|
|
7
|
+
class ExtraField::IUnix < ExtraField::Generic # :nodoc:
|
|
8
|
+
HEADER_ID = 'Ux'
|
|
9
|
+
register_map
|
|
10
|
+
|
|
11
|
+
def initialize(binstr = nil)
|
|
12
|
+
@uid = nil
|
|
13
|
+
@gid = nil
|
|
14
|
+
binstr && merge(binstr)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_accessor :uid, :gid
|
|
18
|
+
|
|
19
|
+
def merge(binstr)
|
|
20
|
+
return if binstr.empty?
|
|
21
|
+
|
|
22
|
+
size, content = initial_parse(binstr)
|
|
23
|
+
# size: 0 for central directory. 4 for local header
|
|
24
|
+
return if !size || size == 0
|
|
25
|
+
|
|
26
|
+
uid, gid = content.unpack('vv')
|
|
27
|
+
@uid = uid
|
|
28
|
+
@gid = gid
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def ==(other)
|
|
32
|
+
@uid == other.uid && @gid == other.gid
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Nil when only the central directory's (empty) form was seen; packed
|
|
36
|
+
# as zero rather than failing.
|
|
37
|
+
def pack_for_local
|
|
38
|
+
[@uid, @gid].map(&:to_i).pack('vv')
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def pack_for_c_dir
|
|
42
|
+
''
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Ported verbatim from rubyzip (Copyright (C) 2002-2026 the rubyzip authors).
|
|
4
|
+
|
|
5
|
+
require_relative 'generic'
|
|
6
|
+
|
|
7
|
+
module Ziperu
|
|
8
|
+
# A class to hold unknown extra fields so that they are preserved.
|
|
9
|
+
class ExtraField::Unknown < ExtraField::Generic # :nodoc:
|
|
10
|
+
def initialize
|
|
11
|
+
@local_bin = +''
|
|
12
|
+
@cdir_bin = +''
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def merge(binstr, local: false)
|
|
16
|
+
return if binstr.empty?
|
|
17
|
+
|
|
18
|
+
if local
|
|
19
|
+
@local_bin << binstr
|
|
20
|
+
else
|
|
21
|
+
@cdir_bin << binstr
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_local_bin
|
|
26
|
+
@local_bin
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def to_c_dir_bin
|
|
30
|
+
@cdir_bin
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def ==(other)
|
|
34
|
+
@local_bin == other.to_local_bin && @cdir_bin == other.to_c_dir_bin
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|