require"forwardable"require"base64"require"time"require"http/errors"require"http/headers"require"http/request/writer"require"http/version"require"http/uri"moduleHTTPclassRequestextendForwardableincludeHTTP::Headers::Mixin# The method given was not understoodclassUnsupportedMethodError<RequestError;end# The scheme of given URI was not understoodclassUnsupportedSchemeError<RequestError;end# Default User-Agent header valueUSER_AGENT="http.rb/#{HTTP::VERSION}".freeze# RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1METHODS=[:options,:get,:head,:post,:put,:delete,:trace,:connect]# RFC 2518: HTTP Extensions for Distributed Authoring -- WEBDAVMETHODS.concat[:propfind,:proppatch,:mkcol,:copy,:move,:lock,:unlock]# RFC 3648: WebDAV Ordered Collections ProtocolMETHODS.concat[:orderpatch]# RFC 3744: WebDAV Access Control ProtocolMETHODS.concat[:acl]# draft-dusseault-http-patch: PATCH Method for HTTPMETHODS.concat[:patch]# draft-reschke-webdav-search: WebDAV SearchMETHODS.concat[:search]# Allowed schemesSCHEMES=[:http,:https,:ws,:wss]# Default ports of supported schemesPORTS={:http=>80,:https=>443,:ws=>80,:wss=>443}# Method is given as a lowercase symbol e.g. :get, :postattr_reader:verb# Scheme is normalized to be a lowercase symbol e.g. :http, :httpsattr_reader:scheme# "Request URI" as per RFC 2616# http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.htmlattr_reader:uriattr_reader:proxy,:body,:version# @option opts [String] :version# @option opts [#to_s] :verb HTTP request method# @option opts [HTTP::URI, #to_s] :uri# @option opts [Hash] :headers# @option opts [Hash] :proxy# @option opts [String] :bodydefinitialize(opts)@verb=opts.fetch(:verb).to_s.downcase.to_sym@uri=normalize_uri(opts.fetch:uri)@scheme=@uri.scheme.to_s.downcase.to_symif@uri.schemefail(UnsupportedMethodError,"unknown method: #{verb}")unlessMETHODS.include?(@verb)fail(UnsupportedSchemeError,"unknown scheme: #{scheme}")unlessSCHEMES.include?(@scheme)@proxy=opts[:proxy]||{}@body=opts[:body]@version=opts[:version]||"1.1"@headers=HTTP::Headers.coerce(opts[:headers]||{})@headers[Headers::HOST]||=default_host_header_value@headers[Headers::USER_AGENT]||=USER_AGENTend# Returns new Request with updated uridefredirect(uri,verb=@verb)req=self.class.new(:verb=>verb,:uri=>@uri.join(uri),:headers=>headers,:proxy=>proxy,:body=>body,:version=>version)req[Headers::HOST]=req.uri.hostreqend# Stream the request to a socketdefstream(socket)include_proxy_authorization_headerifusing_authenticated_proxy?&&!@uri.https?Request::Writer.new(socket,body,headers,headline).streamend# Is this request using a proxy?defusing_proxy?proxy&&proxy.keys.size>=2end# Is this request using an authenticated proxy?defusing_authenticated_proxy?proxy&&proxy.keys.size==4end# Compute and add the Proxy-Authorization headerdefinclude_proxy_authorization_headerheaders[Headers::PROXY_AUTHORIZATION]=proxy_authorization_headerenddefproxy_authorization_headerdigest=Base64.strict_encode64("#{proxy[:proxy_username]}:#{proxy[:proxy_password]}")"Basic #{digest}"end# Setup tunnel through proxy for SSL requestdefconnect_using_proxy(socket)Request::Writer.new(socket,nil,proxy_connect_headers,proxy_connect_header).connect_through_proxyend# Compute HTTP request header for direct or proxy requestdefheadlinerequest_uri=using_proxy??uri:uri.omit(:scheme,:authority)"#{verb.to_s.upcase}#{request_uri.omit:fragment} HTTP/#{version}"end# Compute HTTP request header SSL proxy connectiondefproxy_connect_header"CONNECT #{host}:#{port} HTTP/#{version}"end# Headers to send with proxy connect requestdefproxy_connect_headersconnect_headers=HTTP::Headers.coerce(Headers::HOST=>headers[Headers::HOST],Headers::USER_AGENT=>headers[Headers::USER_AGENT])connect_headers[Headers::PROXY_AUTHORIZATION]=proxy_authorization_headerifusing_authenticated_proxy?connect_headersend# Host for tcp socketdefsocket_hostusing_proxy??proxy[:proxy_address]:hostend# Port for tcp socketdefsocket_portusing_proxy??proxy[:proxy_port]:portendprivate# @!attribute [r] host# @return [String]def_delegator:@uri,:host# @!attribute [r] port# @return [Fixnum]defport@uri.port||@uri.default_portend# @return [String] Default host (with port if needed) header value.defdefault_host_header_valuePORTS[@scheme]!=port?"#{host}:#{port}":hostend# @return [HTTP::URI] URI with all componentes but query being normalized.defnormalize_uri(uri)uri=HTTP::URI.parseuriHTTP::URI.new(:scheme=>uri.normalized_scheme,:authority=>uri.normalized_authority,:path=>uri.normalized_path,:query=>uri.query,:fragment=>uri.normalized_fragment)endendend