zip-merge 7.0.0 → 7.1.3
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
- checksums.yaml.gz.sig +0 -0
- data/LICENSE.md +13 -0
- data/README.md +299 -0
- data/lib/zip/merge/backend.rb +48 -0
- data/lib/zip/merge/provider.rb +110 -0
- data/lib/zip/merge/version.rb +5 -3
- data/lib/zip/merge.rb +162 -78
- data/lib/zip-merge.rb +7 -1
- data/sig/zip/merge.rbs +21 -0
- data.tar.gz.sig +0 -0
- metadata +265 -13
- metadata.gz.sig +0 -0
data/lib/zip/merge.rb
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require_relative
|
|
3
|
+
require 'stringio'
|
|
4
|
+
require 'zlib'
|
|
5
|
+
require 'tree_haver'
|
|
6
|
+
require_relative 'merge/version'
|
|
7
7
|
|
|
8
8
|
module Zip
|
|
9
9
|
module Merge
|
|
10
10
|
LOCAL = 0x04034b50
|
|
11
11
|
CENTRAL = 0x02014b50
|
|
12
12
|
EOCD = 0x06054b50
|
|
13
|
-
DOS_EPOCH = [0, 0, 0x21, 0].pack(
|
|
13
|
+
DOS_EPOCH = [0, 0, 0x21, 0].pack('C*')
|
|
14
14
|
|
|
15
15
|
RenderError = Class.new(StandardError) do
|
|
16
16
|
attr_reader :diagnostic
|
|
@@ -32,19 +32,21 @@ module Zip
|
|
|
32
32
|
TreeHaver::ZipArchiveEntry.new(
|
|
33
33
|
path: name,
|
|
34
34
|
normalized_path: normalize_zip_path(name),
|
|
35
|
-
directory: name.end_with?(
|
|
35
|
+
directory: name.end_with?('/'),
|
|
36
36
|
compression: compression_name(record[:method]),
|
|
37
37
|
compressed_size: record[:compressed_size],
|
|
38
38
|
uncompressed_size: record[:uncompressed_size],
|
|
39
|
-
crc32:
|
|
39
|
+
crc32: '%08x' % record[:crc32],
|
|
40
40
|
local_header_range: TreeHaver::ByteRange.new(start_byte: record[:local_offset], end_byte: local[:data_start]),
|
|
41
|
-
data_range: TreeHaver::ByteRange.new(start_byte: local[:data_start],
|
|
41
|
+
data_range: TreeHaver::ByteRange.new(start_byte: local[:data_start],
|
|
42
|
+
end_byte: local[:data_start] + record[:compressed_size]),
|
|
42
43
|
central_directory_range: record[:range]
|
|
43
44
|
)
|
|
44
45
|
end.sort_by { |entry| entry.local_header_range.start_byte }
|
|
45
46
|
|
|
46
47
|
TreeHaver::ZipFamilyReport.new(
|
|
47
|
-
archive: TreeHaver::ZipArchiveInfo.new(format:
|
|
48
|
+
archive: TreeHaver::ZipArchiveInfo.new(format: 'zip', schema: 'zip.ksy', entry_count: entries.length,
|
|
49
|
+
central_directory_range: central[:range]),
|
|
48
50
|
entries: entries,
|
|
49
51
|
member_decisions: [],
|
|
50
52
|
unsafe_entries: unsafe_entries(entries, central[:records]),
|
|
@@ -71,59 +73,70 @@ module Zip
|
|
|
71
73
|
incoming_entry = incoming_entries[path]
|
|
72
74
|
if unsafe_by_path[path]
|
|
73
75
|
unsafe = unsafe_by_path[path]
|
|
74
|
-
report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation:
|
|
76
|
+
report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation: 'reject',
|
|
77
|
+
disposition: 'unsafe', reason: unsafe.reason)
|
|
75
78
|
report.merge_report.diagnostics << diagnostic(unsafe.category, schema_path(path), unsafe.reason)
|
|
76
79
|
elsif current_entry.nil? && incoming_entry
|
|
77
|
-
decision(report, path,
|
|
80
|
+
decision(report, path, 'add', 'requires_renderer', 'member exists only in incoming archive')
|
|
78
81
|
elsif current_entry && incoming_entry.nil?
|
|
79
|
-
decision(report, path,
|
|
80
|
-
elsif ancestor_entry && same_entry?(current_entry,
|
|
81
|
-
|
|
82
|
+
decision(report, path, 'delete', 'requires_renderer', 'member was removed from incoming archive')
|
|
83
|
+
elsif ancestor_entry && same_entry?(current_entry,
|
|
84
|
+
ancestor_entry) && same_entry?(incoming_entry, ancestor_entry)
|
|
85
|
+
report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation: 'preserve',
|
|
86
|
+
disposition: 'safe', reason: 'member is unchanged from ancestor')
|
|
82
87
|
report.merge_report.preserved_ranges.concat([current_entry.local_header_range, current_entry.data_range])
|
|
83
88
|
elsif (family = nested_family(path))
|
|
84
|
-
report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation:
|
|
85
|
-
|
|
89
|
+
report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation: 'delegate',
|
|
90
|
+
disposition: 'requires_renderer', nested_family: family, reason: 'structured member can be merged by a nested family before ZIP rendering')
|
|
91
|
+
report.merge_report.nested_dispatches << TreeHaver::BinaryNestedDispatch.new(
|
|
92
|
+
schema_path: "#{schema_path(path)}/data", family: family, status: 'planned'
|
|
93
|
+
)
|
|
86
94
|
report.merge_report.rewritten_nodes << schema_path(path)
|
|
87
95
|
report.merge_report.checksum_updates << "#{schema_path(path)}/crc32"
|
|
88
96
|
else
|
|
89
|
-
decision(report, path,
|
|
97
|
+
decision(report, path, 'rewrite', 'requires_renderer', 'member bytes or metadata changed')
|
|
90
98
|
report.merge_report.checksum_updates << "#{schema_path(path)}/crc32"
|
|
91
99
|
end
|
|
92
100
|
report.merge_report.matched_schema_paths << schema_path(path)
|
|
93
101
|
end
|
|
94
102
|
unless report.merge_report.rewritten_nodes.empty? && report.merge_report.checksum_updates.empty?
|
|
95
|
-
report.merge_report.rewritten_nodes <<
|
|
96
|
-
report.merge_report.checksum_updates.concat([
|
|
103
|
+
report.merge_report.rewritten_nodes << '/central_directory'
|
|
104
|
+
report.merge_report.checksum_updates.concat(['/central_directory/size', '/central_directory/offset'])
|
|
97
105
|
end
|
|
98
106
|
report
|
|
99
107
|
end
|
|
100
108
|
|
|
101
109
|
def render_with_raw_preservation(source:, plan:, member_bytes: {}, compression: 0)
|
|
102
|
-
|
|
110
|
+
unless [
|
|
111
|
+
0, 8
|
|
112
|
+
].include?(compression)
|
|
113
|
+
raise render_error('unsupported_compression', '/render/options/compression',
|
|
114
|
+
'unsupported raw-preserving compression method')
|
|
115
|
+
end
|
|
103
116
|
|
|
104
117
|
source = source.b
|
|
105
118
|
source_inventory = parse_zip_inventory(source)
|
|
106
119
|
central = scan_central_directory(source)
|
|
107
120
|
source_entries = entries_by_path(source_inventory.entries)
|
|
108
121
|
raw_ranges = raw_local_record_ranges(source, source_entries)
|
|
109
|
-
output = +
|
|
122
|
+
output = +''.b
|
|
110
123
|
central_records = []
|
|
111
124
|
entries = entries_by_path(plan.entries)
|
|
112
125
|
plan.member_decisions.each do |member|
|
|
113
126
|
entry = entries[member.normalized_path]
|
|
114
127
|
case member.operation
|
|
115
|
-
when
|
|
116
|
-
raise render_error(
|
|
117
|
-
when
|
|
128
|
+
when 'reject'
|
|
129
|
+
raise render_error('rejected_member', schema_path(member.normalized_path), member.reason)
|
|
130
|
+
when 'delete'
|
|
118
131
|
next
|
|
119
|
-
when
|
|
132
|
+
when 'preserve'
|
|
120
133
|
source_entry = source_entries.fetch(member.normalized_path)
|
|
121
134
|
validate_raw_preserve_entry!(source, central, source_entry)
|
|
122
135
|
range = raw_ranges.fetch(member.normalized_path)
|
|
123
136
|
offset = output.bytesize
|
|
124
137
|
output << source.byteslice(range.start_byte...range.end_byte)
|
|
125
138
|
central_records << central_record_from_entry(source_entry, offset)
|
|
126
|
-
when
|
|
139
|
+
when 'add', 'rewrite', 'delegate'
|
|
127
140
|
content = member_bytes.fetch(member.normalized_path)
|
|
128
141
|
rendered, record = rendered_local_record(entry, content.b, compression, output.bytesize)
|
|
129
142
|
output << rendered
|
|
@@ -138,12 +151,14 @@ module Zip
|
|
|
138
151
|
output << eocd_record(central_records.length, central_size, central_start)
|
|
139
152
|
report = parse_zip_inventory(output)
|
|
140
153
|
merge_report = plan.merge_report
|
|
141
|
-
merge_report.preserved_ranges = plan.member_decisions.filter_map
|
|
154
|
+
merge_report.preserved_ranges = plan.member_decisions.filter_map do |member|
|
|
155
|
+
raw_ranges[member.normalized_path] if member.operation == 'preserve'
|
|
156
|
+
end
|
|
142
157
|
[output, report, merge_report]
|
|
143
158
|
end
|
|
144
159
|
|
|
145
160
|
def new_stored_zip(entries)
|
|
146
|
-
output = +
|
|
161
|
+
output = +''.b
|
|
147
162
|
central = []
|
|
148
163
|
entries.keys.sort.each do |name|
|
|
149
164
|
rendered, record = rendered_local_record(path_entry(name, entries[name]), entries[name].b, 0, output.bytesize)
|
|
@@ -157,34 +172,38 @@ module Zip
|
|
|
157
172
|
end
|
|
158
173
|
|
|
159
174
|
def empty_report
|
|
160
|
-
|
|
175
|
+
Binary::Merge.preservation_report(format: 'zip', schema: 'zip.ksy', matched_schema_paths: [],
|
|
176
|
+
preserved_ranges: [])
|
|
161
177
|
end
|
|
162
178
|
|
|
163
179
|
def scan_central_directory(source)
|
|
164
180
|
eocd = source.bytesize - 22
|
|
165
|
-
eocd -= 1 while eocd >= 0 && source.byteslice(eocd, 4).unpack1(
|
|
166
|
-
raise
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
181
|
+
eocd -= 1 while eocd >= 0 && source.byteslice(eocd, 4).unpack1('V') != EOCD
|
|
182
|
+
raise 'missing ZIP end of central directory' if eocd.negative?
|
|
183
|
+
|
|
184
|
+
size = source.byteslice(eocd + 12, 4).unpack1('V')
|
|
185
|
+
offset = source.byteslice(eocd + 16, 4).unpack1('V')
|
|
186
|
+
comment_length = source.byteslice(eocd + 20, 2).unpack1('v')
|
|
170
187
|
records = {}
|
|
171
188
|
cursor = offset
|
|
172
189
|
while cursor < offset + size
|
|
173
|
-
raise
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
190
|
+
raise 'unexpected central directory record' unless source.byteslice(cursor, 4).unpack1('V') == CENTRAL
|
|
191
|
+
|
|
192
|
+
name_len = source.byteslice(cursor + 28, 2).unpack1('v')
|
|
193
|
+
extra_len = source.byteslice(cursor + 30, 2).unpack1('v')
|
|
194
|
+
comment_len = source.byteslice(cursor + 32, 2).unpack1('v')
|
|
177
195
|
name = source.byteslice(cursor + 46, name_len)
|
|
178
196
|
records[name] = {
|
|
179
|
-
range: TreeHaver::ByteRange.new(start_byte: cursor,
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
197
|
+
range: TreeHaver::ByteRange.new(start_byte: cursor,
|
|
198
|
+
end_byte: cursor + 46 + name_len + extra_len + comment_len),
|
|
199
|
+
flags: source.byteslice(cursor + 8, 2).unpack1('v'),
|
|
200
|
+
method: source.byteslice(cursor + 10, 2).unpack1('v'),
|
|
201
|
+
crc32: source.byteslice(cursor + 16, 4).unpack1('V'),
|
|
202
|
+
compressed_size: source.byteslice(cursor + 20, 4).unpack1('V'),
|
|
203
|
+
uncompressed_size: source.byteslice(cursor + 24, 4).unpack1('V'),
|
|
185
204
|
extra_length: extra_len,
|
|
186
205
|
comment_length: comment_len,
|
|
187
|
-
local_offset: source.byteslice(cursor + 42, 4).unpack1(
|
|
206
|
+
local_offset: source.byteslice(cursor + 42, 4).unpack1('V')
|
|
188
207
|
}
|
|
189
208
|
cursor = records[name][:range].end_byte
|
|
190
209
|
end
|
|
@@ -194,32 +213,67 @@ module Zip
|
|
|
194
213
|
def scan_local_headers(source, records)
|
|
195
214
|
records.transform_values do |record|
|
|
196
215
|
cursor = record[:local_offset]
|
|
197
|
-
raise
|
|
198
|
-
|
|
199
|
-
|
|
216
|
+
raise 'unexpected ZIP local header' unless source.byteslice(cursor, 4).unpack1('V') == LOCAL
|
|
217
|
+
|
|
218
|
+
name_len = source.byteslice(cursor + 26, 2).unpack1('v')
|
|
219
|
+
extra_len = source.byteslice(cursor + 28, 2).unpack1('v')
|
|
200
220
|
{ data_start: cursor + 30 + name_len + extra_len, extra_length: extra_len }
|
|
201
221
|
end
|
|
202
222
|
end
|
|
203
223
|
|
|
204
224
|
def validate_raw_preserve_entry!(source, central, entry)
|
|
205
|
-
|
|
225
|
+
if central[:archive_comment]
|
|
226
|
+
raise render_error('archive_comment', '/archive/comment',
|
|
227
|
+
'raw-preserving ZIP renderer does not yet preserve archive comments')
|
|
228
|
+
end
|
|
229
|
+
|
|
206
230
|
record = central[:records].fetch(entry.path)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
231
|
+
unless (record[:flags] & 0x1).zero?
|
|
232
|
+
raise render_error('encrypted_member', schema_path(entry.normalized_path),
|
|
233
|
+
"raw-preserving ZIP renderer rejects encrypted member #{entry.normalized_path}")
|
|
234
|
+
end
|
|
235
|
+
unless [
|
|
236
|
+
0, 8
|
|
237
|
+
].include?(record[:method])
|
|
238
|
+
raise render_error('unsupported_compression', schema_path(entry.normalized_path),
|
|
239
|
+
"raw-preserving ZIP renderer rejects unsupported compression #{entry.compression.inspect}")
|
|
240
|
+
end
|
|
241
|
+
unless record[:extra_length].zero?
|
|
242
|
+
raise render_error('central_directory_extra_field', schema_path(entry.normalized_path),
|
|
243
|
+
"raw-preserving ZIP renderer does not yet preserve central-directory extra fields for #{entry.normalized_path}")
|
|
244
|
+
end
|
|
245
|
+
unless record[:comment_length].zero?
|
|
246
|
+
raise render_error('member_comment', schema_path(entry.normalized_path),
|
|
247
|
+
"raw-preserving ZIP renderer does not yet preserve member comments for #{entry.normalized_path}")
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
local_extra = source.byteslice(entry.local_header_range.start_byte + 28, 2).unpack1('v')
|
|
251
|
+
return if local_extra.zero?
|
|
252
|
+
|
|
253
|
+
raise render_error('local_header_extra_field', schema_path(entry.normalized_path),
|
|
254
|
+
"raw-preserving ZIP renderer does not yet preserve local extra fields for #{entry.normalized_path}")
|
|
213
255
|
end
|
|
214
256
|
|
|
215
257
|
def unsafe_entries(entries, records)
|
|
216
258
|
seen = {}
|
|
217
259
|
entries.flat_map do |entry|
|
|
218
260
|
list = []
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
261
|
+
if escapes_root?(entry.path)
|
|
262
|
+
list << TreeHaver::ZipUnsafeEntry.new(path: entry.path, normalized_path: entry.normalized_path,
|
|
263
|
+
category: 'path_traversal', reason: 'entry escapes the archive root')
|
|
264
|
+
end
|
|
265
|
+
if seen[entry.normalized_path] && seen[entry.normalized_path] != entry.path
|
|
266
|
+
list << TreeHaver::ZipUnsafeEntry.new(path: entry.path, normalized_path: entry.normalized_path,
|
|
267
|
+
category: 'duplicate_normalized_path', reason: 'normalized path collides with an existing entry')
|
|
268
|
+
end
|
|
269
|
+
unless (records[entry.path][:flags] & 0x1).zero?
|
|
270
|
+
list << TreeHaver::ZipUnsafeEntry.new(path: entry.path, normalized_path: entry.normalized_path,
|
|
271
|
+
category: 'encrypted_member', reason: 'encrypted member cannot be rendered by the default provider')
|
|
272
|
+
end
|
|
273
|
+
if signing_sensitive?(entry.normalized_path)
|
|
274
|
+
list << TreeHaver::ZipUnsafeEntry.new(path: entry.path, normalized_path: entry.normalized_path,
|
|
275
|
+
category: 'signing_sensitive_member', reason: 'signature-bearing member mutation is not enabled')
|
|
276
|
+
end
|
|
223
277
|
seen[entry.normalized_path] = entry.path
|
|
224
278
|
list
|
|
225
279
|
end
|
|
@@ -228,57 +282,79 @@ module Zip
|
|
|
228
282
|
def rendered_local_record(entry, content, method, offset)
|
|
229
283
|
payload = method == 8 ? Zlib::Deflate.deflate(content) : content
|
|
230
284
|
crc = Zlib.crc32(content)
|
|
231
|
-
header = [LOCAL, 20, 0,
|
|
232
|
-
|
|
285
|
+
header = [LOCAL, 20, 0,
|
|
286
|
+
method].pack('Vvvv') + DOS_EPOCH + [crc, payload.bytesize, content.bytesize, entry.path.bytesize,
|
|
287
|
+
0].pack('VVVvv') + entry.path
|
|
288
|
+
[header + payload,
|
|
289
|
+
{ name: entry.path, method: method, crc32: crc, compressed_size: payload.bytesize, uncompressed_size: content.bytesize,
|
|
290
|
+
offset: offset, flags: 0 }]
|
|
233
291
|
end
|
|
234
292
|
|
|
235
293
|
def central_directory_record(record)
|
|
236
|
-
[CENTRAL, 20, 20, record[:flags],
|
|
294
|
+
[CENTRAL, 20, 20, record[:flags],
|
|
295
|
+
record[:method]].pack('Vvvvv') + DOS_EPOCH + [record[:crc32], record[:compressed_size], record[:uncompressed_size],
|
|
296
|
+
record[:name].bytesize, 0, 0, 0, 0, 0, record[:offset]].pack('VVVvvvvvVV') + record[:name]
|
|
237
297
|
end
|
|
238
298
|
|
|
239
299
|
def eocd_record(entries, size, offset)
|
|
240
|
-
[EOCD, 0, 0, entries, entries, size, offset, 0].pack(
|
|
300
|
+
[EOCD, 0, 0, entries, entries, size, offset, 0].pack('VvvvvVVv')
|
|
241
301
|
end
|
|
242
302
|
|
|
243
|
-
def raw_local_record_ranges(
|
|
303
|
+
def raw_local_record_ranges(_source, entries)
|
|
244
304
|
ordered = entries.values.sort_by { |entry| entry.local_header_range.start_byte }
|
|
245
305
|
ordered.each_with_index.to_h do |entry, index|
|
|
246
306
|
end_byte = ordered[index + 1]&.local_header_range&.start_byte || entry.central_directory_range.start_byte
|
|
247
|
-
[entry.normalized_path,
|
|
307
|
+
[entry.normalized_path,
|
|
308
|
+
TreeHaver::ByteRange.new(start_byte: entry.local_header_range.start_byte, end_byte: end_byte)]
|
|
248
309
|
end
|
|
249
310
|
end
|
|
250
311
|
|
|
251
312
|
def central_record_from_entry(entry, offset)
|
|
252
|
-
{ name: entry.path, method: entry.compression ==
|
|
313
|
+
{ name: entry.path, method: entry.compression == 'deflate' ? 8 : 0, crc32: entry.crc32.to_i(16),
|
|
314
|
+
compressed_size: entry.compressed_size, uncompressed_size: entry.uncompressed_size, offset: offset, flags: 0 }
|
|
253
315
|
end
|
|
254
316
|
|
|
255
317
|
def path_entry(name, content)
|
|
256
|
-
TreeHaver::ZipArchiveEntry.new(path: name, normalized_path: normalize_zip_path(name),
|
|
318
|
+
TreeHaver::ZipArchiveEntry.new(path: name, normalized_path: normalize_zip_path(name),
|
|
319
|
+
directory: name.end_with?('/'), compression: 'stored', compressed_size: content.bytesize, uncompressed_size: content.bytesize, crc32: '%08x' % Zlib.crc32(content), local_header_range: TreeHaver::ByteRange.new(start_byte: 0, end_byte: 0), data_range: TreeHaver::ByteRange.new(start_byte: 0, end_byte: 0), central_directory_range: TreeHaver::ByteRange.new(start_byte: 0, end_byte: 0))
|
|
257
320
|
end
|
|
258
321
|
|
|
259
322
|
def decision(report, path, operation, disposition, reason)
|
|
260
|
-
report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation: operation,
|
|
323
|
+
report.member_decisions << TreeHaver::ZipMemberDecision.new(normalized_path: path, operation: operation,
|
|
324
|
+
disposition: disposition, reason: reason)
|
|
261
325
|
report.merge_report.rewritten_nodes << schema_path(path)
|
|
262
326
|
end
|
|
263
327
|
|
|
264
328
|
def normalize_zip_path(path)
|
|
265
|
-
path.tr(
|
|
329
|
+
path.tr('\\', '/').split('/').each_with_object([]) do |part, stack|
|
|
330
|
+
unless part == '.'
|
|
331
|
+
part == '..' ? stack.pop : stack << part
|
|
332
|
+
end
|
|
333
|
+
end.join('/')
|
|
266
334
|
end
|
|
267
335
|
|
|
268
336
|
def compression_name(method)
|
|
269
337
|
case method
|
|
270
|
-
when 0 then
|
|
271
|
-
when 8 then
|
|
338
|
+
when 0 then 'stored'
|
|
339
|
+
when 8 then 'deflate'
|
|
272
340
|
else "method-#{method}"
|
|
273
341
|
end
|
|
274
342
|
end
|
|
275
343
|
|
|
276
344
|
def escapes_root?(path)
|
|
277
|
-
path.start_with?(
|
|
345
|
+
path.start_with?('/') || path.tr('\\', '/').split('/').then do |parts|
|
|
346
|
+
depth = 0
|
|
347
|
+
parts.any? do |part|
|
|
348
|
+
part == '..' ? (depth -= 1) : (depth += 1 unless part == '.')
|
|
349
|
+
depth.negative?
|
|
350
|
+
end
|
|
351
|
+
end
|
|
278
352
|
end
|
|
279
353
|
|
|
280
354
|
def signing_sensitive?(path)
|
|
281
|
-
path.upcase.start_with?(
|
|
355
|
+
path.upcase.start_with?('META-INF/') && ['.RSA', '.DSA', '.EC', '.SF'].any? do |suffix|
|
|
356
|
+
path.upcase.end_with?(suffix)
|
|
357
|
+
end
|
|
282
358
|
end
|
|
283
359
|
|
|
284
360
|
def same_entry?(left, right)
|
|
@@ -290,10 +366,11 @@ module Zip
|
|
|
290
366
|
end
|
|
291
367
|
|
|
292
368
|
def nested_family(path)
|
|
293
|
-
return
|
|
294
|
-
return
|
|
295
|
-
return
|
|
296
|
-
|
|
369
|
+
return 'markdown' if path.match?(/\.m(?:d|arkdown)\z/i)
|
|
370
|
+
return 'json' if path.end_with?('.json')
|
|
371
|
+
return 'yaml' if path.match?(/\.ya?ml\z/i)
|
|
372
|
+
|
|
373
|
+
'xml' if path.end_with?('.xml')
|
|
297
374
|
end
|
|
298
375
|
|
|
299
376
|
def schema_path(path)
|
|
@@ -301,7 +378,7 @@ module Zip
|
|
|
301
378
|
end
|
|
302
379
|
|
|
303
380
|
def diagnostic(category, schema_path, message)
|
|
304
|
-
|
|
381
|
+
Binary::Merge.unsafe_diagnostic(schema_path: schema_path, byte_range: nil, message: message, category: category)
|
|
305
382
|
end
|
|
306
383
|
|
|
307
384
|
def render_error(category, schema_path, message)
|
|
@@ -309,3 +386,10 @@ module Zip
|
|
|
309
386
|
end
|
|
310
387
|
end
|
|
311
388
|
end
|
|
389
|
+
|
|
390
|
+
require 'binary/merge'
|
|
391
|
+
require_relative 'merge/backend'
|
|
392
|
+
require_relative 'merge/provider'
|
|
393
|
+
|
|
394
|
+
Zip::Merge.register_backend!
|
|
395
|
+
Zip::Merge.register_provider!
|
data/lib/zip-merge.rb
CHANGED
data/sig/zip/merge.rbs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Zip
|
|
2
|
+
module Merge
|
|
3
|
+
class Provider < Binary::Merge::Provider
|
|
4
|
+
def provider_id: () -> String
|
|
5
|
+
def family: () -> String
|
|
6
|
+
def capabilities: () -> Hash[Symbol, untyped]
|
|
7
|
+
def analyze: (Hash[Symbol, untyped] request) -> Hash[Symbol, untyped]
|
|
8
|
+
def diff2: (Hash[Symbol, untyped] request) -> Hash[Symbol, untyped]
|
|
9
|
+
def merge2: (Hash[Symbol, untyped] request) -> Hash[Symbol, untyped]
|
|
10
|
+
def merge3: (Hash[Symbol, untyped] request) -> Hash[Symbol, untyped]
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.merge_provider: () -> Provider
|
|
14
|
+
def self.register_provider!: () -> Hash[Symbol, untyped]
|
|
15
|
+
|
|
16
|
+
module Version
|
|
17
|
+
VERSION: String
|
|
18
|
+
end
|
|
19
|
+
VERSION: String
|
|
20
|
+
end
|
|
21
|
+
end
|
data.tar.gz.sig
CHANGED
|
Binary file
|