diff options
author | aamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2004-03-06 20:35:19 +0000 |
---|---|---|
committer | aamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e> | 2004-03-06 20:35:19 +0000 |
commit | 39848fe845664948df5102837c84aca0973efeec (patch) | |
tree | 35aee9fac968f89407ee34569d19b2777cdff22a /lib | |
parent | 316bf4e67bff2960945735bb2efb629f1ed13e9e (diff) | |
download | ruby-39848fe845664948df5102837c84aca0973efeec.tar.gz |
* lib/net/http.rb: HTTPHeader keeps its header fields as an array.
* lib/net/http.rb: new method HTTPHeader#add_header, get_fields.
* lib/net/http.rb: new method HTTPHeader#content_length=.
* lib/net/http.rb: new method HTTPHeader#content_type, main_type, sub_type, type_params, content_type=, set_content_type.
* lib/net/http.rb (HTTPHeader#basic_encode): result of pack(m) may contain multiple LFs.
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@5910 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib')
-rw-r--r-- | lib/net/http.rb | 179 |
1 files changed, 136 insertions, 43 deletions
diff --git a/lib/net/http.rb b/lib/net/http.rb index ce26c6f0f3..9b3ee5960b 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1021,36 +1021,88 @@ module Net # :nodoc: # Returns the header field corresponding to the case-insensitive key. # For example, a key of "Content-Type" might return "text/html" def [](key) - @header[key.downcase] + a = @header[key.downcase] or return nil + a.join(', ') end # Sets the header field corresponding to the case-insensitive key. def []=(key, val) - @header[key.downcase] = val + unless val + @header.delete key.downcase + return val + end + @header[key.downcase] = Array(val).map {|s| s.to_str } + end + + # Adds header name and field instead of replace. + # + # request.add_header 'X-My-Header', 'a' + # p request['X-My-Header'] #=> "a" + # request.add_header 'X-My-Header', 'b' + # p request['X-My-Header'] #=> "a, b" + # request.add_header 'X-My-Header', 'c' + # p request['X-My-Header'] #=> "a, b, c" + # p request.get_fields('X-My-Header') #=> ["a", "b", "c"] + # + def add_header(key, val) + if header.key?(key.downcase) + @header[key.downcase].concat Array(val) + else + @header[key.downcase] = Array(val).dup + end + end + + # Returns the header field by Array, corresponding to the + # case-insensitive key. This method allows you to get duplicated + # fields without any processing. + # + # 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 nil, # if there's no header field named key. See Hash#fetch def fetch(key, *args, &block) #:yield: +key+ - @header.fetch(key.downcase, *args, &block) + a = @header.fetch(key.downcase, *args, &block) + a.join(', ') end # Iterates for each header names and values. - def each_header(&block) #:yield: +key+, +value+ - @header.each(&block) + def each_header #:yield: +key+, +value+ + @header.each do |k,va| + yield k, va.join(', ') + end end alias each each_header # Iterates for each header names. - def each_key(&block) #:yield: +key+ - @header.each_key(&block) + def each_name(&block) #:yield: +key+ +e @header.each_key(&block) + end + + alias each_key each_name + + # Iterates for each capitalized header names. + def each_capitalized_name(&block) #:yield: +key+ + @header.each_key do |k| + yield capitalize(k) + end end # Iterates for each header values. - def each_value(&block) #:yield: +value+ - @header.each_value(&block) + def each_value #:yield: +value+ + @header.each_value do |va| + yield va.join(', ') + end end # Removes a header field. @@ -1077,16 +1129,16 @@ module Net # :nodoc: alias canonical_each each_capitalized - def capitalize(k) - k.split(/-/).map {|i| i.capitalize }.join('-') + def capitalize(name) + name.split(/-/).map {|s| s.capitalize }.join('-') end private :capitalize - # Returns a Range object which represents Range: header field, + # Returns an Array of Range objects which represents Range: header field, # or +nil+ if there is no such header. def range - s = @header['range'] or return nil - s.split(/,/).map {|spec| + return nil unless @header['range'] + self['Range'].split(/,/).map {|spec| m = /bytes\s*=\s*(\d+)?\s*-\s*(\d+)?/i.match(spec) or raise HTTPHeaderSyntaxError, "wrong Range: #{spec}" d1 = m[1].to_i @@ -1101,12 +1153,21 @@ module Net # :nodoc: end # Set Range: header from Range (arg r) or beginning index and - # length from it (arg i&len). - def range=(r, fin = nil) - r = (r ... r + fin) if fin + # length from it (arg idx&len). + # + # 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 - rangestr = (r > 0 ? "0-#{r.to_i - 1}" : "-#{-r.to_i}") + n = r.to_i + rangestr = (n > 0 ? "0-#{n-1}" : "-#{-n}") when Range first = r.first last = r.last @@ -1122,28 +1183,37 @@ module Net # :nodoc: else raise TypeError, 'Range/Integer is required' end - @header['range'] = "bytes=#{rangestr}" + @header['range'] = ["bytes=#{rangestr}"] r end - alias set_range range= + alias range= set_range # Returns an Integer object which represents the Content-Length: header field # or +nil+ if that field is not provided. def content_length - return nil unless @header.key?('content-length') - len = @header['content-length'].slice(/\d+/) or + return nil unless key?('Content-Length') + len = self['Content-Length'].slice(/\d+/) or raise 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? - field = @header['transfer-encoding'] or return false - (/(?:\A|[^\-\w])chunked(?:[^\-\w]|\z)/i =~ field) ? true : false + 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 Content-Range: header field. @@ -1151,29 +1221,58 @@ module Net # :nodoc: # 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(@header['content-range']) or + m = %r<bytes\s+(\d+)-(\d+)/(\d+|\*)>i.match(self['Content-Range']) or raise HTTPHeaderSyntaxError, 'wrong Content-Range format' m[1].to_i .. m[2].to_i + 1 end - # The length of the range represented in Range: header. + # The length of the range represented in Content-Range: header. def range_length r = content_range() or return nil r.end - r.begin end + def content_type + "#{main_type()}/#{sub_type()}" + end + + def main_type + return nil unless @header['content-type'] + self['Content-Type'].split(';').first.to_s.split('/')[0].to_s.strip + end + + def sub_type + return nil unless @header['content-type'] + self['Content-Type'].split(';').first.to_s.split('/')[1].to_s.strip + end + + def type_params + result = {} + self['Content-Type'].to_s.split(';')[1..-1].each do |param| + k, v = *param.split('=', 2) + result[k.strip] = v.strip + end + result + end + + 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 the Authorization: header for "Basic" authorization. def basic_auth(account, password) - @header['authorization'] = basic_encode(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) + @header['proxy-authorization'] = [basic_encode(account, password)] end def basic_encode(account, password) - 'Basic ' + ["#{account}:#{password}"].pack('m').strip + 'Basic ' + ["#{account}:#{password}"].pack('m').delete("\r\n") end private :basic_encode @@ -1269,23 +1368,21 @@ module Net # :nodoc: private def send_request_with_body(sock, ver, path, body) - @header['content-length'] = body.length.to_s - @header.delete 'transfer-encoding' - unless @header['content-type'] + self.content_length = body.length + delete 'Transfer-Encoding' + unless content_type() warn 'net/http: warning: Content-Type did not set; using application/x-www-form-urlencoded' if $VERBOSE - @header['content-type'] = 'application/x-www-form-urlencoded' + set_content_type 'application/x-www-form-urlencoded' end write_header sock, ver, path sock.write body end def send_request_with_body_stream(sock, ver, path, f) - unless @header['content-length'] - raise ArgumentError, "request body Content-Length not given but Transfer-Encoding is not `chunked'; give up" unless chunked? - end - unless @header['content-type'] + raise ArgumentError, "Content-Length not given and Transfer-Encoding is not `chunked'" unless content_length() or chunked? + unless content_type() warn 'net/http: warning: Content-Type did not set; using application/x-www-form-urlencoded' if $VERBOSE - @header['content-type'] = 'application/x-www-form-urlencoded' + set_content_type 'application/x-www-form-urlencoded' end write_header sock, ver, path if chunked? @@ -1730,11 +1827,7 @@ module Net # :nodoc: httpv, code, msg = read_status_line(sock) res = response_class(code).new(httpv, code, msg) each_response_header(sock) do |k,v| - if res.key?(k) - res[k] << ', ' << v - else - res[k] = v - end + res.add_header k, v end res end |