diff --git a/lib/final_destination.rb b/lib/final_destination.rb index 94fd0e04bff..fa5cbcc06d0 100644 --- a/lib/final_destination.rb +++ b/lib/final_destination.rb @@ -67,7 +67,7 @@ class FinalDestination @status = :ready @follow_canonical = @opts[:follow_canonical] - @http_verb = http_verb(@force_get_hosts, @follow_canonical) + @http_verb = @opts[:http_verb] || http_verb(@force_get_hosts, @follow_canonical) @cookie = nil @limited_ips = [] @verbose = @opts[:verbose] || false @@ -82,8 +82,8 @@ class FinalDestination 20 end - def self.resolve(url) - new(url).resolve + def self.resolve(url, opts = nil) + new(url, opts).resolve end def http_verb(force_get_hosts, follow_canonical) diff --git a/lib/theme_store/git_importer.rb b/lib/theme_store/git_importer.rb index 63db9b4b821..873986af9e0 100644 --- a/lib/theme_store/git_importer.rb +++ b/lib/theme_store/git_importer.rb @@ -77,6 +77,25 @@ class ThemeStore::GitImporter protected + def redirected_uri + first_clone_uri = @uri.dup + first_clone_uri.path.gsub!(/\/\z/, "") + first_clone_uri.path += "/info/refs" + first_clone_uri.query = "service=git-upload-pack" + + redirected_uri = FinalDestination.resolve(first_clone_uri.to_s, http_verb: :get) + + if redirected_uri&.path.ends_with?("/info/refs") + redirected_uri.path.gsub!(/\/info\/refs\z/, "") + redirected_uri.query = nil + redirected_uri + else + @uri + end + rescue + @uri + end + def raise_import_error! raise RemoteTheme::ImportError.new(I18n.t("themes.import_error.git")) end @@ -117,44 +136,28 @@ class ThemeStore::GitImporter end def clone_http! - uris = [@uri] + @uri = redirected_uri + @url = @uri.to_s - begin - resolved_uri = FinalDestination.resolve(@uri.to_s) - if resolved_uri && resolved_uri != @uri - uris.unshift(resolved_uri) - end - rescue - # If this fails, we can stil attempt to clone using the original URI + unless ["http", "https"].include?(@uri.scheme) + raise_import_error! end - uris.each do |uri| - @uri = uri - @url = @uri.to_s + addresses = FinalDestination::SSRFDetector.lookup_and_filter_ips(@uri.host) - unless ["http", "https"].include?(@uri.scheme) - raise_import_error! - end + unless addresses.empty? + env = { "GIT_TERMINAL_PROMPT" => "0" } - addresses = FinalDestination::SSRFDetector.lookup_and_filter_ips(@uri.host) + args = clone_args( + "http.followRedirects" => "false", + "http.curloptResolve" => "#{@uri.host}:#{@uri.port}:#{addresses.join(',')}", + ) - unless addresses.empty? - env = { "GIT_TERMINAL_PROMPT" => "0" } - - args = clone_args( - "http.followRedirects" => "false", - "http.curloptResolve" => "#{@uri.host}:#{@uri.port}:#{addresses.join(',')}", - ) - - begin - Discourse::Utils.execute_command(env, *args, timeout: COMMAND_TIMEOUT_SECONDS) - return - rescue RuntimeError - end + begin + Discourse::Utils.execute_command(env, *args, timeout: COMMAND_TIMEOUT_SECONDS) + rescue RuntimeError end end - - raise_import_error! end def clone_ssh! diff --git a/spec/lib/theme_store/git_importer_spec.rb b/spec/lib/theme_store/git_importer_spec.rb index 62d588f9bf5..7efe3096b56 100644 --- a/spec/lib/theme_store/git_importer_spec.rb +++ b/spec/lib/theme_store/git_importer_spec.rb @@ -6,6 +6,7 @@ require 'theme_store/git_importer' RSpec.describe ThemeStore::GitImporter do describe "#import" do let(:url) { "https://github.com/example/example.git" } + let(:first_fetch_url) { "https://github.com/example/example.git/info/refs?service=git-upload-pack" } let(:trailing_slash_url) { "https://github.com/example/example/" } let(:ssh_url) { "git@github.com:example/example.git" } let(:branch) { "dev" } @@ -13,8 +14,17 @@ RSpec.describe ThemeStore::GitImporter do before do hex = "xxx" SecureRandom.stubs(:hex).returns(hex) - FinalDestination.stubs(:resolve).with(url).returns(URI.parse(url)) - FinalDestination::SSRFDetector.stubs(:lookup_and_filter_ips).with("github.com").returns(["192.0.2.100"]) + + FinalDestination::SSRFDetector + .stubs(:lookup_and_filter_ips) + .with("github.com") + .returns(["192.0.2.100"]) + + FinalDestination + .stubs(:resolve) + .with(first_fetch_url, http_verb: :get) + .returns(URI.parse(first_fetch_url)) + @temp_folder = "#{Pathname.new(Dir.tmpdir).realpath}/discourse_theme_#{hex}" @ssh_folder = "#{Pathname.new(Dir.tmpdir).realpath}/discourse_theme_ssh_#{hex}" end