Merge pull request #16416 from Bo98/safe-filename

Add consistent path validation
This commit is contained in:
Mike McQuaid 2024-01-01 18:48:28 +00:00 committed by GitHub
commit 65bf26fb27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 45 additions and 24 deletions

View File

@ -365,9 +365,9 @@ module Homebrew
end || 0 end || 0
end end
filename = Bottle::Filename.create(formula, bottle_tag.to_sym, rebuild) filename = Bottle::Filename.create(formula, bottle_tag, rebuild)
local_filename = filename.to_s local_filename = filename.to_s
bottle_path = Pathname.pwd/filename bottle_path = Pathname.pwd/local_filename
tab = nil tab = nil
keg = nil keg = nil
@ -690,8 +690,8 @@ module Homebrew
bottle_hash["bottle"]["tags"].each do |tag, tag_hash| bottle_hash["bottle"]["tags"].each do |tag, tag_hash|
filename = Bottle::Filename.new( filename = Bottle::Filename.new(
formula_name, formula_name,
bottle_hash["formula"]["pkg_version"], PkgVersion.parse(bottle_hash["formula"]["pkg_version"]),
tag, Utils::Bottles::Tag.from_symbol(tag.to_sym),
bottle_hash["bottle"]["rebuild"], bottle_hash["bottle"]["rebuild"],
) )
@ -700,8 +700,8 @@ module Homebrew
all_filename = Bottle::Filename.new( all_filename = Bottle::Filename.new(
formula_name, formula_name,
bottle_hash["formula"]["pkg_version"], PkgVersion.parse(bottle_hash["formula"]["pkg_version"]),
"all", Utils::Bottles::Tag.from_symbol(:all),
bottle_hash["bottle"]["rebuild"], bottle_hash["bottle"]["rebuild"],
) )

View File

@ -197,7 +197,7 @@ class VCSDownloadStrategy < AbstractDownloadStrategy
super super
@ref_type, @ref = extract_ref(meta) @ref_type, @ref = extract_ref(meta)
@revision = meta[:revision] @revision = meta[:revision]
@cached_location = @cache/"#{name}--#{cache_tag}" @cached_location = @cache/Utils.safe_filename("#{name}--#{cache_tag}")
end end
# Download and cache the repository at {#cached_location}. # Download and cache the repository at {#cached_location}.
@ -296,7 +296,7 @@ class AbstractFileDownloadStrategy < AbstractDownloadStrategy
return @symlink_location if defined?(@symlink_location) return @symlink_location if defined?(@symlink_location)
ext = Pathname(parse_basename(url)).extname ext = Pathname(parse_basename(url)).extname
@symlink_location = @cache/"#{name}--#{version}#{ext}" @symlink_location = @cache/Utils.safe_filename("#{name}--#{version}#{ext}")
end end
# Path for storing the completed download. # Path for storing the completed download.
@ -312,7 +312,7 @@ class AbstractFileDownloadStrategy < AbstractDownloadStrategy
@cached_location = if downloads.count == 1 @cached_location = if downloads.count == 1
downloads.first downloads.first
else else
HOMEBREW_CACHE/"downloads/#{url_sha256}--#{resolved_basename}" HOMEBREW_CACHE/"downloads/#{url_sha256}--#{Utils.safe_filename(resolved_basename)}"
end end
end end

View File

@ -308,13 +308,15 @@ class Formula
end end
def validate_attributes! def validate_attributes!
raise FormulaValidationError.new(full_name, :name, name) if name.blank? || name.match?(/\s/) if name.blank? || name.match?(/\s/) || !Utils.safe_filename?(name)
raise FormulaValidationError.new(full_name, :name, name)
end
url = active_spec.url url = active_spec.url
raise FormulaValidationError.new(full_name, :url, url) if url.blank? || url.match?(/\s/) raise FormulaValidationError.new(full_name, :url, url) if url.blank? || url.match?(/\s/)
val = version.respond_to?(:to_str) ? version.to_str : version val = version.respond_to?(:to_str) ? version.to_str : version
return if val.present? && !val.match?(/\s/) return if val.present? && !val.match?(/\s/) && Utils.safe_filename?(val)
raise FormulaValidationError.new(full_name, :version, val) raise FormulaValidationError.new(full_name, :version, val)
end end

View File

