diff --git a/lib/aws/s3/authentication.rb b/lib/aws/s3/authentication.rb
index 47089a9..4ef73ee 100644
--- a/lib/aws/s3/authentication.rb
+++ b/lib/aws/s3/authentication.rb
@@ -1,24 +1,24 @@
module AWS
- module S3
+ module S3
# All authentication is taken care of for you by the AWS::S3 library. None the less, some details of the two types
# of authentication and when they are used may be of interest to some.
#
# === Header based authentication
#
- # Header based authentication is achieved by setting a special Authorization header whose value
+ # Header based authentication is achieved by setting a special Authorization header whose value
# is formatted like so:
#
# "AWS #{access_key_id}:#{encoded_canonical}"
#
# The access_key_id is the public key that is assigned by Amazon for a given account which you use when
- # establishing your initial connection. The encoded_canonical is computed according to rules layed out
+ # establishing your initial connection. The encoded_canonical is computed according to rules layed out
# by Amazon which we will describe presently.
#
# ==== Generating the encoded canonical string
#
- # The "canonical string", generated by the CanonicalString class, is computed by collecting the current request method,
- # a set of significant headers of the current request, and the current request path into a string.
- # That canonical string is then encrypted with the secret_access_key assigned by Amazon. The resulting encrypted canonical
+ # The "canonical string", generated by the CanonicalString class, is computed by collecting the current request method,
+ # a set of significant headers of the current request, and the current request path into a string.
+ # That canonical string is then encrypted with the secret_access_key assigned by Amazon. The resulting encrypted canonical
# string is then base 64 encoded.
#
# === Query string based authentication
@@ -26,43 +26,44 @@ module S3
# When accessing a restricted object from the browser, you can authenticate via the query string, by setting the following parameters:
#
# "AWSAccessKeyId=#{access_key_id}&Expires=#{expires}&Signature=#{encoded_canonical}"
- #
+ #
# The QueryString class is responsible for generating the appropriate parameters for authentication via the
# query string.
#
- # The access_key_id and encoded_canonical are the same as described in the Header based authentication section.
+ # The access_key_id and encoded_canonical are the same as described in the Header based authentication section.
# The expires value dictates for how long the current url is valid (by default, it will expire in 5 minutes). Expiration can be specified
# either by an absolute time (expressed in seconds since the epoch), or in relative time (in number of seconds from now).
# Details of how to customize the expiration of the url are provided in the documentation for the QueryString class.
#
- # All requests made by this library use header authentication. When a query string authenticated url is needed,
+ # All requests made by this library use header authentication. When a query string authenticated url is needed,
# the S3Object#url method will include the appropriate query string parameters.
#
# === Full authentication specification
#
# The full specification of the authentication protocol can be found at
- # http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAuthentication.html
+ # http://docs.amazonwebservices.com/AmazonS3/2006-03-01/RESTAuthentication.html
class Authentication
constant :AMAZON_HEADER_PREFIX, 'x-amz-'
-
+
# Signature is the abstract super class for the Header and QueryString authentication methods. It does the job
# of computing the canonical_string using the CanonicalString class as well as encoding the canonical string. The subclasses
# parameterize these computations and arrange them in a string form appropriate to how they are used, in one case a http request
# header value, and in the other case key/value query string parameter pairs.
class Signature < String #:nodoc:
attr_reader :request, :access_key_id, :secret_access_key, :options
-
+
def initialize(request, access_key_id, secret_access_key, options = {})
super()
@request, @access_key_id, @secret_access_key = request, access_key_id, secret_access_key
@options = options
end
-
+
private
def canonical_string
options = {}
options[:expires] = expires if expires?
+ options[:security_token] = @options[:security_token] if @options.has_key?(:security_token)
CanonicalString.new(request, options)
end
memoized :canonical_string
@@ -72,20 +73,20 @@ def encoded_canonical
b64_hmac = [OpenSSL::HMAC.digest(digest, secret_access_key, canonical_string)].pack("m").strip
url_encode? ? CGI.escape(b64_hmac) : b64_hmac
end
-
+
def url_encode?
!@options[:url_encode].nil?
end
-
+
def expires?
is_a? QueryString
end
-
+
def date
request['date'].to_s.strip.empty? ? Time.now : Time.parse(request['date'])
end
end
-
+
# Provides header authentication by computing the value of the Authorization header. More details about the
# various authentication schemes can be found in the docs for its containing module, Authentication.
class Header < Signature #:nodoc:
@@ -94,7 +95,7 @@ def initialize(*args)
self << "AWS #{access_key_id}:#{encoded_canonical}"
end
end
-
+
# Provides query string authentication by computing the three authorization parameters: AWSAccessKeyId, Expires and Signature.
# More details about the various authentication schemes can be found in the docs for its containing module, Authentication.
class QueryString < Signature #:nodoc:
@@ -104,9 +105,9 @@ def initialize(*args)
options[:url_encode] = true
self << build
end
-
+
private
-
+
# Will return one of three values, in the following order of precedence:
#
# 1) Seconds since the epoch explicitly passed in the +:expires+ option
@@ -117,17 +118,17 @@ def expires
return options[:expires] if options[:expires]
date.to_i + expires_in
end
-
+
def expires_in
options.has_key?(:expires_in) ? Integer(options[:expires_in]) : DEFAULT_EXPIRY
end
-
+
def build
"AWSAccessKeyId=#{access_key_id}&Expires=#{expires}&Signature=#{encoded_canonical}"
end
end
-
- # The CanonicalString is used to generate an encrypted signature, signed with your secrect access key. It is composed of
+
+ # The CanonicalString is used to generate an encrypted signature, signed with your secrect access key. It is composed of
# data related to the given request for which it provides authentication. This data includes the request method, request headers,
# and the request path. Both Header and QueryString use it to generate their signature.
class CanonicalString < String #:nodoc:
@@ -139,7 +140,7 @@ def default_headers
def interesting_headers
['content-md5', 'content-type', 'date', amazon_header_prefix]
end
-
+
def amazon_header_prefix
/^#{AMAZON_HEADER_PREFIX}/io
end
@@ -152,7 +153,7 @@ def query_parameters
response-expires response-cache-control
response-content-disposition response-content-encoding)
end
-
+
def query_parameters_for_signature(params)
params.select {|k, v| query_parameters.include?(k)}
end
@@ -168,13 +169,13 @@ def resource_parameters
end
attr_reader :request, :headers
-
+
def initialize(request, options = {})
super()
@request = request
@headers = {}
@options = options
- # "For non-authenticated or anonymous requests. A NotImplemented error result code will be returned if
+ # "For non-authenticated or anonymous requests. A NotImplemented error result code will be returned if
# an authenticated (signed) request specifies a Host: header other than 's3.amazonaws.com'"
# (from http://docs.amazonwebservices.com/AmazonS3/2006-03-01/VirtualHosting.html)
request['Host'] = DEFAULT_HOST
@@ -185,10 +186,11 @@ def initialize(request, options = {})
def build
self << "#{request.method}\n"
ensure_date_is_valid
-
+
initialize_headers
set_expiry!
-
+ set_security_token!
+
headers.sort_by {|k, _| k}.each do |key, value|
value = value.to_s.strip
self << (key =~ self.class.amazon_header_prefix ? "#{key}:#{value}" : value)
@@ -196,16 +198,20 @@ def build
end
self << path
end
-
+
def initialize_headers
identify_interesting_headers
set_default_headers
end
-
+
def set_expiry!
self.headers['date'] = @options[:expires] if @options[:expires]
end
-
+
+ def set_security_token!
+ self.headers['x-amz-security-token'] = @options[:security_token] if @options[:security_token]
+ end
+
def ensure_date_is_valid
request['Date'] ||= Time.now.httpdate
end
@@ -249,7 +255,7 @@ def extract_significant_parameter
def resource_parameter?(key)
self.class.resource_parameters.include? key
end
-
+
def only_path
request.path[/^[^?]*/]
end
diff --git a/lib/aws/s3/connection.rb b/lib/aws/s3/connection.rb
index 27fed33..8e3f1cd 100644
--- a/lib/aws/s3/connection.rb
+++ b/lib/aws/s3/connection.rb
@@ -5,46 +5,46 @@ class << self
def connect(options = {})
new(options)
end
-
+
def prepare_path(path)
path = path.remove_extended unless path.valid_utf8?
AWS::S3.escape_uri(path)
end
end
-
- attr_reader :access_key_id, :secret_access_key, :http, :options
-
- # Creates a new connection. Connections make the actual requests to S3, though these requests are usually
+
+ attr_reader :access_key_id, :secret_access_key, :security_token, :http, :options
+
+ # Creates a new connection. Connections make the actual requests to S3, though these requests are usually
# called from subclasses of Base.
- #
+ #
# For details on establishing connections, check the Connection::Management::ClassMethods.
def initialize(options = {})
@options = Options.new(options)
connect
end
-
+
def request(verb, path, headers = {}, body = nil, attempts = 0, &block)
- body.rewind if body.respond_to?(:rewind) unless attempts.zero?
-
- requester = Proc.new do
+ body.rewind if body.respond_to?(:rewind) unless attempts.zero?
+
+ requester = Proc.new do
path = self.class.prepare_path(path) if attempts.zero? # Only escape the path once
request = request_method(verb).new(path, headers)
ensure_content_type!(request)
add_user_agent!(request)
authenticate!(request)
if body
- if body.respond_to?(:read)
- request.body_stream = body
- else
- request.body = body
+ if body.respond_to?(:read)
+ request.body_stream = body
+ else
+ request.body = body
end
- request.content_length = body.respond_to?(:lstat) ? body.stat.size : body.size
+ request.content_length = body.respond_to?(:lstat) ? body.stat.size : body.size
else
- request.content_length = 0
+ request.content_length = 0
end
http.request(request, &block)
end
-
+
if persistent?
http.start unless http.started?
requester.call
@@ -55,11 +55,11 @@ def request(verb, path, headers = {}, body = nil, attempts = 0, &block)
@http = create_connection
attempts == 3 ? raise : (attempts += 1; retry)
end
-
+
def url_for(path, options = {})
authenticate = options.delete(:authenticated)
# Default to true unless explicitly false
- authenticate = true if authenticate.nil?
+ authenticate = true if authenticate.nil?
path = path.valid_utf8? ? path : path.remove_extended
request = request_method(:get).new(path, {})
query_string = query_string_authentication(request, options)
@@ -67,18 +67,18 @@ def url_for(path, options = {})
(url << (path[/\?/] ? '&' : '?') << "#{query_string}") if authenticate
end
end
-
+
def subdomain
http.address[/^([^.]+).#{DEFAULT_HOST}$/, 1]
end
-
+
def persistent?
options[:persistent]
end
-
+
def protocol(options = {})
# This always trumps http.use_ssl?
- if options[:use_ssl] == false
+ if options[:use_ssl] == false
'http://'
elsif options[:use_ssl] || http.use_ssl?
'https://'
@@ -86,7 +86,7 @@ def protocol(options = {})
'http://'
end
end
-
+
private
def extract_keys!
missing_keys = []
@@ -94,15 +94,16 @@ def extract_keys!
@access_key_id = extract_key[:access_key_id]
@secret_access_key = extract_key[:secret_access_key]
raise MissingAccessKey.new(missing_keys) unless missing_keys.empty?
+ @security_token = extract_key[:security_token]
end
-
+
def create_connection
http = http_class.new(options[:server], options[:port])
http.use_ssl = !options[:use_ssl].nil? || options[:port] == 443
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
http
end
-
+
def http_class
if options.connecting_through_proxy?
Net::HTTP::Proxy(*options.proxy_settings)
@@ -110,7 +111,7 @@ def http_class
Net::HTTP
end
end
-
+
def connect
extract_keys!
@http = create_connection
@@ -124,16 +125,18 @@ def port_string
def ensure_content_type!(request)
request['Content-Type'] ||= 'binary/octet-stream'
end
-
+
# Just do Header authentication for now
def authenticate!(request)
- request['Authorization'] = Authentication::Header.new(request, access_key_id, secret_access_key)
+ options = {}
+ options[:security_token] = security_token unless security_token.nil?
+ request['Authorization'] = Authentication::Header.new(request, access_key_id, secret_access_key, options)
end
-
+
def add_user_agent!(request)
request['User-Agent'] ||= "AWS::S3/#{Version}"
end
-
+
def query_string_authentication(request, options = {})
Authentication::QueryString.new(request, access_key_id, secret_access_key, options)
end
@@ -141,29 +144,29 @@ def query_string_authentication(request, options = {})
def request_method(verb)
Net::HTTP.const_get(verb.to_s.capitalize)
end
-
+
def method_missing(method, *args, &block)
options[method] || super
end
-
+
module Management #:nodoc:
def self.included(base)
base.cattr_accessor :connections
base.connections = {}
base.extend ClassMethods
end
-
+
# Manage the creation and destruction of connections for AWS::S3::Base and its subclasses. Connections are
# created with establish_connection!.
module ClassMethods
# Creates a new connection with which to make requests to the S3 servers for the calling class.
- #
+ #
# AWS::S3::Base.establish_connection!(:access_key_id => '...', :secret_access_key => '...')
#
# You can set connections for every subclass of AWS::S3::Base. Once the initial connection is made on
# Base, all subsequent connections will inherit whatever values you don't specify explictly. This allows you to
- # customize details of the connection, such as what server the requests are made to, by just specifying one
- # option.
+ # customize details of the connection, such as what server the requests are made to, by just specifying one
+ # option.
#
# AWS::S3::Bucket.established_connection!(:use_ssl => true)
#
@@ -185,23 +188,23 @@ module ClassMethods
# argument is set.
# * :use_ssl - Whether requests should be made over SSL. If set to true, the :port argument
# will be implicitly set to 443, unless specified otherwise. Defaults to false.
- # * :persistent - Whether to use a persistent connection to the server. Having this on provides around a two fold
+ # * :persistent - Whether to use a persistent connection to the server. Having this on provides around a two fold
# performance increase but for long running processes some firewalls may find the long lived connection suspicious and close the connection.
# If you run into connection errors, try setting :persistent to false. Defaults to false.
# * :proxy - If you need to connect through a proxy, you can specify your proxy settings by specifying a :host, :port, :user, and :password
# with the :proxy option.
- # The :host setting is required if specifying a :proxy.
- #
+ # The :host setting is required if specifying a :proxy.
+ #
# AWS::S3::Bucket.established_connection!(:proxy => {
# :host => '...', :port => 8080, :user => 'marcel', :password => 'secret'
# })
def establish_connection!(options = {})
- # After you've already established the default connection, just specify
+ # After you've already established the default connection, just specify
# the difference for subsequent connections
options = default_connection.options.merge(options) if connected?
connections[connection_name] = Connection.connect(options)
end
-
+
# Returns the connection for the current class, or Base's default connection if the current class does not
# have its own connection.
#
@@ -213,12 +216,12 @@ def connection
raise NoConnectionEstablished
end
end
-
+
# Returns true if a connection has been made yet.
def connected?
!connections.empty?
end
-
+
# Removes the connection for the current class. If there is no connection for the current class, the default
# connection will be removed.
def disconnect(name = connection_name)
@@ -227,8 +230,8 @@ def disconnect(name = connection_name)
connection.http.finish if connection.persistent?
connections.delete(name)
end
-
- # Clears *all* connections, from all classes, with prejudice.
+
+ # Clears *all* connections, from all classes, with prejudice.
def disconnect!
connections.each_key {|connection| disconnect(connection)}
end
@@ -247,10 +250,10 @@ def default_connection
end
end
end
-
+
class Options < Hash #:nodoc:
- VALID_OPTIONS = [:access_key_id, :secret_access_key, :server, :port, :use_ssl, :persistent, :proxy].freeze
-
+ VALID_OPTIONS = [:access_key_id, :secret_access_key, :security_token, :server, :port, :use_ssl, :persistent, :proxy].freeze
+
def initialize(options = {})
super()
validate(options)
@@ -261,11 +264,11 @@ def initialize(options = {})
def connecting_through_proxy?
!self[:proxy].nil?
end
-
+
def proxy_settings
self[:proxy].values_at(:host, :port, :user, :password)
end
-
+
private
def validate(options)
invalid_options = options.keys - VALID_OPTIONS
diff --git a/lib/aws/s3/version.rb b/lib/aws/s3/version.rb
index 92547b5..e830a08 100644
--- a/lib/aws/s3/version.rb
+++ b/lib/aws/s3/version.rb
@@ -3,10 +3,10 @@ module S3
module VERSION #:nodoc:
MAJOR = '0'
MINOR = '6'
- TINY = '3'
+ TINY = '4'
BETA = nil # Time.now.to_i.to_s
end
-
+
Version = [VERSION::MAJOR, VERSION::MINOR, VERSION::TINY, VERSION::BETA].compact * '.'
end
end
diff --git a/test/authentication_test.rb b/test/authentication_test.rb
index eed7498..7e4637f 100644
--- a/test/authentication_test.rb
+++ b/test/authentication_test.rb
@@ -1,18 +1,18 @@
require File.dirname(__FILE__) + '/test_helper'
-class HeaderAuthenticationTest < Test::Unit::TestCase
+class HeaderAuthenticationTest < Test::Unit::TestCase
def test_encoded_canonical
signature = Authentication::Signature.new(request, key_id, secret)
assert_equal AmazonDocExampleData::Example1.canonical_string, signature.send(:canonical_string)
assert_equal AmazonDocExampleData::Example1.signature, signature.send(:encoded_canonical)
end
-
+
def test_authorization_header
header = Authentication::Header.new(request, key_id, secret)
assert_equal AmazonDocExampleData::Example1.canonical_string, header.send(:canonical_string)
assert_equal AmazonDocExampleData::Example1.authorization_header, header
end
-
+
private
def request; AmazonDocExampleData::Example1.request end
def key_id ; AmazonDocExampleData::Example1.access_key_id end
@@ -25,13 +25,13 @@ def test_query_string
assert_equal AmazonDocExampleData::Example3.canonical_string, query_string.send(:canonical_string)
assert_equal AmazonDocExampleData::Example3.query_string, query_string
end
-
+
def test_query_string_with_explicit_expiry
query_string = Authentication::QueryString.new(request, key_id, secret, :expires => expires)
assert_equal expires, query_string.send(:canonical_string).instance_variable_get(:@options)[:expires]
assert_equal AmazonDocExampleData::Example3.query_string, query_string
end
-
+
def test_expires_in_is_coerced_to_being_an_integer_in_case_it_is_a_special_integer_proxy
# References bug: http://rubyforge.org/tracker/index.php?func=detail&aid=17458&group_id=2409&atid=9356
integer_proxy = Class.new do
@@ -39,17 +39,17 @@ def test_expires_in_is_coerced_to_being_an_integer_in_case_it_is_a_special_integ
def initialize(integer)
@integer = integer
end
-
+
def to_int
integer
end
end
-
+
actual_integer = 25
query_string = Authentication::QueryString.new(request, key_id, secret, :expires_in => integer_proxy.new(actual_integer))
assert_equal actual_integer, query_string.send(:expires_in)
end
-
+
private
def request; AmazonDocExampleData::Example3.request end
def key_id ; AmazonDocExampleData::Example3.access_key_id end
@@ -57,23 +57,23 @@ def secret ; AmazonDocExampleData::Example3.secret_access_key end
def expires; AmazonDocExampleData::Example3.expires end
end
-class CanonicalStringTest < Test::Unit::TestCase
+class CanonicalStringTest < Test::Unit::TestCase
def setup
@request = Net::HTTP::Post.new('/test')
@canonical_string = Authentication::CanonicalString.new(@request)
end
-
+
def test_path_does_not_include_query_string
request = Net::HTTP::Get.new('/test/query/string?foo=bar&baz=quux')
assert_equal '/test/query/string', Authentication::CanonicalString.new(request).send(:path)
-
+
# Make sure things still work when there is *no* query string
request = Net::HTTP::Get.new('/')
assert_equal '/', Authentication::CanonicalString.new(request).send(:path)
request = Net::HTTP::Get.new('/foo/bar')
assert_equal '/foo/bar', Authentication::CanonicalString.new(request).send(:path)
end
-
+
def test_path_includes_significant_query_strings
significant_query_strings = [
['/test/query/string?acl', '/test/query/string?acl'],
@@ -85,29 +85,42 @@ def test_path_includes_significant_query_strings
['/test/query/string?bar=baz&acl=foo', '/test/query/string?acl'],
['/test/query/string?acl&response-content-disposition=1', '/test/query/string?acl&response-content-disposition=1']
]
-
+
significant_query_strings.each do |uncleaned_path, expected_cleaned_path|
assert_equal expected_cleaned_path, Authentication::CanonicalString.new(Net::HTTP::Get.new(uncleaned_path)).send(:path)
end
end
-
+
def test_default_headers_set
Authentication::CanonicalString.default_headers.each do |header|
assert @canonical_string.headers.include?(header)
end
end
-
+
def test_interesting_headers_are_copied_over
an_interesting_header = 'content-md5'
string_without_interesting_header = Authentication::CanonicalString.new(@request)
assert string_without_interesting_header.headers[an_interesting_header].empty?
-
+
# Add an interesting header
@request[an_interesting_header] = 'foo'
string_with_interesting_header = Authentication::CanonicalString.new(@request)
assert_equal 'foo', string_with_interesting_header.headers[an_interesting_header]
end
-
+
+ def test_security_token_is_copied_over
+ security_token_header = 'x-amz-security-token'
+ string_without_security_token = Authentication::CanonicalString.new(@request)
+ assert !string_without_security_token.headers.has_key?(security_token_header)
+
+ # Add the security token
+ string_with_security_token = Authentication::CanonicalString.new(@request, :security_token => "123456")
+ assert_equal '123456', string_with_security_token.headers[security_token_header]
+
+ # Ensure the security token made a difference in the signature
+ assert_not_equal string_without_security_token, string_with_security_token
+ end
+
def test_canonical_string
request = AmazonDocExampleData::Example1.request
assert_equal AmazonDocExampleData::Example1.canonical_string, Authentication::CanonicalString.new(request)
diff --git a/test/connection_test.rb b/test/connection_test.rb
index 175cb12..17091d6 100644
--- a/test/connection_test.rb
+++ b/test/connection_test.rb
@@ -5,70 +5,70 @@ class ConnectionTest < Test::Unit::TestCase
def setup
@keys = {:access_key_id => '123', :secret_access_key => 'abc'}.freeze
end
-
+
def test_creating_a_connection
connection = Connection.new(keys)
assert_kind_of Net::HTTP, connection.http
end
-
+
def test_use_ssl_option_is_set_in_connection
connection = Connection.new(keys.merge(:use_ssl => true))
assert connection.http.use_ssl?
end
-
+
def test_setting_port_to_443_implies_use_ssl
connection = Connection.new(keys.merge(:port => 443))
assert connection.http.use_ssl?
end
-
+
def test_protocol
connection = Connection.new(keys)
assert_equal 'http://', connection.protocol
connection = Connection.new(keys.merge(:use_ssl => true))
assert_equal 'https://', connection.protocol
end
-
+
def test_url_for_honors_use_ssl_option_if_it_is_false_even_if_connection_has_use_ssl_option_set
# References bug: http://rubyforge.org/tracker/index.php?func=detail&aid=17628&group_id=2409&atid=9356
connection = Connection.new(keys.merge(:use_ssl => true))
assert_match %r(^http://), connection.url_for('/pathdoesnotmatter', :authenticated => false, :use_ssl => false)
end
-
+
def test_connection_is_not_persistent_by_default
connection = Connection.new(keys)
assert !connection.persistent?
-
+
connection = Connection.new(keys.merge(:persistent => true))
assert connection.persistent?
end
-
+
def test_server_and_port_are_passed_onto_connection
connection = Connection.new(keys)
options = connection.instance_variable_get('@options')
assert_equal connection.http.address, options[:server]
assert_equal connection.http.port, options[:port]
end
-
+
def test_not_including_required_access_keys_raises
assert_raises(MissingAccessKey) do
Connection.new
end
-
+
assert_raises(MissingAccessKey) do
Connection.new(:access_key_id => '123')
end
-
+
assert_nothing_raised do
Connection.new(keys)
end
end
-
+
def test_access_keys_extracted
connection = Connection.new(keys)
assert_equal '123', connection.access_key_id
assert_equal 'abc', connection.secret_access_key
end
-
+
def test_request_method_class_lookup
connection = Connection.new(keys)
expectations = {
@@ -76,7 +76,7 @@ def test_request_method_class_lookup
:put => Net::HTTP::Put, :delete => Net::HTTP::Delete,
:head => Net::HTTP::Head
}
-
+
expectations.each do |verb, klass|
assert_equal klass, connection.send(:request_method, verb)
end
@@ -108,7 +108,7 @@ def test_url_for_with_canonical_query_params
dispositioned = lambda {|url| url['?response-content-disposition=a']}
assert dispositioned[connection.url_for("/foo?response-content-disposition=a")]
end
-
+
def test_connecting_through_a_proxy
connection = nil
assert_nothing_raised do
@@ -116,18 +116,18 @@ def test_connecting_through_a_proxy
end
assert connection.http.proxy?
end
-
+
def test_request_only_escapes_the_path_the_first_time_it_runs_and_not_subsequent_times
connection = Connection.new(@keys)
unescaped_path = 'path with spaces'
escaped_path = 'path%20with%20spaces'
-
+
flexmock(Connection).should_receive(:prepare_path).with(unescaped_path).once.and_return(escaped_path).ordered
flexmock(connection.http).should_receive(:request).and_raise(Errno::EPIPE).ordered
flexmock(connection.http).should_receive(:request).ordered
connection.request :put, unescaped_path
end
-
+
def test_if_request_has_no_body_then_the_content_length_is_set_to_zero
# References bug: http://rubyforge.org/tracker/index.php?func=detail&aid=13052&group_id=2409&atid=9356
connection = Connection.new(@keys)
@@ -138,83 +138,89 @@ def test_if_request_has_no_body_then_the_content_length_is_set_to_zero
end
class ConnectionOptionsTest < Test::Unit::TestCase
-
+
def setup
@options = generate_options(:server => 'example.org', :port => 555)
@default_options = generate_options
end
-
+
def test_server_extracted
assert_key_transfered(:server, 'example.org', @options)
end
-
+
def test_port_extracted
assert_key_transfered(:port, 555, @options)
end
-
+
+ def test_security_token
+ options = generate_options(:security_token => '12345')
+ assert_equal "12345", options[:security_token]
+ assert !@default_options.has_key?(:security_token)
+ end
+
def test_server_defaults_to_default_host
assert_equal DEFAULT_HOST, @default_options[:server]
end
-
+
def test_port_defaults_to_80_if_use_ssl_is_false
assert_equal 80, @default_options[:port]
end
-
+
def test_port_is_set_to_443_if_use_ssl_is_true
options = generate_options(:use_ssl => true)
assert_equal 443, options[:port]
end
-
+
def test_explicit_port_trumps_use_ssl
options = generate_options(:port => 555, :use_ssl => true)
assert_equal 555, options[:port]
end
-
+
def test_invalid_options_raise
assert_raises(InvalidConnectionOption) do
generate_options(:host => 'campfire.s3.amazonaws.com')
end
end
-
+
def test_not_specifying_all_required_proxy_settings_raises
assert_raises(ArgumentError) do
generate_options(:proxy => {})
end
end
-
+
def test_not_specifying_proxy_option_at_all_does_not_raise
assert_nothing_raised do
generate_options
end
end
-
+
def test_specifying_all_required_proxy_settings
assert_nothing_raised do
generate_options(:proxy => sample_proxy_settings)
end
end
-
+
def test_only_host_setting_is_required
assert_nothing_raised do
generate_options(:proxy => {:host => 'http://google.com'})
end
end
-
+
def test_proxy_settings_are_extracted
options = generate_options(:proxy => sample_proxy_settings)
assert_equal sample_proxy_settings.values.map {|value| value.to_s}.sort, options.proxy_settings.map {|value| value.to_s}.sort
end
-
+
def test_recognizing_that_the_settings_want_to_connect_through_a_proxy
options = generate_options(:proxy => sample_proxy_settings)
assert options.connecting_through_proxy?
end
-
+
private
def assert_key_transfered(key, value, options)
assert_equal value, options[key]
end
-
+
def generate_options(options = {})
Connection::Options.new(options)
end