1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
# frozen_string_literal: true
require 'zlib'
require 'json'
module Gitlab
module Ci
module Build
module Artifacts
class Metadata
ParserError = Class.new(StandardError)
InvalidStreamError = Class.new(StandardError)
VERSION_PATTERN = /^[\w\s]+(\d+\.\d+\.\d+)/.freeze
INVALID_PATH_PATTERN = %r{(^\.?\.?/)|(/\.?\.?/)}.freeze
attr_reader :stream, :path, :full_version
def initialize(stream, path, **opts)
@stream = stream
@path = path
@opts = opts
@full_version = read_version
end
def version
@full_version.match(VERSION_PATTERN)[1]
end
def errors
gzip do |gz|
read_string(gz) # version
errors = read_string(gz)
raise ParserError, 'Errors field not found!' unless errors
begin
Gitlab::Json.parse(errors)
rescue JSON::ParserError
raise ParserError, 'Invalid errors field!'
end
end
end
def find_entries!
gzip do |gz|
2.times { read_string(gz) } # version and errors fields
match_entries(gz)
end
end
def to_entry
entries = find_entries!
Entry.new(@path, entries)
end
private
def match_entries(gz)
entries = {}
child_pattern = '[^/]*/?$' unless @opts[:recursive]
match_pattern = /^#{Regexp.escape(@path)}#{child_pattern}/
until gz.eof?
begin
path = read_string(gz)&.force_encoding('UTF-8')
meta = read_string(gz)&.force_encoding('UTF-8')
# We might hit an EOF while reading either value, so we should
# abort if we don't get any data.
next unless path && meta
next unless path.valid_encoding? && meta.valid_encoding?
next unless match_pattern.match?(path)
next if INVALID_PATH_PATTERN.match?(path)
entries[path] = Gitlab::Json.parse(meta, symbolize_names: true)
rescue JSON::ParserError, Encoding::CompatibilityError
next
end
end
entries
end
def read_version
gzip do |gz|
version_string = read_string(gz)
unless version_string
raise ParserError, 'Artifacts metadata file empty!'
end
unless VERSION_PATTERN.match?(version_string)
raise ParserError, 'Invalid version!'
end
version_string.chomp
end
end
def read_uint32(gz)
binary = gz.read(4)
binary.unpack1('L>') if binary
end
def read_string(gz)
string_size = read_uint32(gz)
return unless string_size
gz.read(string_size)
end
def gzip(&block)
raise InvalidStreamError, "Invalid stream" unless @stream
# restart gzip reading
@stream.seek(0)
gz = Zlib::GzipReader.new(@stream)
yield(gz)
rescue Zlib::Error => e
raise InvalidStreamError, e.message
ensure
gz&.finish
end
end
end
end
end
end
|