@ -295,12 +295,18 @@ class Bottle
class Filename class Filename
attr_reader :name, :version, :tag, :rebuild attr_reader :name, :version, :tag, :rebuild
sig { params(formula: Formula, tag: Utils::Bottles::Tag, rebuild: Integer).returns(T.attached_class) }
def self.create(formula, tag, rebuild) def self.create(formula, tag, rebuild)
new(formula.name, formula.pkg_version, tag, rebuild) new(formula.name, formula.pkg_version, tag, rebuild)
end end
sig { params(name: String, version: PkgVersion, tag: Utils::Bottles::Tag, rebuild: Integer).void }
def initialize(name, version, tag, rebuild) def initialize(name, version, tag, rebuild)
@name = File.basename name @name = File.basename name
raise ArgumentError, "Invalid bottle name" unless Utils.safe_filename?(@name)
raise ArgumentError, "Invalid bottle version" unless Utils.safe_filename?(version.to_s)
@version = version @version = version
@tag = tag.to_s @tag = tag.to_s
@rebuild = rebuild @rebuild = rebuild

View File

@ -7,47 +7,47 @@ describe Bottle::Filename do
subject { described_class.new(name, version, tag, rebuild) } subject { described_class.new(name, version, tag, rebuild) }
let(:name) { "user/repo/foo" } let(:name) { "user/repo/foo" }
let(:version) { "1.0" } let(:version) { PkgVersion.new(Version.new("1.0"), 0) }
let(:tag) { :tag } let(:tag) { Utils::Bottles::Tag.from_symbol(:x86_64_linux) }
let(:rebuild) { 0 } let(:rebuild) { 0 }
describe "#extname" do describe "#extname" do
its(:extname) { is_expected.to eq ".tag.bottle.tar.gz" } its(:extname) { is_expected.to eq ".x86_64_linux.bottle.tar.gz" }
context "when rebuild is 0" do context "when rebuild is 0" do
its(:extname) { is_expected.to eq ".tag.bottle.tar.gz" } its(:extname) { is_expected.to eq ".x86_64_linux.bottle.tar.gz" }
end end
context "when rebuild is 1" do context "when rebuild is 1" do
let(:rebuild) { 1 } let(:rebuild) { 1 }
its(:extname) { is_expected.to eq ".tag.bottle.1.tar.gz" } its(:extname) { is_expected.to eq ".x86_64_linux.bottle.1.tar.gz" }
end end
end end
describe "#to_s and #to_str" do describe "#to_s and #to_str" do
its(:to_s) { is_expected.to eq "foo--1.0.tag.bottle.tar.gz" } its(:to_s) { is_expected.to eq "foo--1.0.x86_64_linux.bottle.tar.gz" }
its(:to_str) { is_expected.to eq "foo--1.0.tag.bottle.tar.gz" } its(:to_str) { is_expected.to eq "foo--1.0.x86_64_linux.bottle.tar.gz" }
end end
describe "#url_encode" do describe "#url_encode" do
its(:url_encode) { is_expected.to eq "foo-1.0.tag.bottle.tar.gz" } its(:url_encode) { is_expected.to eq "foo-1.0.x86_64_linux.bottle.tar.gz" }
end end
describe "#github_packages" do describe "#github_packages" do
its(:github_packages) { is_expected.to eq "foo--1.0.tag.bottle.tar.gz" } its(:github_packages) { is_expected.to eq "foo--1.0.x86_64_linux.bottle.tar.gz" }
end end
describe "#json" do describe "#json" do
its(:json) { is_expected.to eq "foo--1.0.tag.bottle.json" } its(:json) { is_expected.to eq "foo--1.0.x86_64_linux.bottle.json" }
context "when rebuild is 1" do context "when rebuild is 1" do
its(:json) { is_expected.to eq "foo--1.0.tag.bottle.json" } its(:json) { is_expected.to eq "foo--1.0.x86_64_linux.bottle.json" }
end end
end end
describe "::create" do describe "::create" do
subject { described_class.create(f, :tag, 0) } subject { described_class.create(f, tag, rebuild) }
let(:f) do let(:f) do
formula do formula do
@ -56,6 +56,6 @@ describe Bottle::Filename do
end end
end end
its(:to_s) { is_expected.to eq "formula_name--1.0.tag.bottle.tar.gz" } its(:to_s) { is_expected.to eq "formula_name--1.0.x86_64_linux.bottle.tar.gz" }
end end
end end

View File

@ -168,4 +168,17 @@ module Utils
word.downcase! word.downcase!
word word
end end
SAFE_FILENAME_REGEX = /[[:cntrl:]#{Regexp.escape("#{File::SEPARATOR}#{File::ALT_SEPARATOR}")}]/o
private_constant :SAFE_FILENAME_REGEX
sig { params(basename: String).returns(T::Boolean) }
def self.safe_filename?(basename)
!SAFE_FILENAME_REGEX.match?(basename)
end
sig { params(basename: String).returns(String) }
def self.safe_filename(basename)
basename.gsub(SAFE_FILENAME_REGEX, "")
end
end end