summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoraamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2004-03-06 20:35:19 +0000
committeraamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2004-03-06 20:35:19 +0000
commiteae8f02fb138fb9a36bbe8baf51a421eff92191a (patch)
tree35aee9fac968f89407ee34569d19b2777cdff22a
parentd12cbc68e054bd58c935097c5c58a9b06425b359 (diff)
downloadruby-eae8f02fb138fb9a36bbe8baf51a421eff92191a.tar.gz
ruby-eae8f02fb138fb9a36bbe8baf51a421eff92191a.tar.xz
ruby-eae8f02fb138fb9a36bbe8baf51a421eff92191a.zip
* 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: http://svn.ruby-lang.org/repos/ruby/trunk@5910 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r--ChangeLog14
-rw-r--r--lib/net/http.rb179
-rw-r--r--test/net/http/test_httpheader.rb87
3 files changed, 227 insertions, 53 deletions
diff --git a/ChangeLog b/ChangeLog
index ac3bd7cb1..0a5338f9e 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,17 @@
+Sun Mar 7 05:34:42 2004 Minero Aoki <aamine@loveruby.net>
+
+ * 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.
+
Sun Mar 7 03:11:00 2004 Minero Aoki <aamine@loveruby.net>
* lib/net/http.rb: new method Net::HTTPRequest#body(=).
diff --git a/lib/net/http.rb b/lib/net/http.rb
index ce26c6f0f..9b3ee5960 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
diff --git a/test/net/http/test_httpheader.rb b/test/net/http/test_httpheader.rb
index 196369fa7..4a133b44d 100644
--- a/test/net/http/test_httpheader.rb
+++ b/test/net/http/test_httpheader.rb
@@ -49,6 +49,30 @@ class HTTPHeaderTest < Test::Unit::TestCase
@c['Next-Header'] = 'next string'
assert_equal 'next string', @c['next-header']
end
+
+ def test_add_field
+ end
+
+ def test_get_fields
+ end
+
+ def test_delete
+ end
+
+ def test_each
+ end
+
+ def test_each_key
+ end
+
+ def test_each_value
+ end
+
+ def test_key?
+ end
+
+ def test_to_hash
+ end
def test_range
try_range(1..5, '1-5')
@@ -59,8 +83,7 @@ class HTTPHeaderTest < Test::Unit::TestCase
def try_range(r, s)
@c['range'] = "bytes=#{s}"
- ret, = @c.range
- assert_equal r, ret
+ assert_equal r, Array(@c.range)[0]
end
def test_range=
@@ -76,6 +99,18 @@ class HTTPHeaderTest < Test::Unit::TestCase
assert_equal 'bytes=0-499', @c['range']
end
+ def test_content_range
+ end
+
+ def test_range_length
+ @c['Content-Range'] = "bytes 0-499/1000"
+ assert_equal 500, @c.range_length
+ @c['Content-Range'] = "bytes 1-500/1000"
+ assert_equal 500, @c.range_length
+ @c['Content-Range'] = "bytes 1-1/1000"
+ assert_equal 1, @c.range_length
+ end
+
def test_chunked?
try_chunked true, 'chunked'
try_chunked true, ' chunked '
@@ -110,28 +145,60 @@ class HTTPHeaderTest < Test::Unit::TestCase
assert_equal len, @c.content_length
end
- def test_content_range
+ def test_content_length=
+ @c.content_length = 0
+ assert_equal 0, @c.content_length
+ @c.content_length = 1
+ assert_equal 1, @c.content_length
+ @c.content_length = 999
+ assert_equal 999, @c.content_length
+ @c.content_length = 10000000000000
+ assert_equal 10000000000000, @c.content_length
end
- def test_delete
+ def test_content_type
+ @c.content_type = 'text/html'
+ assert_equal 'text/html', @c.content_type
+ @c.content_type = 'application/pdf'
+ assert_equal 'application/pdf', @c.content_type
+ @c.set_content_type 'text/html', {'charset' => 'iso-2022-jp'}
+ assert_equal 'text/html', @c.content_type
end
- def test_each
+ def test_main_type
+ @c.content_type = 'text/html'
+ assert_equal 'text', @c.main_type
+ @c.content_type = 'application/pdf'
+ assert_equal 'application', @c.main_type
+ @c.set_content_type 'text/html', {'charset' => 'iso-2022-jp'}
+ assert_equal 'text', @c.main_type
end
- def test_each_key
+ def test_sub_type
+ @c.content_type = 'text/html'
+ assert_equal 'html', @c.sub_type
+ @c.content_type = 'application/pdf'
+ assert_equal 'pdf', @c.sub_type
+ @c.set_content_type 'text/html', {'charset' => 'iso-2022-jp'}
+ assert_equal 'html', @c.sub_type
end
- def test_each_value
+ def test_type_params
+ @c.content_type = 'text/html'
+ assert_equal({}, @c.type_params)
+ @c.content_type = 'application/pdf'
+ assert_equal({}, @c.type_params)
+ @c.set_content_type 'text/html', {'charset' => 'iso-2022-jp'}
+ assert_equal({'charset' => 'iso-2022-jp'}, @c.type_params)
end
- def test_key?
+ def test_set_content_type
end
- def test_range_length
+ def test_basic_auth
end
- def test_to_hash
+ def test_proxy_basic_auth
end
end