diff options
| author | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-16 18:49:26 +0900 | 
|---|---|---|
| committer | Jari Vetoniemi <jari.vetoniemi@indooratlas.com> | 2020-03-30 00:39:06 +0900 | 
| commit | fcbf63e62c627deae76c1b8cb8c0876c536ed811 (patch) | |
| tree | 64cb17de3f41a2b6fef2368028fbd00349946994 /jni/ruby/lib/net/http | |
Fresh start
Diffstat (limited to 'jni/ruby/lib/net/http')
| -rw-r--r-- | jni/ruby/lib/net/http/backward.rb | 25 | ||||
| -rw-r--r-- | jni/ruby/lib/net/http/exceptions.rb | 25 | ||||
| -rw-r--r-- | jni/ruby/lib/net/http/generic_request.rb | 332 | ||||
| -rw-r--r-- | jni/ruby/lib/net/http/header.rb | 452 | ||||
| -rw-r--r-- | jni/ruby/lib/net/http/proxy_delta.rb | 16 | ||||
| -rw-r--r-- | jni/ruby/lib/net/http/request.rb | 20 | ||||
| -rw-r--r-- | jni/ruby/lib/net/http/requests.rb | 122 | ||||
| -rw-r--r-- | jni/ruby/lib/net/http/response.rb | 416 | ||||
| -rw-r--r-- | jni/ruby/lib/net/http/responses.rb | 273 | 
9 files changed, 1681 insertions, 0 deletions
| diff --git a/jni/ruby/lib/net/http/backward.rb b/jni/ruby/lib/net/http/backward.rb new file mode 100644 index 0000000..faf47b8 --- /dev/null +++ b/jni/ruby/lib/net/http/backward.rb @@ -0,0 +1,25 @@ +# for backward compatibility + +# :enddoc: + +class Net::HTTP +  ProxyMod = ProxyDelta +end + +module Net +  HTTPSession = Net::HTTP +end + +module Net::NetPrivate +  HTTPRequest = ::Net::HTTPRequest +end + +Net::HTTPInformationCode  = Net::HTTPInformation +Net::HTTPSuccessCode      = Net::HTTPSuccess +Net::HTTPRedirectionCode  = Net::HTTPRedirection +Net::HTTPRetriableCode    = Net::HTTPRedirection +Net::HTTPClientErrorCode  = Net::HTTPClientError +Net::HTTPFatalErrorCode   = Net::HTTPClientError +Net::HTTPServerErrorCode  = Net::HTTPServerError +Net::HTTPResponceReceiver = Net::HTTPResponse + diff --git a/jni/ruby/lib/net/http/exceptions.rb b/jni/ruby/lib/net/http/exceptions.rb new file mode 100644 index 0000000..6c5d81c --- /dev/null +++ b/jni/ruby/lib/net/http/exceptions.rb @@ -0,0 +1,25 @@ +# Net::HTTP exception class. +# You cannot use Net::HTTPExceptions directly; instead, you must use +# its subclasses. +module Net::HTTPExceptions +  def initialize(msg, res)   #:nodoc: +    super msg +    @response = res +  end +  attr_reader :response +  alias data response    #:nodoc: obsolete +end +class Net::HTTPError < Net::ProtocolError +  include Net::HTTPExceptions +end +class Net::HTTPRetriableError < Net::ProtoRetriableError +  include Net::HTTPExceptions +end +class Net::HTTPServerException < Net::ProtoServerError +  # We cannot use the name "HTTPServerError", it is the name of the response. +  include Net::HTTPExceptions +end +class Net::HTTPFatalError < Net::ProtoFatalError +  include Net::HTTPExceptions +end + diff --git a/jni/ruby/lib/net/http/generic_request.rb b/jni/ruby/lib/net/http/generic_request.rb new file mode 100644 index 0000000..00ff434 --- /dev/null +++ b/jni/ruby/lib/net/http/generic_request.rb @@ -0,0 +1,332 @@ +# HTTPGenericRequest is the parent of the HTTPRequest class. +# Do not use this directly; use a subclass of HTTPRequest. +# +# Mixes in the HTTPHeader module to provide easier access to HTTP headers. +# +class Net::HTTPGenericRequest + +  include Net::HTTPHeader + +  def initialize(m, reqbody, resbody, uri_or_path, initheader = nil) +    @method = m +    @request_has_body = reqbody +    @response_has_body = resbody + +    if URI === uri_or_path then +      @uri = uri_or_path.dup +      host = @uri.hostname.dup +      host << ":".freeze << @uri.port.to_s if @uri.port != @uri.default_port +      @path = uri_or_path.request_uri +      raise ArgumentError, "no HTTP request path given" unless @path +    else +      @uri = nil +      host = nil +      raise ArgumentError, "no HTTP request path given" unless uri_or_path +      raise ArgumentError, "HTTP request path is empty" if uri_or_path.empty? +      @path = uri_or_path.dup +    end + +    @decode_content = false + +    if @response_has_body and Net::HTTP::HAVE_ZLIB then +      if !initheader || +         !initheader.keys.any? { |k| +           %w[accept-encoding range].include? k.downcase +         } then +        @decode_content = true +        initheader = initheader ? initheader.dup : {} +        initheader["accept-encoding"] = +          "gzip;q=1.0,deflate;q=0.6,identity;q=0.3" +      end +    end + +    initialize_http_header initheader +    self['Accept'] ||= '*/*' +    self['User-Agent'] ||= 'Ruby' +    self['Host'] ||= host if host +    @body = nil +    @body_stream = nil +    @body_data = nil +  end + +  attr_reader :method +  attr_reader :path +  attr_reader :uri + +  # Automatically set to false if the user sets the Accept-Encoding header. +  # This indicates they wish to handle Content-encoding in responses +  # themselves. +  attr_reader :decode_content + +  def inspect +    "\#<#{self.class} #{@method}>" +  end + +  ## +  # Don't automatically decode response content-encoding if the user indicates +  # they want to handle it. + +  def []=(key, val) # :nodoc: +    @decode_content = false if key.downcase == 'accept-encoding' + +    super key, val +  end + +  def request_body_permitted? +    @request_has_body +  end + +  def response_body_permitted? +    @response_has_body +  end + +  def body_exist? +    warn "Net::HTTPRequest#body_exist? is obsolete; use response_body_permitted?" if $VERBOSE +    response_body_permitted? +  end + +  attr_reader :body + +  def body=(str) +    @body = str +    @body_stream = nil +    @body_data = nil +    str +  end + +  attr_reader :body_stream + +  def body_stream=(input) +    @body = nil +    @body_stream = input +    @body_data = nil +    input +  end + +  def set_body_internal(str)   #:nodoc: internal use only +    raise ArgumentError, "both of body argument and HTTPRequest#body set" if str and (@body or @body_stream) +    self.body = str if str +    if @body.nil? && @body_stream.nil? && @body_data.nil? && request_body_permitted? +      self.body = '' +    end +  end + +  # +  # write +  # + +  def exec(sock, ver, path)   #:nodoc: internal use only +    if @body +      send_request_with_body sock, ver, path, @body +    elsif @body_stream +      send_request_with_body_stream sock, ver, path, @body_stream +    elsif @body_data +      send_request_with_body_data sock, ver, path, @body_data +    else +      write_header sock, ver, path +    end +  end + +  def update_uri(addr, port, ssl) # :nodoc: internal use only +    # reflect the connection and @path to @uri +    return unless @uri + +    if ssl +      scheme = 'https'.freeze +      klass = URI::HTTPS +    else +      scheme = 'http'.freeze +      klass = URI::HTTP +    end + +    if host = self['host'] +      host.sub!(/:.*/s, ''.freeze) +    elsif host = @uri.host +    else +     host = addr +    end +    # convert the class of the URI +    if @uri.is_a?(klass) +      @uri.host = host +      @uri.port = port +    else +      @uri = klass.new( +        scheme, @uri.userinfo, +        host, port, nil, +        @uri.path, nil, @uri.query, nil) +    end +  end + +  private + +  class Chunker #:nodoc: +    def initialize(sock) +      @sock = sock +      @prev = nil +    end + +    def write(buf) +      # avoid memcpy() of buf, buf can huge and eat memory bandwidth +      @sock.write("#{buf.bytesize.to_s(16)}\r\n") +      rv = @sock.write(buf) +      @sock.write("\r\n") +      rv +    end + +    def finish +      @sock.write("0\r\n\r\n") +    end +  end + +  def send_request_with_body(sock, ver, path, body) +    self.content_length = body.bytesize +    delete 'Transfer-Encoding' +    supply_default_content_type +    write_header sock, ver, path +    wait_for_continue sock, ver if sock.continue_timeout +    sock.write body +  end + +  def send_request_with_body_stream(sock, ver, path, f) +    unless content_length() or chunked? +      raise ArgumentError, +          "Content-Length not given and Transfer-Encoding is not `chunked'" +    end +    supply_default_content_type +    write_header sock, ver, path +    wait_for_continue sock, ver if sock.continue_timeout +    if chunked? +      chunker = Chunker.new(sock) +      IO.copy_stream(f, chunker) +      chunker.finish +    else +      # copy_stream can sendfile() to sock.io unless we use SSL. +      # If sock.io is an SSLSocket, copy_stream will hit SSL_write() +      IO.copy_stream(f, sock.io) +    end +  end + +  def send_request_with_body_data(sock, ver, path, params) +    if /\Amultipart\/form-data\z/i !~ self.content_type +      self.content_type = 'application/x-www-form-urlencoded' +      return send_request_with_body(sock, ver, path, URI.encode_www_form(params)) +    end + +    opt = @form_option.dup +    require 'securerandom' unless defined?(SecureRandom) +    opt[:boundary] ||= SecureRandom.urlsafe_base64(40) +    self.set_content_type(self.content_type, boundary: opt[:boundary]) +    if chunked? +      write_header sock, ver, path +      encode_multipart_form_data(sock, params, opt) +    else +      require 'tempfile' +      file = Tempfile.new('multipart') +      file.binmode +      encode_multipart_form_data(file, params, opt) +      file.rewind +      self.content_length = file.size +      write_header sock, ver, path +      IO.copy_stream(file, sock) +      file.close(true) +    end +  end + +  def encode_multipart_form_data(out, params, opt) +    charset = opt[:charset] +    boundary = opt[:boundary] +    require 'securerandom' unless defined?(SecureRandom) +    boundary ||= SecureRandom.urlsafe_base64(40) +    chunked_p = chunked? + +    buf = '' +    params.each do |key, value, h={}| +      key = quote_string(key, charset) +      filename = +        h.key?(:filename) ? h[:filename] : +        value.respond_to?(:to_path) ? File.basename(value.to_path) : +        nil + +      buf << "--#{boundary}\r\n" +      if filename +        filename = quote_string(filename, charset) +        type = h[:content_type] || 'application/octet-stream' +        buf << "Content-Disposition: form-data; " \ +          "name=\"#{key}\"; filename=\"#{filename}\"\r\n" \ +          "Content-Type: #{type}\r\n\r\n" +        if !out.respond_to?(:write) || !value.respond_to?(:read) +          # if +out+ is not an IO or +value+ is not an IO +          buf << (value.respond_to?(:read) ? value.read : value) +        elsif value.respond_to?(:size) && chunked_p +          # if +out+ is an IO and +value+ is a File, use IO.copy_stream +          flush_buffer(out, buf, chunked_p) +          out << "%x\r\n" % value.size if chunked_p +          IO.copy_stream(value, out) +          out << "\r\n" if chunked_p +        else +          # +out+ is an IO, and +value+ is not a File but an IO +          flush_buffer(out, buf, chunked_p) +          1 while flush_buffer(out, value.read(4096), chunked_p) +        end +      else +        # non-file field: +        #   HTML5 says, "The parts of the generated multipart/form-data +        #   resource that correspond to non-file fields must not have a +        #   Content-Type header specified." +        buf << "Content-Disposition: form-data; name=\"#{key}\"\r\n\r\n" +        buf << (value.respond_to?(:read) ? value.read : value) +      end +      buf << "\r\n" +    end +    buf << "--#{boundary}--\r\n" +    flush_buffer(out, buf, chunked_p) +    out << "0\r\n\r\n" if chunked_p +  end + +  def quote_string(str, charset) +    str = str.encode(charset, fallback:->(c){'&#%d;'%c.encode("UTF-8").ord}) if charset +    str.gsub(/[\\"]/, '\\\\\&') +  end + +  def flush_buffer(out, buf, chunked_p) +    return unless buf +    out << "%x\r\n"%buf.bytesize if chunked_p +    out << buf +    out << "\r\n" if chunked_p +    buf.clear +  end + +  def supply_default_content_type +    return if content_type() +    warn 'net/http: warning: Content-Type did not set; using application/x-www-form-urlencoded' if $VERBOSE +    set_content_type 'application/x-www-form-urlencoded' +  end + +  ## +  # Waits up to the continue timeout for a response from the server provided +  # we're speaking HTTP 1.1 and are expecting a 100-continue response. + +  def wait_for_continue(sock, ver) +    if ver >= '1.1' and @header['expect'] and +        @header['expect'].include?('100-continue') +      if IO.select([sock.io], nil, nil, sock.continue_timeout) +        res = Net::HTTPResponse.read_new(sock) +        unless res.kind_of?(Net::HTTPContinue) +          res.decode_content = @decode_content +          throw :response, res +        end +      end +    end +  end + +  def write_header(sock, ver, path) +    buf = "#{@method} #{path} HTTP/#{ver}\r\n" +    each_capitalized do |k,v| +      buf << "#{k}: #{v}\r\n" +    end +    buf << "\r\n" +    sock.write buf +  end + +end + diff --git a/jni/ruby/lib/net/http/header.rb b/jni/ruby/lib/net/http/header.rb new file mode 100644 index 0000000..912419d --- /dev/null +++ b/jni/ruby/lib/net/http/header.rb @@ -0,0 +1,452 @@ +# The HTTPHeader module defines methods for reading and writing +# HTTP headers. +# +# It is used as a mixin by other classes, to provide hash-like +# access to HTTP header values. Unlike raw hash access, HTTPHeader +# provides access via case-insensitive keys. It also provides +# methods for accessing commonly-used HTTP header values in more +# convenient formats. +# +module Net::HTTPHeader + +  def initialize_http_header(initheader) +    @header = {} +    return unless initheader +    initheader.each do |key, value| +      warn "net/http: warning: duplicated HTTP header: #{key}" if key?(key) and $VERBOSE +      @header[key.downcase] = [value.strip] +    end +  end + +  def size   #:nodoc: obsolete +    @header.size +  end + +  alias length size   #:nodoc: obsolete + +  # Returns the header field corresponding to the case-insensitive key. +  # For example, a key of "Content-Type" might return "text/html" +  def [](key) +    a = @header[key.downcase] or return nil +    a.join(', ') +  end + +  # Sets the header field corresponding to the case-insensitive key. +  def []=(key, val) +    unless val +      @header.delete key.downcase +      return val +    end +    @header[key.downcase] = [val] +  end + +  # [Ruby 1.8.3] +  # Adds a value to a named header field, instead of replacing its value. +  # Second argument +val+ must be a String. +  # See also #[]=, #[] and #get_fields. +  # +  #   request.add_field 'X-My-Header', 'a' +  #   p request['X-My-Header']              #=> "a" +  #   p request.get_fields('X-My-Header')   #=> ["a"] +  #   request.add_field 'X-My-Header', 'b' +  #   p request['X-My-Header']              #=> "a, b" +  #   p request.get_fields('X-My-Header')   #=> ["a", "b"] +  #   request.add_field 'X-My-Header', 'c' +  #   p request['X-My-Header']              #=> "a, b, c" +  #   p request.get_fields('X-My-Header')   #=> ["a", "b", "c"] +  # +  def add_field(key, val) +    if @header.key?(key.downcase) +      @header[key.downcase].push val +    else +      @header[key.downcase] = [val] +    end +  end + +  # [Ruby 1.8.3] +  # Returns an array of header field strings corresponding to the +  # case-insensitive +key+.  This method allows you to get duplicated +  # header fields without any processing.  See also #[]. +  # +  #   p response.get_fields('Set-Cookie') +  #     #=> ["session=al98axx; expires=Fri, 31-Dec-1999 23:58:23", +  #          "query=rubyscript; expires=Fri, 31-Dec-1999 23:58:23"] +  #   p response['Set-Cookie'] +  #     #=> "session=al98axx; expires=Fri, 31-Dec-1999 23:58:23, query=rubyscript; expires=Fri, 31-Dec-1999 23:58:23" +  # +  def get_fields(key) +    return nil unless @header[key.downcase] +    @header[key.downcase].dup +  end + +  # Returns the header field corresponding to the case-insensitive key. +  # Returns the default value +args+, or the result of the block, or +  # raises an IndexError if there's no header field named +key+ +  # See Hash#fetch +  def fetch(key, *args, &block)   #:yield: +key+ +    a = @header.fetch(key.downcase, *args, &block) +    a.kind_of?(Array) ? a.join(', ') : a +  end + +  # Iterates through the header names and values, passing in the name +  # and value to the code block supplied. +  # +  # Example: +  # +  #     response.header.each_header {|key,value| puts "#{key} = #{value}" } +  # +  def each_header   #:yield: +key+, +value+ +    block_given? or return enum_for(__method__) +    @header.each do |k,va| +      yield k, va.join(', ') +    end +  end + +  alias each each_header + +  # Iterates through the header names in the header, passing +  # each header name to the code block. +  def each_name(&block)   #:yield: +key+ +    block_given? or return enum_for(__method__) +    @header.each_key(&block) +  end + +  alias each_key each_name + +  # Iterates through the header names in the header, passing +  # capitalized header names to the code block. +  # +  # Note that header names are capitalized systematically; +  # capitalization may not match that used by the remote HTTP +  # server in its response. +  def each_capitalized_name  #:yield: +key+ +    block_given? or return enum_for(__method__) +    @header.each_key do |k| +      yield capitalize(k) +    end +  end + +  # Iterates through header values, passing each value to the +  # code block. +  def each_value   #:yield: +value+ +    block_given? or return enum_for(__method__) +    @header.each_value do |va| +      yield va.join(', ') +    end +  end + +  # Removes a header field, specified by case-insensitive key. +  def delete(key) +    @header.delete(key.downcase) +  end + +  # true if +key+ header exists. +  def key?(key) +    @header.key?(key.downcase) +  end + +  # Returns a Hash consisting of header names and array of values. +  # e.g. +  # {"cache-control" => ["private"], +  #  "content-type" => ["text/html"], +  #  "date" => ["Wed, 22 Jun 2005 22:11:50 GMT"]} +  def to_hash +    @header.dup +  end + +  # As for #each_header, except the keys are provided in capitalized form. +  # +  # Note that header names are capitalized systematically; +  # capitalization may not match that used by the remote HTTP +  # server in its response. +  def each_capitalized +    block_given? or return enum_for(__method__) +    @header.each do |k,v| +      yield capitalize(k), v.join(', ') +    end +  end + +  alias canonical_each each_capitalized + +  def capitalize(name) +    name.split(/-/).map {|s| s.capitalize }.join('-') +  end +  private :capitalize + +  # Returns an Array of Range objects which represent the Range: +  # HTTP header field, or +nil+ if there is no such header. +  def range +    return nil unless @header['range'] + +    value = self['Range'] +    # byte-range-set = *( "," OWS ) ( byte-range-spec / suffix-byte-range-spec ) +    #   *( OWS "," [ OWS ( byte-range-spec / suffix-byte-range-spec ) ] ) +    # corrected collected ABNF +    # http://tools.ietf.org/html/draft-ietf-httpbis-p5-range-19#section-5.4.1 +    # http://tools.ietf.org/html/draft-ietf-httpbis-p5-range-19#appendix-C +    # http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-19#section-3.2.5 +    unless /\Abytes=((?:,[ \t]*)*(?:\d+-\d*|-\d+)(?:[ \t]*,(?:[ \t]*\d+-\d*|-\d+)?)*)\z/ =~ value +      raise Net::HTTPHeaderSyntaxError, "invalid syntax for byte-ranges-specifier: '#{value}'" +    end + +    byte_range_set = $1 +    result = byte_range_set.split(/,/).map {|spec| +      m = /(\d+)?\s*-\s*(\d+)?/i.match(spec) or +              raise Net::HTTPHeaderSyntaxError, "invalid byte-range-spec: '#{spec}'" +      d1 = m[1].to_i +      d2 = m[2].to_i +      if m[1] and m[2] +        if d1 > d2 +          raise Net::HTTPHeaderSyntaxError, "last-byte-pos MUST greater than or equal to first-byte-pos but '#{spec}'" +        end +        d1..d2 +      elsif m[1] +        d1..-1 +      elsif m[2] +        -d2..-1 +      else +        raise Net::HTTPHeaderSyntaxError, 'range is not specified' +      end +    } +    # if result.empty? +    # byte-range-set must include at least one byte-range-spec or suffix-byte-range-spec +    # but above regexp already denies it. +    if result.size == 1 && result[0].begin == 0 && result[0].end == -1 +      raise Net::HTTPHeaderSyntaxError, 'only one suffix-byte-range-spec with zero suffix-length' +    end +    result +  end + +  # Sets the HTTP Range: header. +  # Accepts either a Range object as a single argument, +  # or a beginning index and a length from that index. +  # Example: +  # +  #   req.range = (0..1023) +  #   req.set_range 0, 1023 +  # +  def set_range(r, e = nil) +    unless r +      @header.delete 'range' +      return r +    end +    r = (r...r+e) if e +    case r +    when Numeric +      n = r.to_i +      rangestr = (n > 0 ? "0-#{n-1}" : "-#{-n}") +    when Range +      first = r.first +      last = r.end +      last -= 1 if r.exclude_end? +      if last == -1 +        rangestr = (first > 0 ? "#{first}-" : "-#{-first}") +      else +        raise Net::HTTPHeaderSyntaxError, 'range.first is negative' if first < 0 +        raise Net::HTTPHeaderSyntaxError, 'range.last is negative' if last < 0 +        raise Net::HTTPHeaderSyntaxError, 'must be .first < .last' if first > last +        rangestr = "#{first}-#{last}" +      end +    else +      raise TypeError, 'Range/Integer is required' +    end +    @header['range'] = ["bytes=#{rangestr}"] +    r +  end + +  alias range= set_range + +  # Returns an Integer object which represents the HTTP Content-Length: +  # header field, or +nil+ if that field was not provided. +  def content_length +    return nil unless key?('Content-Length') +    len = self['Content-Length'].slice(/\d+/) or +        raise Net::HTTPHeaderSyntaxError, 'wrong Content-Length format' +    len.to_i +  end + +  def content_length=(len) +    unless len +      @header.delete 'content-length' +      return nil +    end +    @header['content-length'] = [len.to_i.to_s] +  end + +  # Returns "true" if the "transfer-encoding" header is present and +  # set to "chunked".  This is an HTTP/1.1 feature, allowing the +  # the content to be sent in "chunks" without at the outset +  # stating the entire content length. +  def chunked? +    return false unless @header['transfer-encoding'] +    field = self['Transfer-Encoding'] +    (/(?:\A|[^\-\w])chunked(?![\-\w])/i =~ field) ? true : false +  end + +  # Returns a Range object which represents the value of the Content-Range: +  # header field. +  # For a partial entity body, this indicates where this fragment +  # fits inside the full entity body, as range of byte offsets. +  def content_range +    return nil unless @header['content-range'] +    m = %r<bytes\s+(\d+)-(\d+)/(\d+|\*)>i.match(self['Content-Range']) or +        raise Net::HTTPHeaderSyntaxError, 'wrong Content-Range format' +    m[1].to_i .. m[2].to_i +  end + +  # The length of the range represented in Content-Range: header. +  def range_length +    r = content_range() or return nil +    r.end - r.begin + 1 +  end + +  # Returns a content type string such as "text/html". +  # This method returns nil if Content-Type: header field does not exist. +  def content_type +    return nil unless main_type() +    if sub_type() +    then "#{main_type()}/#{sub_type()}" +    else main_type() +    end +  end + +  # Returns a content type string such as "text". +  # This method returns nil if Content-Type: header field does not exist. +  def main_type +    return nil unless @header['content-type'] +    self['Content-Type'].split(';').first.to_s.split('/')[0].to_s.strip +  end + +  # Returns a content type string such as "html". +  # This method returns nil if Content-Type: header field does not exist +  # or sub-type is not given (e.g. "Content-Type: text"). +  def sub_type +    return nil unless @header['content-type'] +    _, sub = *self['Content-Type'].split(';').first.to_s.split('/') +    return nil unless sub +    sub.strip +  end + +  # Any parameters specified for the content type, returned as a Hash. +  # For example, a header of Content-Type: text/html; charset=EUC-JP +  # would result in type_params returning {'charset' => 'EUC-JP'} +  def type_params +    result = {} +    list = self['Content-Type'].to_s.split(';') +    list.shift +    list.each do |param| +      k, v = *param.split('=', 2) +      result[k.strip] = v.strip +    end +    result +  end + +  # Sets the content type in an HTTP header. +  # The +type+ should be a full HTTP content type, e.g. "text/html". +  # The +params+ are an optional Hash of parameters to add after the +  # content type, e.g. {'charset' => 'iso-8859-1'} +  def set_content_type(type, params = {}) +    @header['content-type'] = [type + params.map{|k,v|"; #{k}=#{v}"}.join('')] +  end + +  alias content_type= set_content_type + +  # Set header fields and a body from HTML form data. +  # +params+ should be an Array of Arrays or +  # a Hash containing HTML form data. +  # Optional argument +sep+ means data record separator. +  # +  # Values are URL encoded as necessary and the content-type is set to +  # application/x-www-form-urlencoded +  # +  # Example: +  #    http.form_data = {"q" => "ruby", "lang" => "en"} +  #    http.form_data = {"q" => ["ruby", "perl"], "lang" => "en"} +  #    http.set_form_data({"q" => "ruby", "lang" => "en"}, ';') +  # +  def set_form_data(params, sep = '&') +    query = URI.encode_www_form(params) +    query.gsub!(/&/, sep) if sep != '&' +    self.body = query +    self.content_type = 'application/x-www-form-urlencoded' +  end + +  alias form_data= set_form_data + +  # Set a HTML form data set. +  # +params+ is the form data set; it is an Array of Arrays or a Hash +  # +enctype is the type to encode the form data set. +  # It is application/x-www-form-urlencoded or multipart/form-data. +  # +formpot+ is an optional hash to specify the detail. +  # +  # boundary:: the boundary of the multipart message +  # charset::  the charset of the message. All names and the values of +  #            non-file fields are encoded as the charset. +  # +  # Each item of params is an array and contains following items: +  # +name+::  the name of the field +  # +value+:: the value of the field, it should be a String or a File +  # +opt+::   an optional hash to specify additional information +  # +  # Each item is a file field or a normal field. +  # If +value+ is a File object or the +opt+ have a filename key, +  # the item is treated as a file field. +  # +  # If Transfer-Encoding is set as chunked, this send the request in +  # chunked encoding. Because chunked encoding is HTTP/1.1 feature, +  # you must confirm the server to support HTTP/1.1 before sending it. +  # +  # Example: +  #    http.set_form([["q", "ruby"], ["lang", "en"]]) +  # +  # See also RFC 2388, RFC 2616, HTML 4.01, and HTML5 +  # +  def set_form(params, enctype='application/x-www-form-urlencoded', formopt={}) +    @body_data = params +    @body = nil +    @body_stream = nil +    @form_option = formopt +    case enctype +    when /\Aapplication\/x-www-form-urlencoded\z/i, +      /\Amultipart\/form-data\z/i +      self.content_type = enctype +    else +      raise ArgumentError, "invalid enctype: #{enctype}" +    end +  end + +  # Set the Authorization: header for "Basic" authorization. +  def basic_auth(account, password) +    @header['authorization'] = [basic_encode(account, password)] +  end + +  # Set Proxy-Authorization: header for "Basic" authorization. +  def proxy_basic_auth(account, password) +    @header['proxy-authorization'] = [basic_encode(account, password)] +  end + +  def basic_encode(account, password) +    'Basic ' + ["#{account}:#{password}"].pack('m').delete("\r\n") +  end +  private :basic_encode + +  def connection_close? +    tokens(@header['connection']).include?('close') or +    tokens(@header['proxy-connection']).include?('close') +  end + +  def connection_keep_alive? +    tokens(@header['connection']).include?('keep-alive') or +    tokens(@header['proxy-connection']).include?('keep-alive') +  end + +  def tokens(vals) +    return [] unless vals +    vals.map {|v| v.split(',') }.flatten\ +        .reject {|str| str.strip.empty? }\ +        .map {|tok| tok.strip.downcase } +  end +  private :tokens + +end + diff --git a/jni/ruby/lib/net/http/proxy_delta.rb b/jni/ruby/lib/net/http/proxy_delta.rb new file mode 100644 index 0000000..b16c9f1 --- /dev/null +++ b/jni/ruby/lib/net/http/proxy_delta.rb @@ -0,0 +1,16 @@ +module Net::HTTP::ProxyDelta   #:nodoc: internal use only +  private + +  def conn_address +    proxy_address() +  end + +  def conn_port +    proxy_port() +  end + +  def edit_path(path) +    use_ssl? ? path : "http://#{addr_port()}#{path}" +  end +end + diff --git a/jni/ruby/lib/net/http/request.rb b/jni/ruby/lib/net/http/request.rb new file mode 100644 index 0000000..e8b0f48 --- /dev/null +++ b/jni/ruby/lib/net/http/request.rb @@ -0,0 +1,20 @@ +# HTTP request class. +# This class wraps together the request header and the request path. +# You cannot use this class directly. Instead, you should use one of its +# subclasses: Net::HTTP::Get, Net::HTTP::Post, Net::HTTP::Head. +# +class Net::HTTPRequest < Net::HTTPGenericRequest +  # Creates an HTTP request object for +path+. +  # +  # +initheader+ are the default headers to use.  Net::HTTP adds +  # Accept-Encoding to enable compression of the response body unless +  # Accept-Encoding or Range are supplied in +initheader+. + +  def initialize(path, initheader = nil) +    super self.class::METHOD, +          self.class::REQUEST_HAS_BODY, +          self.class::RESPONSE_HAS_BODY, +          path, initheader +  end +end + diff --git a/jni/ruby/lib/net/http/requests.rb b/jni/ruby/lib/net/http/requests.rb new file mode 100644 index 0000000..c1f8360 --- /dev/null +++ b/jni/ruby/lib/net/http/requests.rb @@ -0,0 +1,122 @@ +# +# HTTP/1.1 methods --- RFC2616 +# + +# See Net::HTTPGenericRequest for attributes and methods. +# See Net::HTTP for usage examples. +class Net::HTTP::Get < Net::HTTPRequest +  METHOD = 'GET' +  REQUEST_HAS_BODY  = false +  RESPONSE_HAS_BODY = true +end + +# See Net::HTTPGenericRequest for attributes and methods. +# See Net::HTTP for usage examples. +class Net::HTTP::Head < Net::HTTPRequest +  METHOD = 'HEAD' +  REQUEST_HAS_BODY = false +  RESPONSE_HAS_BODY = false +end + +# See Net::HTTPGenericRequest for attributes and methods. +# See Net::HTTP for usage examples. +class Net::HTTP::Post < Net::HTTPRequest +  METHOD = 'POST' +  REQUEST_HAS_BODY = true +  RESPONSE_HAS_BODY = true +end + +# See Net::HTTPGenericRequest for attributes and methods. +# See Net::HTTP for usage examples. +class Net::HTTP::Put < Net::HTTPRequest +  METHOD = 'PUT' +  REQUEST_HAS_BODY = true +  RESPONSE_HAS_BODY = true +end + +# See Net::HTTPGenericRequest for attributes and methods. +# See Net::HTTP for usage examples. +class Net::HTTP::Delete < Net::HTTPRequest +  METHOD = 'DELETE' +  REQUEST_HAS_BODY = false +  RESPONSE_HAS_BODY = true +end + +# See Net::HTTPGenericRequest for attributes and methods. +class Net::HTTP::Options < Net::HTTPRequest +  METHOD = 'OPTIONS' +  REQUEST_HAS_BODY = false +  RESPONSE_HAS_BODY = true +end + +# See Net::HTTPGenericRequest for attributes and methods. +class Net::HTTP::Trace < Net::HTTPRequest +  METHOD = 'TRACE' +  REQUEST_HAS_BODY = false +  RESPONSE_HAS_BODY = true +end + +# +# PATCH method --- RFC5789 +# + +# See Net::HTTPGenericRequest for attributes and methods. +class Net::HTTP::Patch < Net::HTTPRequest +  METHOD = 'PATCH' +  REQUEST_HAS_BODY = true +  RESPONSE_HAS_BODY = true +end + +# +# WebDAV methods --- RFC2518 +# + +# See Net::HTTPGenericRequest for attributes and methods. +class Net::HTTP::Propfind < Net::HTTPRequest +  METHOD = 'PROPFIND' +  REQUEST_HAS_BODY = true +  RESPONSE_HAS_BODY = true +end + +# See Net::HTTPGenericRequest for attributes and methods. +class Net::HTTP::Proppatch < Net::HTTPRequest +  METHOD = 'PROPPATCH' +  REQUEST_HAS_BODY = true +  RESPONSE_HAS_BODY = true +end + +# See Net::HTTPGenericRequest for attributes and methods. +class Net::HTTP::Mkcol < Net::HTTPRequest +  METHOD = 'MKCOL' +  REQUEST_HAS_BODY = true +  RESPONSE_HAS_BODY = true +end + +# See Net::HTTPGenericRequest for attributes and methods. +class Net::HTTP::Copy < Net::HTTPRequest +  METHOD = 'COPY' +  REQUEST_HAS_BODY = false +  RESPONSE_HAS_BODY = true +end + +# See Net::HTTPGenericRequest for attributes and methods. +class Net::HTTP::Move < Net::HTTPRequest +  METHOD = 'MOVE' +  REQUEST_HAS_BODY = false +  RESPONSE_HAS_BODY = true +end + +# See Net::HTTPGenericRequest for attributes and methods. +class Net::HTTP::Lock < Net::HTTPRequest +  METHOD = 'LOCK' +  REQUEST_HAS_BODY = true +  RESPONSE_HAS_BODY = true +end + +# See Net::HTTPGenericRequest for attributes and methods. +class Net::HTTP::Unlock < Net::HTTPRequest +  METHOD = 'UNLOCK' +  REQUEST_HAS_BODY = true +  RESPONSE_HAS_BODY = true +end + diff --git a/jni/ruby/lib/net/http/response.rb b/jni/ruby/lib/net/http/response.rb new file mode 100644 index 0000000..126c221 --- /dev/null +++ b/jni/ruby/lib/net/http/response.rb @@ -0,0 +1,416 @@ +# HTTP response class. +# +# This class wraps together the response header and the response body (the +# entity requested). +# +# It mixes in the HTTPHeader module, which provides access to response +# header values both via hash-like methods and via individual readers. +# +# Note that each possible HTTP response code defines its own +# HTTPResponse subclass.  These are listed below. +# +# All classes are defined under the Net module. Indentation indicates +# inheritance.  For a list of the classes see Net::HTTP. +# +# +class Net::HTTPResponse +  class << self +    # true if the response has a body. +    def body_permitted? +      self::HAS_BODY +    end + +    def exception_type   # :nodoc: internal use only +      self::EXCEPTION_TYPE +    end + +    def read_new(sock)   #:nodoc: internal use only +      httpv, code, msg = read_status_line(sock) +      res = response_class(code).new(httpv, code, msg) +      each_response_header(sock) do |k,v| +        res.add_field k, v +      end +      res +    end + +    private + +    def read_status_line(sock) +      str = sock.readline +      m = /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)(?:\s+(.*))?\z/in.match(str) or +        raise Net::HTTPBadResponse, "wrong status line: #{str.dump}" +      m.captures +    end + +    def response_class(code) +      CODE_TO_OBJ[code] or +      CODE_CLASS_TO_OBJ[code[0,1]] or +      Net::HTTPUnknownResponse +    end + +    def each_response_header(sock) +      key = value = nil +      while true +        line = sock.readuntil("\n", true).sub(/\s+\z/, '') +        break if line.empty? +        if line[0] == ?\s or line[0] == ?\t and value +          value << ' ' unless value.empty? +          value << line.strip +        else +          yield key, value if key +          key, value = line.strip.split(/\s*:\s*/, 2) +          raise Net::HTTPBadResponse, 'wrong header line format' if value.nil? +        end +      end +      yield key, value if key +    end +  end + +  # next is to fix bug in RDoc, where the private inside class << self +  # spills out. +  public + +  include Net::HTTPHeader + +  def initialize(httpv, code, msg)   #:nodoc: internal use only +    @http_version = httpv +    @code         = code +    @message      = msg +    initialize_http_header nil +    @body = nil +    @read = false +    @uri  = nil +    @decode_content = false +  end + +  # The HTTP version supported by the server. +  attr_reader :http_version + +  # The HTTP result code string. For example, '302'.  You can also +  # determine the response type by examining which response subclass +  # the response object is an instance of. +  attr_reader :code + +  # The HTTP result message sent by the server. For example, 'Not Found'. +  attr_reader :message +  alias msg message   # :nodoc: obsolete + +  # The URI used to fetch this response.  The response URI is only available +  # if a URI was used to create the request. +  attr_reader :uri + +  # Set to true automatically when the request did not contain an +  # Accept-Encoding header from the user. +  attr_accessor :decode_content + +  def inspect +    "#<#{self.class} #{@code} #{@message} readbody=#{@read}>" +  end + +  # +  # response <-> exception relationship +  # + +  def code_type   #:nodoc: +    self.class +  end + +  def error!   #:nodoc: +    raise error_type().new(@code + ' ' + @message.dump, self) +  end + +  def error_type   #:nodoc: +    self.class::EXCEPTION_TYPE +  end + +  # Raises an HTTP error if the response is not 2xx (success). +  def value +    error! unless self.kind_of?(Net::HTTPSuccess) +  end + +  def uri= uri # :nodoc: +    @uri = uri.dup if uri +  end + +  # +  # header (for backward compatibility only; DO NOT USE) +  # + +  def response   #:nodoc: +    warn "#{caller(1)[0]}: warning: Net::HTTPResponse#response is obsolete" if $VERBOSE +    self +  end + +  def header   #:nodoc: +    warn "#{caller(1)[0]}: warning: Net::HTTPResponse#header is obsolete" if $VERBOSE +    self +  end + +  def read_header   #:nodoc: +    warn "#{caller(1)[0]}: warning: Net::HTTPResponse#read_header is obsolete" if $VERBOSE +    self +  end + +  # +  # body +  # + +  def reading_body(sock, reqmethodallowbody)  #:nodoc: internal use only +    @socket = sock +    @body_exist = reqmethodallowbody && self.class.body_permitted? +    begin +      yield +      self.body   # ensure to read body +    ensure +      @socket = nil +    end +  end + +  # Gets the entity body returned by the remote HTTP server. +  # +  # If a block is given, the body is passed to the block, and +  # the body is provided in fragments, as it is read in from the socket. +  # +  # Calling this method a second or subsequent time for the same +  # HTTPResponse object will return the value already read. +  # +  #   http.request_get('/index.html') {|res| +  #     puts res.read_body +  #   } +  # +  #   http.request_get('/index.html') {|res| +  #     p res.read_body.object_id   # 538149362 +  #     p res.read_body.object_id   # 538149362 +  #   } +  # +  #   # using iterator +  #   http.request_get('/index.html') {|res| +  #     res.read_body do |segment| +  #       print segment +  #     end +  #   } +  # +  def read_body(dest = nil, &block) +    if @read +      raise IOError, "#{self.class}\#read_body called twice" if dest or block +      return @body +    end +    to = procdest(dest, block) +    stream_check +    if @body_exist +      read_body_0 to +      @body = to +    else +      @body = nil +    end +    @read = true + +    @body +  end + +  # Returns the full entity body. +  # +  # Calling this method a second or subsequent time will return the +  # string already read. +  # +  #   http.request_get('/index.html') {|res| +  #     puts res.body +  #   } +  # +  #   http.request_get('/index.html') {|res| +  #     p res.body.object_id   # 538149362 +  #     p res.body.object_id   # 538149362 +  #   } +  # +  def body +    read_body() +  end + +  # Because it may be necessary to modify the body, Eg, decompression +  # this method facilitates that. +  def body=(value) +    @body = value +  end + +  alias entity body   #:nodoc: obsolete + +  private + +  ## +  # Checks for a supported Content-Encoding header and yields an Inflate +  # wrapper for this response's socket when zlib is present.  If the +  # Content-Encoding is unsupported or zlib is missing the plain socket is +  # yielded. +  # +  # If a Content-Range header is present a plain socket is yielded as the +  # bytes in the range may not be a complete deflate block. + +  def inflater # :nodoc: +    return yield @socket unless Net::HTTP::HAVE_ZLIB +    return yield @socket unless @decode_content +    return yield @socket if self['content-range'] + +    v = self['content-encoding'] +    case v && v.downcase +    when 'deflate', 'gzip', 'x-gzip' then +      self.delete 'content-encoding' + +      inflate_body_io = Inflater.new(@socket) + +      begin +        yield inflate_body_io +      ensure +        orig_err = $! +        begin +          inflate_body_io.finish +        rescue => err +          raise orig_err || err +        end +      end +    when 'none', 'identity' then +      self.delete 'content-encoding' + +      yield @socket +    else +      yield @socket +    end +  end + +  def read_body_0(dest) +    inflater do |inflate_body_io| +      if chunked? +        read_chunked dest, inflate_body_io +        return +      end + +      @socket = inflate_body_io + +      clen = content_length() +      if clen +        @socket.read clen, dest, true   # ignore EOF +        return +      end +      clen = range_length() +      if clen +        @socket.read clen, dest +        return +      end +      @socket.read_all dest +    end +  end + +  ## +  # read_chunked reads from +@socket+ for chunk-size, chunk-extension, CRLF, +  # etc. and +chunk_data_io+ for chunk-data which may be deflate or gzip +  # encoded. +  # +  # See RFC 2616 section 3.6.1 for definitions + +  def read_chunked(dest, chunk_data_io) # :nodoc: +    total = 0 +    while true +      line = @socket.readline +      hexlen = line.slice(/[0-9a-fA-F]+/) or +          raise Net::HTTPBadResponse, "wrong chunk size line: #{line}" +      len = hexlen.hex +      break if len == 0 +      begin +        chunk_data_io.read len, dest +      ensure +        total += len +        @socket.read 2   # \r\n +      end +    end +    until @socket.readline.empty? +      # none +    end +  end + +  def stream_check +    raise IOError, 'attempt to read body out of block' if @socket.closed? +  end + +  def procdest(dest, block) +    raise ArgumentError, 'both arg and block given for HTTP method' if +      dest and block +    if block +      Net::ReadAdapter.new(block) +    else +      dest || '' +    end +  end + +  ## +  # Inflater is a wrapper around Net::BufferedIO that transparently inflates +  # zlib and gzip streams. + +  class Inflater # :nodoc: + +    ## +    # Creates a new Inflater wrapping +socket+ + +    def initialize socket +      @socket = socket +      # zlib with automatic gzip detection +      @inflate = Zlib::Inflate.new(32 + Zlib::MAX_WBITS) +    end + +    ## +    # Finishes the inflate stream. + +    def finish +      return if @inflate.total_in == 0 +      @inflate.finish +    end + +    ## +    # Returns a Net::ReadAdapter that inflates each read chunk into +dest+. +    # +    # This allows a large response body to be inflated without storing the +    # entire body in memory. + +    def inflate_adapter(dest) +      if dest.respond_to?(:set_encoding) +        dest.set_encoding(Encoding::ASCII_8BIT) +      elsif dest.respond_to?(:force_encoding) +        dest.force_encoding(Encoding::ASCII_8BIT) +      end +      block = proc do |compressed_chunk| +        @inflate.inflate(compressed_chunk) do |chunk| +          dest << chunk +        end +      end + +      Net::ReadAdapter.new(block) +    end + +    ## +    # Reads +clen+ bytes from the socket, inflates them, then writes them to +    # +dest+.  +ignore_eof+ is passed down to Net::BufferedIO#read +    # +    # Unlike Net::BufferedIO#read, this method returns more than +clen+ bytes. +    # At this time there is no way for a user of Net::HTTPResponse to read a +    # specific number of bytes from the HTTP response body, so this internal +    # API does not return the same number of bytes as were requested. +    # +    # See https://bugs.ruby-lang.org/issues/6492 for further discussion. + +    def read clen, dest, ignore_eof = false +      temp_dest = inflate_adapter(dest) + +      @socket.read clen, temp_dest, ignore_eof +    end + +    ## +    # Reads the rest of the socket, inflates it, then writes it to +dest+. + +    def read_all dest +      temp_dest = inflate_adapter(dest) + +      @socket.read_all temp_dest +    end + +  end + +end + diff --git a/jni/ruby/lib/net/http/responses.rb b/jni/ruby/lib/net/http/responses.rb new file mode 100644 index 0000000..1454a27 --- /dev/null +++ b/jni/ruby/lib/net/http/responses.rb @@ -0,0 +1,273 @@ +# :stopdoc: +class Net::HTTPUnknownResponse < Net::HTTPResponse +  HAS_BODY = true +  EXCEPTION_TYPE = Net::HTTPError +end +class Net::HTTPInformation < Net::HTTPResponse           # 1xx +  HAS_BODY = false +  EXCEPTION_TYPE = Net::HTTPError +end +class Net::HTTPSuccess < Net::HTTPResponse               # 2xx +  HAS_BODY = true +  EXCEPTION_TYPE = Net::HTTPError +end +class Net::HTTPRedirection < Net::HTTPResponse           # 3xx +  HAS_BODY = true +  EXCEPTION_TYPE = Net::HTTPRetriableError +end +class Net::HTTPClientError < Net::HTTPResponse           # 4xx +  HAS_BODY = true +  EXCEPTION_TYPE = Net::HTTPServerException   # for backward compatibility +end +class Net::HTTPServerError < Net::HTTPResponse           # 5xx +  HAS_BODY = true +  EXCEPTION_TYPE = Net::HTTPFatalError    # for backward compatibility +end + +class Net::HTTPContinue < Net::HTTPInformation           # 100 +  HAS_BODY = false +end +class Net::HTTPSwitchProtocol < Net::HTTPInformation     # 101 +  HAS_BODY = false +end +# 102 - RFC 2518; removed in RFC 4918 + +class Net::HTTPOK < Net::HTTPSuccess                            # 200 +  HAS_BODY = true +end +class Net::HTTPCreated < Net::HTTPSuccess                       # 201 +  HAS_BODY = true +end +class Net::HTTPAccepted < Net::HTTPSuccess                      # 202 +  HAS_BODY = true +end +class Net::HTTPNonAuthoritativeInformation < Net::HTTPSuccess   # 203 +  HAS_BODY = true +end +class Net::HTTPNoContent < Net::HTTPSuccess                     # 204 +  HAS_BODY = false +end +class Net::HTTPResetContent < Net::HTTPSuccess                  # 205 +  HAS_BODY = false +end +class Net::HTTPPartialContent < Net::HTTPSuccess                # 206 +  HAS_BODY = true +end +class Net::HTTPMultiStatus < Net::HTTPSuccess                   # 207 - RFC 4918 +  HAS_BODY = true +end +# 208 Already Reported - RFC 5842; experimental +class Net::HTTPIMUsed < Net::HTTPSuccess                        # 226 - RFC 3229 +  HAS_BODY = true +end + +class Net::HTTPMultipleChoices < Net::HTTPRedirection    # 300 +  HAS_BODY = true +end +Net::HTTPMultipleChoice = Net::HTTPMultipleChoices +class Net::HTTPMovedPermanently < Net::HTTPRedirection   # 301 +  HAS_BODY = true +end +class Net::HTTPFound < Net::HTTPRedirection              # 302 +  HAS_BODY = true +end +Net::HTTPMovedTemporarily = Net::HTTPFound +class Net::HTTPSeeOther < Net::HTTPRedirection           # 303 +  HAS_BODY = true +end +class Net::HTTPNotModified < Net::HTTPRedirection        # 304 +  HAS_BODY = false +end +class Net::HTTPUseProxy < Net::HTTPRedirection           # 305 +  HAS_BODY = false +end +# 306 Switch Proxy - no longer unused +class Net::HTTPTemporaryRedirect < Net::HTTPRedirection  # 307 +  HAS_BODY = true +end +class Net::HTTPPermanentRedirect < Net::HTTPRedirection  # 308 +  HAS_BODY = true +end + +class Net::HTTPBadRequest < Net::HTTPClientError                    # 400 +  HAS_BODY = true +end +class Net::HTTPUnauthorized < Net::HTTPClientError                  # 401 +  HAS_BODY = true +end +class Net::HTTPPaymentRequired < Net::HTTPClientError               # 402 +  HAS_BODY = true +end +class Net::HTTPForbidden < Net::HTTPClientError                     # 403 +  HAS_BODY = true +end +class Net::HTTPNotFound < Net::HTTPClientError                      # 404 +  HAS_BODY = true +end +class Net::HTTPMethodNotAllowed < Net::HTTPClientError              # 405 +  HAS_BODY = true +end +class Net::HTTPNotAcceptable < Net::HTTPClientError                 # 406 +  HAS_BODY = true +end +class Net::HTTPProxyAuthenticationRequired < Net::HTTPClientError   # 407 +  HAS_BODY = true +end +class Net::HTTPRequestTimeOut < Net::HTTPClientError                # 408 +  HAS_BODY = true +end +class Net::HTTPConflict < Net::HTTPClientError                      # 409 +  HAS_BODY = true +end +class Net::HTTPGone < Net::HTTPClientError                          # 410 +  HAS_BODY = true +end +class Net::HTTPLengthRequired < Net::HTTPClientError                # 411 +  HAS_BODY = true +end +class Net::HTTPPreconditionFailed < Net::HTTPClientError            # 412 +  HAS_BODY = true +end +class Net::HTTPRequestEntityTooLarge < Net::HTTPClientError         # 413 +  HAS_BODY = true +end +class Net::HTTPRequestURITooLong < Net::HTTPClientError             # 414 +  HAS_BODY = true +end +Net::HTTPRequestURITooLarge = Net::HTTPRequestURITooLong +class Net::HTTPUnsupportedMediaType < Net::HTTPClientError          # 415 +  HAS_BODY = true +end +class Net::HTTPRequestedRangeNotSatisfiable < Net::HTTPClientError  # 416 +  HAS_BODY = true +end +class Net::HTTPExpectationFailed < Net::HTTPClientError             # 417 +  HAS_BODY = true +end +# 418 I'm a teapot - RFC 2324; a joke RFC +# 420 Enhance Your Calm - Twitter +class Net::HTTPUnprocessableEntity < Net::HTTPClientError           # 422 - RFC 4918 +  HAS_BODY = true +end +class Net::HTTPLocked < Net::HTTPClientError                        # 423 - RFC 4918 +  HAS_BODY = true +end +class Net::HTTPFailedDependency < Net::HTTPClientError              # 424 - RFC 4918 +  HAS_BODY = true +end +# 425 Unordered Collection - existed only in draft +class Net::HTTPUpgradeRequired < Net::HTTPClientError               # 426 - RFC 2817 +  HAS_BODY = true +end +class Net::HTTPPreconditionRequired < Net::HTTPClientError          # 428 - RFC 6585 +  HAS_BODY = true +end +class Net::HTTPTooManyRequests < Net::HTTPClientError               # 429 - RFC 6585 +  HAS_BODY = true +end +class Net::HTTPRequestHeaderFieldsTooLarge < Net::HTTPClientError   # 431 - RFC 6585 +  HAS_BODY = true +end +# 444 No Response - Nginx +# 449 Retry With - Microsoft +# 450 Blocked by Windows Parental Controls - Microsoft +# 499 Client Closed Request - Nginx + +class Net::HTTPInternalServerError < Net::HTTPServerError           # 500 +  HAS_BODY = true +end +class Net::HTTPNotImplemented < Net::HTTPServerError                # 501 +  HAS_BODY = true +end +class Net::HTTPBadGateway < Net::HTTPServerError                    # 502 +  HAS_BODY = true +end +class Net::HTTPServiceUnavailable < Net::HTTPServerError            # 503 +  HAS_BODY = true +end +class Net::HTTPGatewayTimeOut < Net::HTTPServerError                # 504 +  HAS_BODY = true +end +class Net::HTTPVersionNotSupported < Net::HTTPServerError           # 505 +  HAS_BODY = true +end +# 506 Variant Also Negotiates - RFC 2295; experimental +class Net::HTTPInsufficientStorage < Net::HTTPServerError           # 507 - RFC 4918 +  HAS_BODY = true +end +# 508 Loop Detected - RFC 5842; experimental +# 509 Bandwidth Limit Exceeded - Apache bw/limited extension +# 510 Not Extended - RFC 2774; experimental +class Net::HTTPNetworkAuthenticationRequired < Net::HTTPServerError # 511 - RFC 6585 +  HAS_BODY = true +end + +class Net::HTTPResponse +  CODE_CLASS_TO_OBJ = { +    '1' => Net::HTTPInformation, +    '2' => Net::HTTPSuccess, +    '3' => Net::HTTPRedirection, +    '4' => Net::HTTPClientError, +    '5' => Net::HTTPServerError +  } +  CODE_TO_OBJ = { +    '100' => Net::HTTPContinue, +    '101' => Net::HTTPSwitchProtocol, + +    '200' => Net::HTTPOK, +    '201' => Net::HTTPCreated, +    '202' => Net::HTTPAccepted, +    '203' => Net::HTTPNonAuthoritativeInformation, +    '204' => Net::HTTPNoContent, +    '205' => Net::HTTPResetContent, +    '206' => Net::HTTPPartialContent, +    '207' => Net::HTTPMultiStatus, +    '226' => Net::HTTPIMUsed, + +    '300' => Net::HTTPMultipleChoices, +    '301' => Net::HTTPMovedPermanently, +    '302' => Net::HTTPFound, +    '303' => Net::HTTPSeeOther, +    '304' => Net::HTTPNotModified, +    '305' => Net::HTTPUseProxy, +    '307' => Net::HTTPTemporaryRedirect, + +    '400' => Net::HTTPBadRequest, +    '401' => Net::HTTPUnauthorized, +    '402' => Net::HTTPPaymentRequired, +    '403' => Net::HTTPForbidden, +    '404' => Net::HTTPNotFound, +    '405' => Net::HTTPMethodNotAllowed, +    '406' => Net::HTTPNotAcceptable, +    '407' => Net::HTTPProxyAuthenticationRequired, +    '408' => Net::HTTPRequestTimeOut, +    '409' => Net::HTTPConflict, +    '410' => Net::HTTPGone, +    '411' => Net::HTTPLengthRequired, +    '412' => Net::HTTPPreconditionFailed, +    '413' => Net::HTTPRequestEntityTooLarge, +    '414' => Net::HTTPRequestURITooLong, +    '415' => Net::HTTPUnsupportedMediaType, +    '416' => Net::HTTPRequestedRangeNotSatisfiable, +    '417' => Net::HTTPExpectationFailed, +    '422' => Net::HTTPUnprocessableEntity, +    '423' => Net::HTTPLocked, +    '424' => Net::HTTPFailedDependency, +    '426' => Net::HTTPUpgradeRequired, +    '428' => Net::HTTPPreconditionRequired, +    '429' => Net::HTTPTooManyRequests, +    '431' => Net::HTTPRequestHeaderFieldsTooLarge, + +    '500' => Net::HTTPInternalServerError, +    '501' => Net::HTTPNotImplemented, +    '502' => Net::HTTPBadGateway, +    '503' => Net::HTTPServiceUnavailable, +    '504' => Net::HTTPGatewayTimeOut, +    '505' => Net::HTTPVersionNotSupported, +    '507' => Net::HTTPInsufficientStorage, +    '511' => Net::HTTPNetworkAuthenticationRequired, +  } +end + +# :startdoc: + | 
