92 lines
2.4 KiB
Ruby
92 lines
2.4 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Gitlab
|
|
module Middleware
|
|
class CompressedJson
|
|
COLLECTOR_PATH = '/api/v4/error_tracking/collector'
|
|
PACKAGES_PATH = %r{
|
|
\A/api/v4/ (?# prefix)
|
|
(?:projects/
|
|
(?<project_id>
|
|
.+ (?# at least one character)
|
|
)/
|
|
)? (?# projects segment)
|
|
packages/npm/-/npm/v1/security/
|
|
(?:(?:advisories/bulk)|(?:audits/quick))\z (?# end)
|
|
}xi.freeze
|
|
MAXIMUM_BODY_SIZE = 200.kilobytes.to_i
|
|
UNSAFE_CHARACTERS = %r{[!"#&'()*+,./:;<>=?@\[\]^`{}|~$]}xi.freeze
|
|
|
|
def initialize(app)
|
|
@app = app
|
|
end
|
|
|
|
def call(env)
|
|
if compressed_et_request?(env)
|
|
input = extract(env['rack.input'])
|
|
|
|
if input.length > MAXIMUM_BODY_SIZE
|
|
return too_large
|
|
end
|
|
|
|
env.delete('HTTP_CONTENT_ENCODING')
|
|
env['CONTENT_LENGTH'] = input.length
|
|
env['rack.input'] = StringIO.new(input)
|
|
end
|
|
|
|
@app.call(env)
|
|
end
|
|
|
|
def compressed_et_request?(env)
|
|
post_request?(env) &&
|
|
gzip_encoding?(env) &&
|
|
match_content_type?(env) &&
|
|
match_path?(env)
|
|
end
|
|
|
|
def too_large
|
|
[413, { 'Content-Type' => 'text/plain' }, ['Payload Too Large']]
|
|
end
|
|
|
|
def relative_url
|
|
File.join('', Gitlab.config.gitlab.relative_url_root).chomp('/')
|
|
end
|
|
|
|
def extract(input)
|
|
Zlib::GzipReader.new(input).read(MAXIMUM_BODY_SIZE + 1)
|
|
end
|
|
|
|
def post_request?(env)
|
|
env['REQUEST_METHOD'] == 'POST'
|
|
end
|
|
|
|
def gzip_encoding?(env)
|
|
env['HTTP_CONTENT_ENCODING'] == 'gzip'
|
|
end
|
|
|
|
def match_content_type?(env)
|
|
env['CONTENT_TYPE'].nil? ||
|
|
env['CONTENT_TYPE'] == 'application/json' ||
|
|
env['CONTENT_TYPE'] == 'application/x-sentry-envelope'
|
|
end
|
|
|
|
def match_path?(env)
|
|
env['PATH_INFO'].start_with?((File.join(relative_url, COLLECTOR_PATH))) ||
|
|
match_packages_path?(env)
|
|
end
|
|
|
|
def match_packages_path?(env)
|
|
match_data = env['PATH_INFO'].delete_prefix(relative_url).match(PACKAGES_PATH)
|
|
return false unless match_data
|
|
|
|
return true unless match_data[:project_id] # instance level endpoint was matched
|
|
|
|
url_encoded?(match_data[:project_id])
|
|
end
|
|
|
|
def url_encoded?(project_id)
|
|
project_id !~ UNSAFE_CHARACTERS
|
|
end
|
|
end
|
|
end
|
|
end
|