2018-10-26 00:12:43 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-01-04 07:08:49 -05:00
|
|
|
require 'zlib'
|
|
|
|
require 'json'
|
|
|
|
|
|
|
|
module Gitlab
|
|
|
|
module Ci
|
|
|
|
module Build
|
|
|
|
module Artifacts
|
|
|
|
class Metadata
|
2017-03-01 06:00:37 -05:00
|
|
|
ParserError = Class.new(StandardError)
|
2018-07-09 07:34:18 -04:00
|
|
|
InvalidStreamError = Class.new(StandardError)
|
2016-01-14 06:12:05 -05:00
|
|
|
|
2016-01-13 16:31:27 -05:00
|
|
|
VERSION_PATTERN = /^[\w\s]+(\d+\.\d+\.\d+)/
|
|
|
|
INVALID_PATH_PATTERN = %r{(^\.?\.?/)|(/\.?\.?/)}
|
|
|
|
|
2018-07-09 07:34:18 -04:00
|
|
|
attr_reader :stream, :path, :full_version
|
2016-01-04 07:08:49 -05:00
|
|
|
|
2018-07-09 07:34:18 -04:00
|
|
|
def initialize(stream, path, **opts)
|
|
|
|
@stream, @path, @opts = stream, path, opts
|
2016-01-09 08:41:43 -05:00
|
|
|
@full_version = read_version
|
2016-01-08 06:35:49 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def version
|
2016-01-13 16:31:27 -05:00
|
|
|
@full_version.match(VERSION_PATTERN)[1]
|
2016-01-08 06:35:49 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def errors
|
2016-01-13 04:13:39 -05:00
|
|
|
gzip do |gz|
|
2016-01-08 06:35:49 -05:00
|
|
|
read_string(gz) # version
|
2016-01-09 08:41:43 -05:00
|
|
|
errors = read_string(gz)
|
2016-01-14 06:12:05 -05:00
|
|
|
raise ParserError, 'Errors field not found!' unless errors
|
|
|
|
|
|
|
|
begin
|
|
|
|
JSON.parse(errors)
|
|
|
|
rescue JSON::ParserError
|
|
|
|
raise ParserError, 'Invalid errors field!'
|
|
|
|
end
|
2016-01-08 06:35:49 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-01-13 16:31:27 -05:00
|
|
|
def find_entries!
|
2016-01-08 06:35:49 -05:00
|
|
|
gzip do |gz|
|
2016-01-09 08:41:43 -05:00
|
|
|
2.times { read_string(gz) } # version and errors fields
|
|
|
|
match_entries(gz)
|
2016-01-04 09:07:49 -05:00
|
|
|
end
|
2016-01-04 07:08:49 -05:00
|
|
|
end
|
|
|
|
|
2016-01-13 15:17:28 -05:00
|
|
|
def to_entry
|
2016-01-13 17:24:28 -05:00
|
|
|
entries = find_entries!
|
|
|
|
Entry.new(@path, entries)
|
2016-01-04 07:08:49 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
2016-01-09 08:41:43 -05:00
|
|
|
def match_entries(gz)
|
2016-01-13 17:24:28 -05:00
|
|
|
entries = {}
|
2016-01-20 15:48:22 -05:00
|
|
|
|
|
|
|
child_pattern = '[^/]*/?$' unless @opts[:recursive]
|
|
|
|
match_pattern = /^#{Regexp.escape(@path)}#{child_pattern}/
|
2016-01-09 08:41:43 -05:00
|
|
|
|
2016-05-30 05:37:14 -04:00
|
|
|
until gz.eof?
|
2016-01-08 06:35:49 -05:00
|
|
|
begin
|
2018-10-18 20:01:55 -04:00
|
|
|
path = read_string(gz)&.force_encoding('UTF-8')
|
|
|
|
meta = read_string(gz)&.force_encoding('UTF-8')
|
2017-08-15 13:44:37 -04:00
|
|
|
|
2018-10-18 20:01:55 -04:00
|
|
|
# We might hit an EOF while reading either value, so we should
|
|
|
|
# abort if we don't get any data.
|
|
|
|
next unless path && meta
|
2016-01-13 07:45:28 -05:00
|
|
|
next unless path.valid_encoding? && meta.valid_encoding?
|
2016-01-11 03:57:03 -05:00
|
|
|
next unless path =~ match_pattern
|
2016-01-13 16:31:27 -05:00
|
|
|
next if path =~ INVALID_PATH_PATTERN
|
2016-01-09 08:41:43 -05:00
|
|
|
|
2016-01-14 06:12:05 -05:00
|
|
|
entries[path] = JSON.parse(meta, symbolize_names: true)
|
2016-01-12 04:04:26 -05:00
|
|
|
rescue JSON::ParserError, Encoding::CompatibilityError
|
2016-01-08 06:35:49 -05:00
|
|
|
next
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-01-13 17:24:28 -05:00
|
|
|
entries
|
2016-01-08 06:35:49 -05:00
|
|
|
end
|
|
|
|
|
2016-01-09 08:41:43 -05:00
|
|
|
def read_version
|
2016-01-12 05:02:15 -05:00
|
|
|
gzip do |gz|
|
2016-01-09 08:41:43 -05:00
|
|
|
version_string = read_string(gz)
|
|
|
|
|
|
|
|
unless version_string
|
2016-01-14 06:12:05 -05:00
|
|
|
raise ParserError, 'Artifacts metadata file empty!'
|
2016-01-09 08:41:43 -05:00
|
|
|
end
|
|
|
|
|
2016-01-13 16:31:27 -05:00
|
|
|
unless version_string =~ VERSION_PATTERN
|
2016-01-14 06:12:05 -05:00
|
|
|
raise ParserError, 'Invalid version!'
|
2016-01-09 08:41:43 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
version_string.chomp
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def read_uint32(gz)
|
2016-01-08 06:35:49 -05:00
|
|
|
binary = gz.read(4)
|
|
|
|
binary.unpack('L>')[0] if binary
|
|
|
|
end
|
|
|
|
|
|
|
|
def read_string(gz)
|
2016-01-09 08:41:43 -05:00
|
|
|
string_size = read_uint32(gz)
|
2016-01-13 04:13:39 -05:00
|
|
|
return nil unless string_size
|
2017-11-14 04:02:39 -05:00
|
|
|
|
2016-01-09 08:41:43 -05:00
|
|
|
gz.read(string_size)
|
2016-01-08 06:35:49 -05:00
|
|
|
end
|
|
|
|
|
2016-01-13 16:31:27 -05:00
|
|
|
def gzip(&block)
|
2018-07-09 07:34:18 -04:00
|
|
|
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
|
2016-01-04 07:08:49 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|