# frozen_string_literal: truerequire"net/http"require"zlib"moduleSentryclassHTTPTransport<TransportGZIP_ENCODING="gzip"GZIP_THRESHOLD=1024*30CONTENT_TYPE="application/x-sentry-envelope"DEFAULT_DELAY=60RETRY_AFTER_HEADER="retry-after"RATE_LIMIT_HEADER="x-sentry-rate-limits"USER_AGENT="sentry-ruby/#{Sentry::VERSION}"# The list of errors ::Net::HTTP is known to raise# See https://github.com/ruby/ruby/blob/b0c639f249165d759596f9579fa985cb30533de6/lib/bundler/fetcher.rb#L281-L286HTTP_ERRORS=[Timeout::Error,EOFError,SocketError,Errno::ENETDOWN,Errno::ENETUNREACH,Errno::EINVAL,Errno::ECONNRESET,Errno::ETIMEDOUT,Errno::EAGAIN,Net::HTTPBadResponse,Net::HTTPHeaderSyntaxError,Net::ProtocolError,Zlib::BufError,Errno::EHOSTUNREACH,Errno::ECONNREFUSED].freezedefinitialize(*args)superlog_debug("Sentry HTTP Transport will connect to #{@dsn.server}")if@dsnenddefsend_data(data)encoding=""ifshould_compress?(data)data=Zlib.gzip(data)encoding=GZIP_ENCODINGendheaders={"Content-Type"=>CONTENT_TYPE,"Content-Encoding"=>encoding,"User-Agent"=>USER_AGENT}auth_header=generate_auth_headerheaders["X-Sentry-Auth"]=auth_headerifauth_headerresponse=conn.startdo|http|request=::Net::HTTP::Post.new(endpoint,headers)request.body=datahttp.request(request)endifresponse.code.match?(/\A2\d{2}/)handle_rate_limited_response(response)ifhas_rate_limited_header?(response)elsifresponse.code=="429"log_debug("the server responded with status 429")handle_rate_limited_response(response)elseerror_info="the server responded with status #{response.code}"error_info+="\nbody: #{response.body}"error_info+=" Error in headers is: #{response['x-sentry-error']}"ifresponse["x-sentry-error"]raiseSentry::ExternalError,error_infoendrescueSocketError,*HTTP_ERRORS=>eon_errorifrespond_to?(:on_error)raiseSentry::ExternalError.new(e&.message)enddefendpoint@dsn.envelope_endpointenddefgenerate_auth_headerreturnnilunless@dsnnow=Sentry.utc_now.to_ifields={"sentry_version"=>PROTOCOL_VERSION,"sentry_client"=>USER_AGENT,"sentry_timestamp"=>now,"sentry_key"=>@dsn.public_key}fields["sentry_secret"]=@dsn.secret_keyif@dsn.secret_key"Sentry "+fields.map{|key,value|"#{key}=#{value}"}.join(", ")enddefconnserver=URI(@dsn.server)# connection respects proxy setting from @transport_configuration, or environment variables (HTTP_PROXY, HTTPS_PROXY, NO_PROXY)# Net::HTTP will automatically read the env vars.# See https://ruby-doc.org/3.2.2/stdlibs/net/Net/HTTP.html#class-Net::HTTP-label-Proxiesconnection=ifproxy=normalize_proxy(@transport_configuration.proxy)::Net::HTTP.new(server.hostname,server.port,proxy[:uri].hostname,proxy[:uri].port,proxy[:user],proxy[:password])else::Net::HTTP.new(server.hostname,server.port)endconnection.use_ssl=server.scheme=="https"connection.read_timeout=@transport_configuration.timeoutconnection.write_timeout=@transport_configuration.timeoutifconnection.respond_to?(:write_timeout)connection.open_timeout=@transport_configuration.open_timeoutssl_configuration.eachdo|key,value|connection.send("#{key}=",value)endconnectionendprivatedefhas_rate_limited_header?(headers)headers[RETRY_AFTER_HEADER]||headers[RATE_LIMIT_HEADER]enddefhandle_rate_limited_response(headers)rate_limits=ifrate_limits=headers[RATE_LIMIT_HEADER]parse_rate_limit_header(rate_limits)elsifretry_after=headers[RETRY_AFTER_HEADER]# although Sentry doesn't send a date string back# based on HTTP specification, this could be a date string (instead of an integer)retry_after=retry_after.to_iretry_after=DEFAULT_DELAYifretry_after==0{nil=>Time.now+retry_after}else{nil=>Time.now+DEFAULT_DELAY}endrate_limits.eachdo|category,limit|ifcurrent_limit=@rate_limits[category]ifcurrent_limit<limit@rate_limits[category]=limitendelse@rate_limits[category]=limitendendenddefparse_rate_limit_header(rate_limit_header)time=Time.nowresult={}limits=rate_limit_header.split(",")limits.eachdo|limit|nextiflimit.nil?||limit.empty?beginretry_after,categories=limit.strip.split(":").first(2)retry_after=time+retry_after.to_icategories=categories.split(";")ifcategories.empty?result[nil]=retry_afterelsecategories.eachdo|category|result[category]=retry_afterendendrescueStandardErrorendendresultenddefshould_compress?(data)@transport_configuration.encoding==GZIP_ENCODING&&data.bytesize>=GZIP_THRESHOLDend# @param proxy [String, URI, Hash] Proxy config value passed into `config.transport`.# Accepts either a URI formatted string, URI, or a hash with the `uri`, `user`, and `password` keys.# @return [Hash] Normalized proxy config that will be passed into `Net::HTTP`defnormalize_proxy(proxy)returnproxyunlessproxycaseproxywhenStringuri=URI(proxy){uri: uri,user: uri.user,password: uri.password}whenURI{uri: proxy,user: proxy.user,password: proxy.password}whenHashproxyendenddefssl_configurationconfiguration={verify: @transport_configuration.ssl_verification,ca_file: @transport_configuration.ssl_ca_file}.merge(@transport_configuration.ssl||{})configuration[:verify_mode]=configuration.delete(:verify)?OpenSSL::SSL::VERIFY_PEER:OpenSSL::SSL::VERIFY_NONEconfigurationendendend