Module: Perfgate::Storage::Archive

Defined in:
lib/perfgate/storage/archive.rb

Overview

Packs/unpacks the portable baseline-run-.tar.gz archive format (spec 18.2), used to move a run bundle outside of a Filesystem adapter's own root -- e.g. as a manually-downloaded CI artifact. Extraction rejects any entry that would escape the destination directory (spec 22: reject path traversal).

Class Method Summary collapse

Class Method Details

.add_entry(tar, dir, run_id, entry) ⇒ Object



33
34
35
36
37
38
39
40
41
42
43
# File 'lib/perfgate/storage/archive.rb', line 33

def add_entry(tar, dir, run_id, entry)
  return if [".", ".."].include?(File.basename(entry))

  source = File.join(dir, entry)
  name = File.join(run_id, entry)
  if File.directory?(source)
    tar.mkdir(name, 0o755)
  else
    tar.add_file(name, 0o644) { |io| io.write(File.read(source)) }
  end
end

.extract(archive_path, into) ⇒ Object



27
28
29
30
31
# File 'lib/perfgate/storage/archive.rb', line 27

def extract(archive_path, into)
  Zlib::GzipReader.open(archive_path) do |gz|
    Gem::Package::TarReader.new(gz) { |tar| extract_entries(tar, into) }
  end
end

.extract_entries(tar, into) ⇒ Object



45
46
47
48
49
50
# File 'lib/perfgate/storage/archive.rb', line 45

def extract_entries(tar, into)
  tar.each do |entry|
    destination = safe_destination(into, entry.full_name)
    entry.directory? ? FileUtils.mkdir_p(destination) : extract_file(entry, destination)
  end
end

.extract_file(entry, destination) ⇒ Object



52
53
54
55
# File 'lib/perfgate/storage/archive.rb', line 52

def extract_file(entry, destination)
  FileUtils.mkdir_p(File.dirname(destination))
  File.write(destination, entry.read)
end

.safe_destination(into, entry_name) ⇒ Object



57
58
59
60
61
62
63
64
65
66
# File 'lib/perfgate/storage/archive.rb', line 57

def safe_destination(into, entry_name)
  base = File.join(into, "runs")
  destination = File.expand_path(File.join(base, entry_name))

  unless destination.start_with?("#{File.expand_path(base)}/")
    raise Perfgate::ResultBundleError, "archive entry #{entry_name.inspect} escapes the destination directory"
  end

  destination
end

.write(archive_path, dir, run_id) ⇒ Object



18
19
20
21
22
23
24
25
# File 'lib/perfgate/storage/archive.rb', line 18

def write(archive_path, dir, run_id)
  tar_io = StringIO.new
  Gem::Package::TarWriter.new(tar_io) do |tar|
    Dir.glob("**/*", File::FNM_DOTMATCH, base: dir).each { |entry| add_entry(tar, dir, run_id, entry) }
  end

  Zlib::GzipWriter.open(archive_path) { |gz| gz.write(tar_io.string) }
end