diff options
Diffstat (limited to 'lib/gitlab/lfs/client.rb')
-rw-r--r-- | lib/gitlab/lfs/client.rb | 81 |
1 files changed, 59 insertions, 22 deletions
diff --git a/lib/gitlab/lfs/client.rb b/lib/gitlab/lfs/client.rb index a05e8107cad..10df9262cca 100644 --- a/lib/gitlab/lfs/client.rb +++ b/lib/gitlab/lfs/client.rb @@ -36,7 +36,7 @@ module Gitlab headers: build_request_headers ) - raise BatchSubmitError unless rsp.success? + raise BatchSubmitError.new(http_response: rsp) unless rsp.success? # HTTParty provides rsp.parsed_response, but it only kicks in for the # application/json content type in the response, which we can't rely on @@ -53,19 +53,13 @@ module Gitlab params = { body_stream: file, - headers: { - 'Content-Length' => object.size.to_s, - 'Content-Type' => 'application/octet-stream', - 'User-Agent' => GIT_LFS_USER_AGENT - }.merge(upload_action['header'] || {}) + headers: upload_headers(object, upload_action) } - authenticated = true if params[:headers].key?('Authorization') - params[:basic_auth] = basic_auth unless authenticated - - rsp = Gitlab::HTTP.put(upload_action['href'], params) + url = set_basic_auth_and_extract_lfs_url!(params, upload_action['href']) + rsp = Gitlab::HTTP.put(url, params) - raise ObjectUploadError unless rsp.success? + raise ObjectUploadError.new(http_response: rsp) unless rsp.success? ensure file&.close end @@ -76,20 +70,51 @@ module Gitlab headers: build_request_headers(verify_action['header']) } - authenticated = true if params[:headers].key?('Authorization') - params[:basic_auth] = basic_auth unless authenticated - - rsp = Gitlab::HTTP.post(verify_action['href'], params) + url = set_basic_auth_and_extract_lfs_url!(params, verify_action['href']) + rsp = Gitlab::HTTP.post(url, params) - raise ObjectVerifyError unless rsp.success? + raise ObjectVerifyError.new(http_response: rsp) unless rsp.success? end private + def set_basic_auth_and_extract_lfs_url!(params, raw_url) + authenticated = true if params[:headers].key?('Authorization') + params[:basic_auth] = basic_auth unless authenticated + strip_userinfo = authenticated || params[:basic_auth].present? + lfs_url(raw_url, strip_userinfo) + end + def build_request_headers(extra_headers = nil) DEFAULT_HEADERS.merge(extra_headers || {}) end + def upload_headers(object, upload_action) + # This uses the httprb library to handle case-insensitive HTTP headers + headers = ::HTTP::Headers.new + headers.merge!(upload_action['header']) + transfer_encodings = Array(headers['Transfer-Encoding']&.split(',')).map(&:strip) + + headers['Content-Length'] = object.size.to_s unless transfer_encodings.include?('chunked') + headers['Content-Type'] = 'application/octet-stream' + headers['User-Agent'] = GIT_LFS_USER_AGENT + + headers.to_h + end + + def lfs_url(raw_url, strip_userinfo) + # HTTParty will give precedence to the username/password + # specified in the URL. This causes problems with Azure DevOps, + # which includes a username in the URL. Stripping the userinfo + # from the URL allows the provided HTTP Basic Authentication + # credentials to be used. + if strip_userinfo + Gitlab::UrlSanitizer.new(raw_url).sanitized_url + else + raw_url + end + end + attr_reader :credentials def batch_url @@ -105,9 +130,21 @@ module Gitlab { username: credentials[:user], password: credentials[:password] } end - class BatchSubmitError < StandardError + class HttpError < StandardError + def initialize(http_response:) + super + + @http_response = http_response + end + + def http_error + "HTTP status #{@http_response.code}" + end + end + + class BatchSubmitError < HttpError def message - "Failed to submit batch" + "Failed to submit batch: #{http_error}" end end @@ -122,15 +159,15 @@ module Gitlab end end - class ObjectUploadError < StandardError + class ObjectUploadError < HttpError def message - "Failed to upload object" + "Failed to upload object: #{http_error}" end end - class ObjectVerifyError < StandardError + class ObjectVerifyError < HttpError def message - "Failed to verify object" + "Failed to verify object: #{http_error}" end end end |