summaryrefslogtreecommitdiff
path: root/lib/net
diff options
context:
space:
mode:
authoraamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2005-09-13 07:27:18 +0000
committeraamine <aamine@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2005-09-13 07:27:18 +0000
commite99c067d8eabedf3210ab7531f0ca9b910809b1b (patch)
tree9cfb8e7bc1fc6ba3f772a3528552a8834f37466c /lib/net
parentbf20e3b82ef1271d4648e6db0abd2237fc490247 (diff)
* lib/net/http.rb: backported from trunk, rev 1.128. [ruby-dev:25673] [ruby-dev:26617]
* lib/net/protocol.rb: backported from trunk, rev 1.78. * lib/net/protocol.rb: new method #old_open to support net/smtp and net/pop. * lib/net/smtp.rb: use #old_open. * lib/net/pop.rb: ditto. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/branches/ruby_1_8@9146 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'lib/net')
-rw-r--r--lib/net/http.rb841
-rw-r--r--lib/net/pop.rb2
-rw-r--r--lib/net/protocol.rb392
-rw-r--r--lib/net/smtp.rb2
4 files changed, 781 insertions, 456 deletions
diff --git a/lib/net/http.rb b/lib/net/http.rb
index a0b46e117d..51d2e7c9d3 100644
--- a/lib/net/http.rb
+++ b/lib/net/http.rb
@@ -3,8 +3,10 @@
#
# Copyright (c) 1999-2005 Yukihiro Matsumoto
# Copyright (c) 1999-2005 Minero Aoki
+# Copyright (c) 2001 GOTOU Yuuzou
#
-# Written & maintained by Minero Aoki <aamine@loveruby.net>.
+# Written and maintained by Minero Aoki <aamine@loveruby.net>.
+# HTTPS support added by GOTOU Yuuzou <gotoyuzo@notwork.org>.
#
# This file is derived from "http-access.rb".
#
@@ -26,7 +28,7 @@
require 'net/protocol'
require 'uri'
-module Net # :nodoc:
+module Net #:nodoc:
# :stopdoc:
class HTTPBadResponse < StandardError; end
@@ -44,31 +46,66 @@ module Net # :nodoc:
#
# === Getting Document From WWW Server
#
- # (formal version)
+ # Example #1: Simple GET+print
#
# require 'net/http'
- # Net::HTTP.start('www.example.com', 80) {|http|
- # response = http.get('/index.html')
- # puts response.body
- # }
+ # Net::HTTP.get_print 'www.example.com', '/index.html'
#
- # (shorter version)
+ # Example #2: Simple GET+print by URL
#
# require 'net/http'
- # Net::HTTP.get_print 'www.example.com', '/index.html'
+ # require 'uri'
+ # Net::HTTP.get_print URI.parse('http://www.example.com/index.html')
#
- # or
+ # Example #3: More generic GET+print
#
# require 'net/http'
# require 'uri'
- # Net::HTTP.get_print URI.parse('http://www.example.com/index.html')
+ #
+ # url = URI.parse('http://www.example.com/index.html')
+ # res = Net::HTTP.start(url.host, url.port) {|http|
+ # http.get('/index.html')
+ # }
+ # puts res.body
+ #
+ # Example #4: More generic GET+print
+ #
+ # require 'net/http'
+ #
+ # url = URI.parse('http://www.example.com/index.html')
+ # req = Net::HTTP::Get.new(url.path)
+ # res = Net::HTTP.start(url.host, url.port) {|http|
+ # http.request(req)
+ # }
+ # puts res.body
#
# === Posting Form Data
#
# require 'net/http'
- # Net::HTTP.start('some.www.server', 80) {|http|
- # response = http.post('/cgi-bin/search.rb', 'query=ruby')
- # }
+ # require 'uri'
+ #
+ # #1: Simple POST
+ # res = Net::HTTP.post_form(URI.parse('http://www.example.com/search.cgi'),
+ # {'q'=>'ruby', 'max'=>'50'})
+ # puts res.body
+ #
+ # #2: POST with basic authentication
+ # res = Net::HTTP.post_form(URI.parse('http://jack:pass@www.example.com/todo.cgi'),
+ # {'from'=>'2005-01-01', 'to'=>'2005-03-31'})
+ # puts res.body
+ #
+ # #3: Detailed control
+ # url = URI.parse('http://www.example.com/todo.cgi')
+ # req = Net::HTTP::Post.new(url.path)
+ # req.basic_auth 'jack', 'pass'
+ # req.set_form_data({'from'=>'2005-01-01', 'to'=>'2005-03-31'}, ';')
+ # res = Net::HTTP.new(url.host, url.port).start { http.request(req) }
+ # case res
+ # when Net::HTTPSuccess, Net::HTTPRedirection
+ # # OK
+ # else
+ # res.error!
+ # end
#
# === Accessing via Proxy
#
@@ -148,9 +185,78 @@ module Net # :nodoc:
# print response.body
# }
#
+ # === HTTP Request Classes
+ #
+ # Here is HTTP request class hierarchy.
+ #
+ # Net::HTTPRequest
+ # Net::HTTP::Get
+ # Net::HTTP::Head
+ # Net::HTTP::Post
+ # Net::HTTP::Put
+ # Net::HTTP::Proppatch
+ # Net::HTTP::Lock
+ # Net::HTTP::Unlock
+ # Net::HTTP::Options
+ # Net::HTTP::Propfind
+ # Net::HTTP::Delete
+ # Net::HTTP::Move
+ # Net::HTTP::Copy
+ # Net::HTTP::Mkcol
+ # Net::HTTP::Trace
+ #
# === HTTP Response Classes
#
- # TODO: write me.
+ # Here is HTTP response class hierarchy.
+ # All classes are defined in Net module.
+ #
+ # HTTPResponse
+ # HTTPUnknownResponse
+ # HTTPInformation # 1xx
+ # HTTPContinue # 100
+ # HTTPSwitchProtocl # 101
+ # HTTPSuccess # 2xx
+ # HTTPOK # 200
+ # HTTPCreated # 201
+ # HTTPAccepted # 202
+ # HTTPNonAuthoritativeInformation # 203
+ # HTTPNoContent # 204
+ # HTTPResetContent # 205
+ # HTTPPartialContent # 206
+ # HTTPRedirection # 3xx
+ # HTTPMultipleChoice # 300
+ # HTTPMovedPermanently # 301
+ # HTTPFound # 302
+ # HTTPSeeOther # 303
+ # HTTPNotModified # 304
+ # HTTPUseProxy # 305
+ # HTTPTemporaryRedirect # 307
+ # HTTPClientError # 4xx
+ # HTTPBadRequest # 400
+ # HTTPUnauthorized # 401
+ # HTTPPaymentRequired # 402
+ # HTTPForbidden # 403
+ # HTTPNotFound # 404
+ # HTTPMethodNotAllowed # 405
+ # HTTPNotAcceptable # 406
+ # HTTPProxyAuthenticationRequired # 407
+ # HTTPRequestTimeOut # 408
+ # HTTPConflict # 409
+ # HTTPGone # 410
+ # HTTPLengthRequired # 411
+ # HTTPPreconditionFailed # 412
+ # HTTPRequestEntityTooLarge # 413
+ # HTTPRequestURITooLong # 414
+ # HTTPUnsupportedMediaType # 415
+ # HTTPRequestedRangeNotSatisfiable # 416
+ # HTTPExpectationFailed # 417
+ # HTTPServerError # 5xx
+ # HTTPInternalServerError # 500
+ # HTTPNotImplemented # 501
+ # HTTPBadGateway # 502
+ # HTTPServiceUnavailable # 503
+ # HTTPGatewayTimeOut # 504
+ # HTTPVersionNotSupported # 505
#
# == Switching Net::HTTP versions
#
@@ -174,7 +280,7 @@ module Net # :nodoc:
# :stopdoc:
Revision = %q$Revision$.split[1]
HTTPVersion = '1.1'
- @@newimpl = true # for backward compatability
+ @newimpl = true # for backward compatability
# :startdoc:
# Turns on net/http 1.2 (ruby 1.8) features.
@@ -186,25 +292,25 @@ module Net # :nodoc:
# Net::HTTP.version_1_2
#
def HTTP.version_1_2
- @@newimpl = true
+ @newimpl = true
end
# Turns on net/http 1.1 (ruby 1.6) features.
# Defaults to OFF in ruby 1.8.
def HTTP.version_1_1
- @@newimpl = false
+ @newimpl = false
end
# true if net/http is in version 1.2 mode.
# Defaults to true.
def HTTP.version_1_2?
- @@newimpl
+ @newimpl
end
# true if net/http is in version 1.1 compatible mode.
# Defaults to true.
def HTTP.version_1_1?
- not @@newimpl
+ not @newimpl
end
class << HTTP
@@ -212,12 +318,6 @@ module Net # :nodoc:
alias is_version_1_2? version_1_2? #:nodoc:
end
- def HTTP.setimplversion(obj) #:nodoc:
- f = @@newimpl
- obj.instance_eval { @newimpl = f }
- end
- private_class_method :setimplversion
-
#
# short cut methods
#
@@ -231,19 +331,13 @@ module Net # :nodoc:
#
# or:
#
- # Net::HTTP.get_print('www.example.com', '/index.html')
+ # Net::HTTP.get_print 'www.example.com', '/index.html'
#
- def HTTP.get_print(arg1, arg2 = nil, port = nil)
- if arg2
- addr, path = arg1, arg2
- else
- uri = arg1
- addr = uri.host
- path = uri.request_uri
- port = uri.port
- end
- new(addr, port || HTTP.default_port).start {|http|
- http.get path, nil, $stdout
+ def HTTP.get_print(uri_or_host, path = nil, port = nil)
+ get_response(uri_or_host, path, port) {|res|
+ res.read_body do |chunk|
+ $stdout.print chunk
+ end
}
nil
end
@@ -258,8 +352,8 @@ module Net # :nodoc:
#
# print Net::HTTP.get('www.example.com', '/index.html')
#
- def HTTP.get(arg1, arg2 = nil, arg3 = nil)
- get_response(arg1,arg2,arg3).body
+ def HTTP.get(uri_or_host, path = nil, port = nil)
+ get_response(uri_or_host, path, port).body
end
# Send a GET request to the target and return the response
@@ -274,29 +368,43 @@ module Net # :nodoc:
# res = Net::HTTP.get_response('www.example.com', '/index.html')
# print res.body
#
- def HTTP.get_response(arg1, arg2 = nil, arg3 = nil)
- if arg2
- get_by_path(arg1, arg2, arg3)
+ def HTTP.get_response(uri_or_host, path = nil, port = nil, &block)
+ if path
+ host = uri_or_host
+ new(host, port || HTTP.default_port).start {|http|
+ return http.request_get(path, &block)
+ }
else
- get_by_uri(arg1)
+ uri = uri_or_host
+ new(uri.host, uri.port).start {|http|
+ return http.request_get(uri.request_uri, &block)
+ }
end
end
- def HTTP.get_by_path(addr, path, port = nil) #:nodoc:
- new(addr, port || HTTP.default_port).start {|http|
- return http.request(Get.new(path))
- }
- end
- private_class_method :get_by_path
-
- def HTTP.get_by_uri(uri) #:nodoc:
- # Should we allow this?
- # uri = URI.parse(uri) unless uri.respond_to?(:host)
- new(uri.host, uri.port).start {|http|
- return http.request(Get.new(uri.request_uri))
+ # Posts HTML form data to the +URL+.
+ # Form data must be represented as a Hash of String to String, e.g:
+ #
+ # { "cmd" => "search", "q" => "ruby", "max" => "50" }
+ #
+ # This method also does Basic Authentication iff +URL+.user exists.
+ #
+ # Example:
+ #
+ # require 'net/http'
+ # require 'uri'
+ #
+ # HTTP.post_form URI.parse('http://www.example.com/search.cgi'),
+ # { "q" => "ruby", "max" => "50" }
+ #
+ def HTTP.post_form(url, params)
+ req = Post.new(url.path)
+ req.form_data = params
+ req.basic_auth url.user, url.password if url.user
+ new(url.host, url.port).start {|http|
+ http.request(req)
}
end
- private_class_method :get_by_uri
#
# HTTP session management
@@ -304,35 +412,47 @@ module Net # :nodoc:
# The default port to use for HTTP requests; defaults to 80.
def HTTP.default_port
+ http_default_port()
+ end
+
+ # The default port to use for HTTP requests; defaults to 80.
+ def HTTP.http_default_port
80
end
+ # The default port to use for HTTPS requests; defaults to 443.
+ def HTTP.https_default_port
+ 443
+ end
+
def HTTP.socket_type #:nodoc: obsolete
- InternetMessageIO
+ BufferedIO
end
- class << HTTP
- # creates a new Net::HTTP object and opens its TCP connection and
- # HTTP session. If the optional block is given, the newly
- # created Net::HTTP object is passed to it and closed when the
- # block finishes. In this case, the return value of this method
- # is the return value of the block. If no block is given, the
- # return value of this method is the newly created Net::HTTP object
- # itself, and the caller is responsible for closing it upon completion.
- def start(address, port = nil, p_addr = nil, p_port = nil, p_user = nil, p_pass = nil, &block) # :yield: +http+
- new(address, port, p_addr, p_port, p_user, p_pass).start(&block)
- end
+ # creates a new Net::HTTP object and opens its TCP connection and
+ # HTTP session. If the optional block is given, the newly
+ # created Net::HTTP object is passed to it and closed when the
+ # block finishes. In this case, the return value of this method
+ # is the return value of the block. If no block is given, the
+ # return value of this method is the newly created Net::HTTP object
+ # itself, and the caller is responsible for closing it upon completion.
+ def HTTP.start(address, port = nil, p_addr = nil, p_port = nil, p_user = nil, p_pass = nil, &block) # :yield: +http+
+ new(address, port, p_addr, p_port, p_user, p_pass).start(&block)
+ end
+ class << HTTP
alias newobj new
+ end
- # Creates a new Net::HTTP object.
- # If +proxy_addr+ is given, creates an Net::HTTP object with proxy support.
- # This method does not open the TCP connection.
- def new(address, port = nil, p_addr = nil, p_port = nil, p_user = nil, p_pass = nil)
- obj = Proxy(p_addr, p_port, p_user, p_pass).newobj(address, port)
- setimplversion obj
- obj
- end
+ # Creates a new Net::HTTP object.
+ # If +proxy_addr+ is given, creates an Net::HTTP object with proxy support.
+ # This method does not open the TCP connection.
+ def HTTP.new(address, port = nil, p_addr = nil, p_port = nil, p_user = nil, p_pass = nil)
+ h = Proxy(p_addr, p_port, p_user, p_pass).newobj(address, port)
+ h.instance_eval {
+ @newimpl = ::Net::HTTP.version_1_2?
+ }
+ h
end
# Creates a new Net::HTTP object for the specified +address+.
@@ -340,17 +460,16 @@ module Net # :nodoc:
def initialize(address, port = nil)
@address = address
@port = (port || HTTP.default_port)
-
@curr_http_version = HTTPVersion
@seems_1_0_server = false
@close_on_empty_response = false
@socket = nil
@started = false
-
- @open_timeout = 30
+ @open_timeout = nil
@read_timeout = 60
-
@debug_output = nil
+ @use_ssl = false
+ @ssl_context = nil
end
def inspect
@@ -402,6 +521,11 @@ module Net # :nodoc:
attr_accessor :close_on_empty_response
+ # returns true if use SSL/TLS with HTTP.
+ def use_ssl?
+ false # redefined in net/https
+ end
+
# Opens TCP connection and HTTP session.
#
# When this method is called with block, gives a HTTP object
@@ -426,14 +550,45 @@ module Net # :nodoc:
end
def do_start
- @socket = self.class.socket_type.open(conn_address(), conn_port(),
- @open_timeout, @read_timeout,
- @debug_output)
- on_connect
+ connect
@started = true
end
private :do_start
+ def connect
+ D "opening connection to #{conn_address()}..."
+ s = timeout(@open_timeout) { TCPSocket.open(conn_address(), conn_port()) }
+ D "opened"
+ if use_ssl?
+ unless @ssl_context.verify_mode
+ warn "warning: peer certificate won't be verified in this SSL session"
+ @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ end
+ s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context)
+ s.sync_close = true
+ end
+ @socket = BufferedIO.new(s)
+ @socket.read_timeout = @read_timeout
+ @socket.debug_output = @debug_output
+ if use_ssl?
+ if proxy?
+ @socket.writeline sprintf('CONNECT %s:%s HTTP/%s',
+ @address, @port, HTTPVersion)
+ @socket.writeline "Host: #{@address}:#{@port}"
+ if proxy_user
+ credential = ["#{proxy_user}:#{proxy_pass}"].pack('m')
+ credential.delete!("\r\n")
+ @socket.writeline "Proxy-Authorization: Basic #{credential}"
+ end
+ @socket.writeline ''
+ HTTPResponse.read_new(@socket).value
+ end
+ s.connect
+ end
+ on_connect
+ end
+ private :connect
+
def on_connect
end
private :on_connect
@@ -508,7 +663,7 @@ module Net # :nodoc:
attr_reader :proxy_pass
end
- # True if self is a HTTP proxy class
+ # True if self is a HTTP proxy class.
def proxy?
self.class.proxy_class?
end
@@ -541,11 +696,11 @@ module Net # :nodoc:
# without proxy
def conn_address
- address
+ address()
end
def conn_port
- port
+ port()
end
def edit_path(path)
@@ -564,7 +719,7 @@ module Net # :nodoc:
end
def edit_path(path)
- 'http://' + addr_port() + path
+ use_ssl? ? path : "http://#{addr_port()}#{path}"
end
end
@@ -657,14 +812,8 @@ module Net # :nodoc:
# the socket. Note that in this case, the returned response
# object will *not* contain a (meaningful) body.
#
- # +dest+ is an alternative method of collecting the body. It
- # must be an object responding to the "<<" operator (such as
- # a String or an Array). Each fragment of the entity body
- # will be "<<"-ed in turn onto +dest+ if provided, and it will
- # also become the body of the returned response object.
- #
- # You must *not* provide both +dest+ and a block; doing so
- # will result in an ArgumentError.
+ # +dest+ argument is obsolete.
+ # It still works but you must not use it.
#
# In version 1.1, this method might raise an exception for
# 3xx (redirect). In this case you can get an HTTPResponse object
@@ -704,6 +853,66 @@ module Net # :nodoc:
res
end
+ # Sends a PROPPATCH request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def proppatch(path, body, initheader = nil)
+ request(Proppatch.new(path, initheader), body)
+ end
+
+ # Sends a LOCK request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def lock(path, body, initheader = nil)
+ request(Lock.new(path, initheader), body)
+ end
+
+ # Sends a UNLOCK request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def unlock(path, body, initheader = nil)
+ request(Unlock.new(path, initheader), body)
+ end
+
+ # Sends a OPTIONS request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def options(path, initheader = nil)
+ request(Options.new(path, initheader))
+ end
+
+ # Sends a PROPFIND request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def propfind(path, body = nil, initheader = {'Depth' => '0'})
+ request(Propfind.new(path, initheader), body)
+ end
+
+ # Sends a DELETE request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def delete(path, initheader = {'Depth' => 'Infinity'})
+ request(Delete.new(path, initheader))
+ end
+
+ # Sends a MOVE request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def move(path, initheader = nil)
+ request(Move.new(path, initheader))
+ end
+
+ # Sends a COPY request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def copy(path, initheader = nil)
+ request(Copy.new(path, initheader))
+ end
+
+ # Sends a MKCOL request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def mkcol(path, body = nil, initheader = nil)
+ request(Mkcol.new(path, initheader), body)
+ end
+
+ # Sends a TRACE request to the +path+ and gets a response,
+ # as an HTTPResponse object.
+ def trace(path, initheader = nil)
+ request(Trace.new(path, initheader))
+ end
+
# Sends a GET request to the +path+ and gets a response,
# as an HTTPResponse object.
#
@@ -823,11 +1032,14 @@ module Net # :nodoc:
}
end
if proxy_user()
- req.proxy_basic_auth proxy_user(), proxy_pass()
+ unless use_ssl?
+ req.proxy_basic_auth proxy_user(), proxy_pass()
+ end
end
+ req.set_body_internal body
begin_transport req
- req.exec @socket, @curr_http_version, edit_path(req.path), body
+ req.exec @socket, @curr_http_version, edit_path(req.path)
begin
res = HTTPResponse.read_new(@socket)
end while res.kind_of?(HTTPContinue)
@@ -843,8 +1055,7 @@ module Net # :nodoc:
def begin_transport(req)
if @socket.closed?
- @socket.reopen @open_timeout
- on_connect
+ connect
end
if @seems_1_0_server
req['connection'] ||= 'close'
@@ -889,7 +1100,11 @@ module Net # :nodoc:
private
def addr_port
- address + (port == HTTP.default_port ? '' : ":#{port}")
+ if use_ssl?
+ address() + (port == HTTP.https_default_port ? '' : ":#{port()}")
+ else
+ address() + (port == HTTP.http_default_port ? '' : ":#{port()}")
+ end
end
def D(msg)
@@ -902,6 +1117,7 @@ module Net # :nodoc:
HTTPSession = HTTP
+
#
# Header module.
#
@@ -912,16 +1128,14 @@ module Net # :nodoc:
#
module HTTPHeader
- def initialize_http_header(h)
+ def initialize_http_header(initheader)
@header = {}
- return unless h
- h.each do |k,v|
- key = k.downcase
- $stderr.puts "net/http: warning: duplicated HTTP header: #{k}" if @header.key?(key) and $VERBOSE
- @header[key] = [v.strip]
+ 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
- private :initialize_http_header
def size #:nodoc: obsolete
@header.size
@@ -930,46 +1144,19 @@ module Net # :nodoc:
alias length size #:nodoc: obsolete
# Returns the header field corresponding to the case-insensitive key.
- # See also #get_fields.
- #
- # p response['Content-Type'] #=> "text/html; charset=utf-8"
- # p response['cOnTeNt-tYpE'] #=> "text/html; charset=utf-8"
- #
+ # For example, a key of "Content-Type" might return "text/html"
def [](key)
a = @header[key.downcase] or return nil
a.join(', ')
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
-
# Sets the header field corresponding to the case-insensitive key.
- # See also #add_field.
- #
- # request['My-Header'] = 'a'
- # p request['My-Header'] #=> "a"
- # request['My-Header'] = 'b'
- # p request['My-Header'] #=> "b"
- #
def []=(key, val)
unless val
@header.delete key.downcase
return val
end
- @header[key.downcase] = [val].flatten.map {|s| s.to_str }
+ @header[key.downcase] = Array(val).map {|s| s.to_str }
end
# [Ruby 1.8.3]
@@ -988,23 +1175,40 @@ module Net # :nodoc:
# p request.get_fields('X-My-Header') #=> ["a", "b", "c"]
#
def add_field(key, val)
- if @header[key.downcase]
- @header[key.downcase].push val
+ if @header.key?(key.downcase)
+ @header[key.downcase].concat Array(val)
else
- @header[key.downcase] = [val]
+ @header[key.downcase] = Array(val).dup
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 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).join(', ')
+ 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 do |k, va|
+ def each_header #:yield: +key+, +value+
+ @header.each do |k,va|
yield k, va.join(', ')
end
end
@@ -1012,12 +1216,21 @@ module Net # :nodoc:
alias each each_header
# Iterates for each header names.
- def each_key(&block) #:yield: +key+
+ def each_name(&block) #:yield: +key+
@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+
+ def each_value #:yield: +value+
@header.each_value do |va|
yield va.join(', ')
end
@@ -1035,26 +1248,24 @@ module Net # :nodoc:
# Returns a Hash consist of header names and values.
def to_hash
- h = {}
- @header.each do |k, va|
- h[k] = va.join(', ')
- end
- h
+ @header.dup
end
# As for #each_header, except the keys are provided in capitalized form.
- def canonical_each
- @header.each do |k, va|
- yield canonical(k), va.join(', ')
+ def each_capitalized
+ @header.each do |k,v|
+ yield capitalize(k), v.join(', ')
end
end
- def canonical(k)
- k.split(/-/).map {|i| i.capitalize }.join('-')
+ alias canonical_each each_capitalized
+
+ def capitalize(name)
+ name.split(/-/).map {|s| s.capitalize }.join('-')
end
- private :canonical
+ 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
return nil unless @header['range']
@@ -1073,54 +1284,67 @@ 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
- s = r > 0 ? "0-#{r - 1}" : "-#{-r}"
+ n = r.to_i
+ rangestr = (n > 0 ? "0-#{n-1}" : "-#{-n}")
when Range
first = r.first
last = r.last
- if r.exclude_end?
- last -= 1
- end
-
+ last -= 1 if r.exclude_end?
if last == -1
- s = first > 0 ? "#{first}-" : "-#{-first}"
+ rangestr = (first > 0 ? "#{first}-" : "-#{-first}")
else
- first >= 0 or raise HTTPHeaderSyntaxError, 'range.first is negative'
- last > 0 or raise HTTPHeaderSyntaxError, 'range.last is negative'
- first < last or raise HTTPHeaderSyntaxError, 'must be .first < .last'
- s = "#{first}-#{last}"
+ raise HTTPHeaderSyntaxError, 'range.first is negative' if first < 0
+ raise HTTPHeaderSyntaxError, 'range.last is negative' if last < 0
+ raise HTTPHeaderSyntaxError, 'must be .first < .last' if first > last
+ rangestr = "#{first}-#{last}"
end
else
raise TypeError, 'Range/Integer is required'
end
-
- @header['range'] = ["bytes=#{s}"]
+ @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['content-length']
+ return nil unless key?('Content-Length')
len = self['Content-Length'].slice(/\d+/) or
- raise HTTPHeaderSyntaxError, 'wrong Content-Length format'
+ 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?
- return false unless @header.key?('transfer-encoding')
- s = self['Transfer-Encoding']
- /(?:\A|[^\-\w])chunked(?:[^\-\w]|\z)/i =~ s ? 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.
@@ -1128,17 +1352,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(self['Content-Range']) or
- raise HTTPHeaderSyntaxError, 'wrong Content-Range format'
+ 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
+
+ def set_form_data(params, sep = '&')
+ self.body = params.map {|k,v| "#{urlencode(k.to_s)}=#{urlencode(v.to_s)}" }.join(sep)
+ self.content_type = 'application/x-www-form-urlencoded'
+ end
+
+ alias form_data= set_form_data
+
+ def urlencode(str)
+ str.gsub(/[^a-zA-Z0-9_\.\-]/n) {|s| sprintf('%%%02x', s[0]) }
+ end
+ private :urlencode
+
# Set the Authorization: header for "Basic" authorization.
def basic_auth(account, password)
@header['authorization'] = [basic_encode(account, password)]
@@ -1156,6 +1421,7 @@ module Net # :nodoc:
end
+
#
# Parent of HTTPRequest class. Do not use this directly; use
# a subclass of HTTPRequest.
@@ -1170,9 +1436,12 @@ module Net # :nodoc:
@method = m
@request_has_body = reqbody
@response_has_body = resbody
+ raise ArgumentError, "HTTP request path is empty" if path.empty?
@path = path
initialize_http_header initheader
self['Accept'] ||= '*/*'
+ @body = nil
+ @body_stream = nil
end
attr_reader :method
@@ -1190,43 +1459,82 @@ module Net # :nodoc:
@response_has_body
end
- alias body_exist? response_body_permitted?
+ 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
+ str
+ end
+
+ attr_reader :body_stream
+
+ def body_stream=(input)
+ @body = nil
+ @body_stream = input
+ 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
+ end
#
# write
#
- def exec(sock, ver, path, body) #:nodoc: internal use only
- if body
- check_body_permitted
- send_request_with_body sock, ver, path, body
+ 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
else
- request sock, ver, path
+ write_header sock, ver, path
end
end
private
- def check_body_permitted
- request_body_permitted? or
- raise ArgumentError, 'HTTP request body is not permitted'
- end
-
def send_request_with_body(sock, ver, path, body)
- self['Content-Length'] = body.length.to_s
- self.delete 'Transfer-Encoding'
- unless self['Content-Type']
- $stderr.puts 'net/http: warning: Content-Type did not set; using application/x-www-form-urlencoded' if $VERBOSE
- self['Content-Type'] = 'application/x-www-form-urlencoded'
+ 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
+ set_content_type 'application/x-www-form-urlencoded'
end
- request sock, ver, path
+ write_header sock, ver, path
sock.write body
end
- def request(sock, ver, path)
+ def send_request_with_body_stream(sock, ver, path, f)
+ 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
+ set_content_type 'application/x-www-form-urlencoded'
+ end
+ write_header sock, ver, path
+ if chunked?
+ while s = f.read(1024)
+ sock.write(sprintf("%x\r\n", s.length) << s << "\r\n")
+ end
+ sock.write "0\r\n\r\n"
+ else
+ while s = f.read(1024)
+ sock.write s
+ end
+ end
+ end
+
+ def write_header(sock, ver, path)
buf = "#{@method} #{path} HTTP/#{ver}\r\n"
- canonical_each do |k,v|
- buf << k + ': ' + v + "\r\n"
+ each_capitalized do |k,v|
+ buf << "#{k}: #{v}\r\n"
end
buf << "\r\n"
sock.write buf
@@ -1251,7 +1559,10 @@ module Net # :nodoc:
end
- class HTTP
+ class HTTP # reopen
+ #
+ # HTTP 1.1 methods --- RFC2616
+ #
class Get < HTTPRequest
METHOD = 'GET'
@@ -1277,6 +1588,69 @@ module Net # :nodoc:
RESPONSE_HAS_BODY = true
end
+ class Delete < HTTPRequest
+ METHOD = 'DELETE'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = true
+ end
+
+ class Options < HTTPRequest
+ METHOD = 'OPTIONS'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = false
+ end
+
+ class Trace < HTTPRequest
+ METHOD = 'TRACE'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = true
+ end
+
+ #
+ # WebDAV methods --- RFC2518
+ #
+
+ class Propfind < HTTPRequest
+ METHOD = 'PROPFIND'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+ end
+
+ class Proppatch < HTTPRequest
+ METHOD = 'PROPPATCH'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+ end
+
+ class Mkcol < HTTPRequest
+ METHOD = 'MKCOL'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+ end
+
+ class Copy < HTTPRequest
+ METHOD = 'COPY'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = true
+ end
+
+ class Move < HTTPRequest
+ METHOD = 'MOVE'
+ REQUEST_HAS_BODY = false
+ RESPONSE_HAS_BODY = true
+ end
+
+ class Lock < HTTPRequest
+ METHOD = 'LOCK'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+ end
+
+ class Unlock < HTTPRequest
+ METHOD = 'UNLOCK'
+ REQUEST_HAS_BODY = true
+ RESPONSE_HAS_BODY = true
+ end
end
@@ -1380,7 +1754,7 @@ module Net # :nodoc:
def HTTPResponse.exception_type # :nodoc: internal use only
self::EXCEPTION_TYPE
end
- end # redefined after
+ end # reopened after
# :stopdoc:
@@ -1540,7 +1914,7 @@ module Net # :nodoc:
# :startdoc:
- class HTTPResponse # redefine
+ class HTTPResponse # reopen
CODE_CLASS_TO_OBJ = {
'1' => HTTPInformation,
@@ -1596,9 +1970,7 @@ module Net # :nodoc:
'505' => HTTPVersionNotSupported
}
-
- class << self
-
+ class << HTTPResponse
def read_new(sock) #:nodoc: internal use only
httpv, code, msg = read_status_line(sock)
res = response_class(code).new(httpv, code, msg)
@@ -1613,8 +1985,8 @@ module Net # :nodoc:
def read_status_line(sock)
str = sock.readline
m = /\AHTTP(?:\/(\d+\.\d+))?\s+(\d\d\d)\s*(.*)\z/in.match(str) or
- raise HTTPBadResponse, "wrong status line: #{str.dump}"
- m.to_a[1,3]
+ raise HTTPBadResponse, "wrong status line: #{str.dump}"
+ m.captures
end
def response_class(code)
@@ -1632,7 +2004,6 @@ module Net # :nodoc:
yield m[1], m.post_match
end
end
-
end
# next is to fix bug in RDoc, where the private inside class << self
@@ -1663,7 +2034,7 @@ module Net # :nodoc:
alias msg message # :nodoc: obsolete
def inspect
- "#<#{self.class} #{@code} readbody=#{@read}>"
+ "#<#{self.class} #{@code} #{@message} readbody=#{@read}>"
end
# For backward compatibility.
@@ -1673,7 +2044,11 @@ module Net # :nodoc:
#
def to_ary
warn "net/http.rb: warning: Net::HTTP v1.1 style assignment found at #{caller(1)[0]}; use `response = http.get(...)' instead." if $VERBOSE
- [self, body()]
+ res = self.dup
+ class << res
+ undef to_ary
+ end
+ [res, res.body]
end
#
@@ -1694,7 +2069,7 @@ module Net # :nodoc:
# Raises HTTP error if the response is not 2xx.
def value
- error! unless HTTPSuccess === self
+ error! unless self.kind_of?(HTTPSuccess)
end
#
@@ -1702,11 +2077,19 @@ module Net # :nodoc:
#
def response #:nodoc:
+ warn "#{caller(1)[0]}: warning: HTTPResponse#response is obsolete" if $VERBOSE
self
end
- alias header response #:nodoc:
- alias read_header response #:nodoc:
+ def header #:nodoc:
+ warn "#{caller(1)[0]}: warning: HTTPResponse#header is obsolete" if $VERBOSE
+ self
+ end
+
+ def read_header #:nodoc:
+ warn "#{caller(1)[0]}: warning: HTTPResponse#read_header is obsolete" if $VERBOSE
+ self
+ end
#
# body
@@ -1788,19 +2171,19 @@ module Net # :nodoc:
def read_body_0(dest)
if chunked?
read_chunked dest
- else
- clen = content_length()
- if clen
- @socket.read clen, dest, true # ignore EOF
- else
- clen = range_length()
- if clen
- @socket.read clen, dest
- else
- @socket.read_all dest
- end
- end
+ return
+ end
+ 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
def read_chunked(dest)
@@ -1839,8 +2222,8 @@ module Net # :nodoc:
# :enddoc:
+ #--
# for backward compatibility
-
class HTTP
ProxyMod = ProxyDelta
end
diff --git a/lib/net/pop.rb b/lib/net/pop.rb
index a4f50b002c..2ecbcbdf28 100644
--- a/lib/net/pop.rb
+++ b/lib/net/pop.rb
@@ -435,7 +435,7 @@ module Net
end
def do_start( account, password )
- @socket = self.class.socket_type.open(@address, @port,
+ @socket = self.class.socket_type.old_open(@address, @port,
@open_timeout, @read_timeout, @debug_output)
on_connect
@command = POP3Command.new(@socket)
diff --git a/lib/net/protocol.rb b/lib/net/protocol.rb
index b527956c98..d722fdcbd4 100644
--- a/lib/net/protocol.rb
+++ b/lib/net/protocol.rb
@@ -2,8 +2,8 @@
# = net/protocol.rb
#
#--
-# Copyright (c) 1999-2004 Yukihiro Matsumoto
-# Copyright (c) 1999-2004 Minero Aoki
+# Copyright (c) 1999-2005 Yukihiro Matsumoto
+# Copyright (c) 1999-2005 Minero Aoki
#
# written and maintained by Minero Aoki <aamine@loveruby.net>
#
@@ -25,7 +25,7 @@ module Net # :nodoc:
class Protocol #:nodoc: internal use only
private
- def Protocol.protocol_param( name, val )
+ def Protocol.protocol_param(name, val)
module_eval(<<-End, __FILE__, __LINE__ + 1)
def #{name}
#{val}
@@ -34,6 +34,7 @@ module Net # :nodoc:
end
end
+
class ProtocolError < StandardError; end
class ProtoSyntaxError < ProtocolError; end
class ProtoFatalError < ProtocolError; end
@@ -45,335 +46,282 @@ module Net # :nodoc:
ProtocRetryError = ProtoRetriableError
- class InternetMessageIO #:nodoc: internal use only
-
- class << self
- alias open new
- end
-
- def initialize( addr, port,
- open_timeout = nil, read_timeout = nil,
- debug_output = nil )
- @address = addr
- @port = port
- @read_timeout = read_timeout
- @debug_output = debug_output
- @socket = nil
- @rbuf = nil # read buffer
- @wbuf = nil # write buffer
- connect open_timeout
- LOG 'opened'
- end
-
- attr_reader :address
- attr_reader :port
-
- def ip_address
- return '' unless @socket
- @socket.addr[3]
- end
-
- attr_accessor :read_timeout
-
- attr_reader :socket
-
- def connect( open_timeout )
- LOG "opening connection to #{@address}..."
- timeout(open_timeout) {
- @socket = TCPsocket.new(@address, @port)
- }
+ class BufferedIO #:nodoc: internal use only
+ def initialize(io)
+ @io = io
+ @read_timeout = 60
+ @debug_output = nil
@rbuf = ''
end
- private :connect
- def close
- if @socket
- @socket.close
- LOG 'closed'
- else
- LOG 'close call for already closed socket'
- end
- @socket = nil
- @rbuf = ''
- end
+ attr_reader :io
+ attr_accessor :read_timeout
+ attr_accessor :debug_output
- def reopen( open_timeout = nil )
- LOG 'reopening...'
- close
- connect open_timeout
- LOG 'reopened'
+ def inspect
+ "#<#{self.class} io=#{@io}>"
end
def closed?
- not @socket
+ @io.closed?
end
- def inspect
- "#<#{self.class} #{closed?() ? 'closed' : 'opened'}>"
+ def close
+ @io.close
end
- ###
- ### READ
- ###
+ #
+ # Read
+ #
public
- def read( len, dest = '', ignore_eof = false )
+ def read(len, dest = '', ignore_eof = false)
LOG "reading #{len} bytes..."
- # LOG_off() # experimental: [ruby-list:38800]
read_bytes = 0
begin
while read_bytes + @rbuf.size < len
- read_bytes += rbuf_moveto(dest, @rbuf.size)
+ dest << (s = rbuf_consume(@rbuf.size))
+ read_bytes += s.size
rbuf_fill
end
- rbuf_moveto dest, len - read_bytes
+ dest << (s = rbuf_consume(len - read_bytes))
+ read_bytes += s.size
rescue EOFError
raise unless ignore_eof
end
- # LOG_on()
LOG "read #{read_bytes} bytes"
dest
end
- def read_all( dest = '' )
+ def read_all(dest = '')
LOG 'reading all...'
- # LOG_off() # experimental: [ruby-list:38800]
read_bytes = 0
begin
while true
- read_bytes += rbuf_moveto(dest, @rbuf.size)
+ dest << (s = rbuf_consume(@rbuf.size))
+ read_bytes += s.size
rbuf_fill
end
rescue EOFError
;
end
- # LOG_on()
LOG "read #{read_bytes} bytes"
dest
end
- def readuntil( terminator, ignore_eof = false )
- dest = ''
+ def readuntil(terminator, ignore_eof = false)
begin
until idx = @rbuf.index(terminator)
rbuf_fill
end
- rbuf_moveto dest, idx + terminator.size
+ return rbuf_consume(idx + terminator.size)
rescue EOFError
raise unless ignore_eof
- rbuf_moveto dest, @rbuf.size
+ return rbuf_consume(@rbuf.size)
end
- dest
end
def readline
readuntil("\n").chop
end
- def each_message_chunk
- LOG 'reading message...'
- LOG_off()
- read_bytes = 0
- while (line = readuntil("\r\n")) != ".\r\n"
- read_bytes += line.size
- yield line.sub(/\A\./, '')
- end
- LOG_on()
- LOG "read message (#{read_bytes} bytes)"
- end
-
- # *library private* (cannot handle 'break')
- def each_list_item
- while (str = readuntil("\r\n")) != ".\r\n"
- yield str.chop
- end
- end
-
private
def rbuf_fill
timeout(@read_timeout) {
- @rbuf << @socket.sysread(1024)
+ @rbuf << @io.sysread(1024)
}
end
- def rbuf_moveto( dest, len )
- dest << (s = @rbuf.slice!(0, len))
+ def rbuf_consume(len)
+ s = @rbuf.slice!(0, len)
@debug_output << %Q[-> #{s.dump}\n] if @debug_output
- len
+ s
end
- ###
- ### WRITE
- ###
+ #
+ # Write
+ #
public
- def write( str )
+ def write(str)
writing {
write0 str
}
end
- def writeline( str )
+ def writeline(str)
writing {
write0 str + "\r\n"
}
end
- def write_message( src )
- LOG "writing message from #{src.class}"
- LOG_off()
- len = using_each_crlf_line {
- write_message_0 src
- }
- LOG_on()
- LOG "wrote #{len} bytes"
- len
- end
-
- def write_message_by_block( &block )
- LOG 'writing message from block'
- LOG_off()
- len = using_each_crlf_line {
- begin
- block.call(WriteAdapter.new(self, :write_message_0))
- rescue LocalJumpError
- # allow `break' from writer block
- end
- }
- LOG_on()
- LOG "wrote #{len} bytes"
- len
- end
-
private
def writing
@written_bytes = 0
@debug_output << '<- ' if @debug_output
yield
- @socket.flush
@debug_output << "\n" if @debug_output
bytes = @written_bytes
@written_bytes = nil
bytes
end
- def write0( str )
+ def write0(str)
@debug_output << str.dump if @debug_output
- len = @socket.write(str)
+ len = @io.write(str)
@written_bytes += len
len
end
#
- # Reads string from src calling :each, and write to @socket.
- # Escapes '.' on the each line head.
+ # Logging
#
- def write_message_0( src )
+
+ private
+
+ def LOG_off
+ @save_debug_out = @debug_output
+ @debug_output = nil
+ end
+
+ def LOG_on
+ @debug_output = @save_debug_out
+ end
+
+ def LOG(msg)
+ return unless @debug_output
+ @debug_output << msg + "\n"
+ end
+ end
+
+
+ class InternetMessageIO < BufferedIO #:nodoc: internal use only
+ def InternetMessageIO.old_open(addr, port,
+ open_timeout = nil, read_timeout = nil, debug_output = nil)
+ debug_output << "opening connection to #{addr}...\n" if debug_output
+ s = timeout(open_timeout) { TCPsocket.new(addr, port) }
+ io = new(s)
+ io.read_timeout = read_timeout
+ io.debug_output = debug_output
+ io
+ end
+
+ def initialize(io)
+ super
+ @wbuf = nil
+ end
+
+ #
+ # Read
+ #
+
+ def each_message_chunk
+ LOG 'reading message...'
+ LOG_off()
+ read_bytes = 0
+ while (line = readuntil("\r\n")) != ".\r\n"
+ read_bytes += line.size
+ yield line.sub(/\A\./, '')
+ end
+ LOG_on()
+ LOG "read message (#{read_bytes} bytes)"
+ end
+
+ # *library private* (cannot handle 'break')
+ def each_list_item
+ while (str = readuntil("\r\n")) != ".\r\n"
+ yield str.chop
+ end
+ end
+
+ def write_message_0(src)
prev = @written_bytes
each_crlf_line(src) do |line|
- if line[0] == ?.
- then write0 '.' + line
- else write0 line
- end
+ write0 line.sub(/\A\./, '..')
end
@written_bytes - prev
end
#
- # setup @wbuf for each_crlf_line.
+ # Write
#
- def using_each_crlf_line
- writing {
- @wbuf = ''
- yield
- if not @wbuf.empty? # unterminated last line
- if @wbuf[-1] == ?\r
- @wbuf.chop!
- end
- @wbuf.concat "\r\n"
- write0 @wbuf
- elsif @written_bytes == 0 # empty src
- write0 "\r\n"
- end
- write0 ".\r\n"
- @wbuf = nil
+
+ def write_message(src)
+ LOG "writing message from #{src.class}"
+ LOG_off()
+ len = writing {
+ using_each_crlf_line {
+ write_message_0 src
+ }
}
+ LOG_on()
+ LOG "wrote #{len} bytes"
+ len
end
- #
- # extract a CR-LF-terminating-line from @wbuf and yield it.
- #
- def each_crlf_line( src )
- adding(src) do
- beg = 0
- buf = @wbuf
- while buf.index(/\n|\r\n|\r/, beg)
- m = Regexp.last_match
- if (m.begin(0) == buf.length - 1) and buf[-1] == ?\r
- # "...\r" : can follow "\n..."
- break
+ def write_message_by_block(&block)
+ LOG 'writing message from block'
+ LOG_off()
+ len = writing {
+ using_each_crlf_line {
+ begin
+ block.call(WriteAdapter.new(self, :write_message_0))
+ rescue LocalJumpError
+ # allow `break' from writer block
end
- str = buf[beg ... m.begin(0)]
- str.concat "\r\n"
- yield str
- beg = m.end(0)
+ }
+ }
+ LOG_on()
+ LOG "wrote #{len} bytes"
+ len
+ end
+
+ private
+
+ def using_each_crlf_line
+ @wbuf = ''
+ yield
+ if not @wbuf.empty? # unterminated last line
+ write0 @wbuf.chomp + "\r\n"
+ elsif @written_bytes == 0 # empty src
+ write0 "\r\n"
+ end
+ write0 ".\r\n"
+ @wbuf = nil
+ end
+
+ def each_crlf_line(src)
+ buffer_filling(@wbuf, src) do
+ while line = @wbuf.slice!(/\A.*(?:\n|\r\n|\r(?!\z))/n)
+ yield line.chomp("\n") + "\r\n"
end
- @wbuf = buf[beg ... buf.length]
end
end
- #
- # Reads strings from SRC and add to @wbuf, then yield.
- #
- def adding( src )
+ def buffer_filling(buf, src)
case src
when String # for speeding up.
- 0.step(src.size - 1, 2048) do |i|
- @wbuf << src[i,2048]
+ 0.step(src.size - 1, 1024) do |i|
+ buf << src[i, 1024]
yield
end
-
when File # for speeding up.
- while s = src.read(2048)
- s[0,0] = @wbuf
- @wbuf = s
+ while s = src.read(1024)
+ buf << s
yield
end
-
else # generic reader
src.each do |s|
- @wbuf << s
- yield if @wbuf.size > 2048
+ buf << s
+ yield if buf.size > 1024
end
- yield unless @wbuf.empty?
+ yield unless buf.empty?
end
end
-
- ###
- ### DEBUG
- ###
-
- private
-
- def LOG_off
- @save_debug_out = @debug_output
- @debug_output = nil
- end
-
- def LOG_on
- @debug_output = @save_debug_out
- end
-
- def LOG( msg )
- return unless @debug_output
- @debug_output << msg
- @debug_output << "\n"
- end
-
end
@@ -381,41 +329,38 @@ module Net # :nodoc:
# The writer adapter class
#
class WriteAdapter
-
- def initialize( sock, mid )
- @socket = sock
- @method_id = mid
+ def initialize(socket, method)
+ @socket = socket
+ @method_id = method
end
def inspect
"#<#{self.class} socket=#{@socket.inspect}>"
end
- def write( str )
+ def write(str)
@socket.__send__(@method_id, str)
end
alias print write
- def <<( str )
+ def <<(str)
write str
self
end
- def puts( str = '' )
+ def puts(str = '')
write str.chomp("\n") + "\n"
end
- def printf( *args )
+ def printf(*args)
write sprintf(*args)
end
-
end
class ReadAdapter #:nodoc: internal use only
-
- def initialize( block )
+ def initialize(block)
@block = block
end
@@ -423,21 +368,18 @@ module Net # :nodoc:
"#<#{self.class}>"
end
- def <<( str )
+ def <<(str)
call_block(str, &@block) if @block
end
private
- #
# This method is needed because @block must be called by yield,
# not Proc#call. You can see difference when using `break' in
# the block.
- #
- def call_block( str )
+ def call_block(str)
yield str
end
-
end
diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb
index db54b998c9..dda9dab072 100644
--- a/lib/net/smtp.rb
+++ b/lib/net/smtp.rb
@@ -390,7 +390,7 @@ module Net
raise IOError, 'SMTP session already started' if @started
check_auth_args user, secret, authtype if user or secret
- @socket = InternetMessageIO.open(@address, @port,
+ @socket = InternetMessageIO.old_open(@address, @port,
@open_timeout, @read_timeout,
@debug_output)
check_response(critical { recv_response() })