qoobaa-s3 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.document +5 -0
- data/.gitignore +6 -0
- data/LICENSE +20 -0
- data/README.rdoc +7 -0
- data/Rakefile +58 -0
- data/VERSION +1 -0
- data/bin/s3cmd.rb +188 -0
- data/lib/s3.rb +23 -0
- data/lib/s3/bucket.rb +154 -0
- data/lib/s3/connection.rb +152 -0
- data/lib/s3/exceptions.rb +94 -0
- data/lib/s3/object.rb +153 -0
- data/lib/s3/roxy/moxie.rb +58 -0
- data/lib/s3/roxy/proxy.rb +72 -0
- data/lib/s3/service.rb +106 -0
- data/lib/s3/signature.rb +120 -0
- data/s3.gemspec +65 -0
- data/test/bucket_test.rb +22 -0
- data/test/connection_test.rb +164 -0
- data/test/s3_test.rb +5 -0
- data/test/service_test.rb +133 -0
- data/test/signature_test.rb +143 -0
- data/test/test_helper.rb +11 -0
- metadata +82 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
# Copyright (c) 2008 Ryan Daigle
|
2
|
+
|
3
|
+
# Permission is hereby granted, free of charge, to any person
|
4
|
+
# obtaining a copy of this software and associated documentation files
|
5
|
+
# (the "Software"), to deal in the Software without restriction,
|
6
|
+
# including without limitation the rights to use, copy, modify, merge,
|
7
|
+
# publish, distribute, sublicense, and/or sell copies of the Software,
|
8
|
+
# and to permit persons to whom the Software is furnished to do so,
|
9
|
+
# subject to the following conditions:
|
10
|
+
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
18
|
+
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
19
|
+
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
20
|
+
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
# SOFTWARE.
|
22
|
+
|
23
|
+
module S3
|
24
|
+
module Roxy
|
25
|
+
# The very simple proxy class that provides a basic pass-through
|
26
|
+
# mechanism between the proxy owner and the proxy target.
|
27
|
+
class Proxy
|
28
|
+
|
29
|
+
alias :proxy_instance_eval :instance_eval
|
30
|
+
alias :proxy_extend :extend
|
31
|
+
|
32
|
+
# Make sure the proxy is as dumb as it can be.
|
33
|
+
# Blatanly taken from Jim Wierich's BlankSlate post:
|
34
|
+
# http://onestepback.org/index.cgi/Tech/Ruby/BlankSlate.rdoc
|
35
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^proxy_|^object_id)/ }
|
36
|
+
|
37
|
+
def initialize(owner, options, args, &block)
|
38
|
+
@owner = owner
|
39
|
+
@target = options[:to]
|
40
|
+
@args = args
|
41
|
+
|
42
|
+
# Adorn with user-provided proxy methods
|
43
|
+
[options[:extend]].flatten.each { |ext| proxy_extend(ext) } if options[:extend]
|
44
|
+
proxy_instance_eval &block if block_given?
|
45
|
+
end
|
46
|
+
|
47
|
+
def proxy_owner
|
48
|
+
@owner
|
49
|
+
end
|
50
|
+
|
51
|
+
def proxy_target
|
52
|
+
if @target.is_a?(Proc)
|
53
|
+
@target.call(@owner)
|
54
|
+
elsif @target.is_a?(UnboundMethod)
|
55
|
+
bound_method = @target.bind(proxy_owner)
|
56
|
+
bound_method.arity == 0 ? bound_method.call : bound_method.call(*@args)
|
57
|
+
else
|
58
|
+
@target
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# def inspect
|
63
|
+
# "#<S3::Roxy::Proxy:0x#{object_id.to_s(16)}>"
|
64
|
+
# end
|
65
|
+
|
66
|
+
# Delegate all method calls we don't know about to target object
|
67
|
+
def method_missing(sym, *args, &block)
|
68
|
+
proxy_target.__send__(sym, *args, &block)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/s3/service.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
module S3
|
2
|
+
class Service
|
3
|
+
extend Roxy::Moxie
|
4
|
+
|
5
|
+
attr_reader :access_key_id, :secret_access_key, :use_ssl
|
6
|
+
|
7
|
+
def ==(other)
|
8
|
+
self.access_key_id == other.access_key_id and self.secret_access_key == other.secret_access_key
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(options)
|
12
|
+
@access_key_id = options[:access_key_id] or raise ArgumentError.new("No access key id given")
|
13
|
+
@secret_access_key = options[:secret_access_key] or raise ArgumentError.new("No secret access key given")
|
14
|
+
@use_ssl = options[:use_ssl]
|
15
|
+
@timeout = options[:timeout]
|
16
|
+
@debug = options[:debug]
|
17
|
+
end
|
18
|
+
|
19
|
+
def buckets(reload = false)
|
20
|
+
if reload or @buckets.nil?
|
21
|
+
response = service_request(:get)
|
22
|
+
@buckets = parse_buckets(response.body)
|
23
|
+
else
|
24
|
+
@buckets
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def protocol
|
29
|
+
use_ssl ? "https://" : "http://"
|
30
|
+
end
|
31
|
+
|
32
|
+
def port
|
33
|
+
use_ssl ? 443 : 80
|
34
|
+
end
|
35
|
+
|
36
|
+
proxy :buckets do
|
37
|
+
def build(name)
|
38
|
+
Bucket.new(proxy_owner, name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_first(name)
|
42
|
+
bucket = build(name)
|
43
|
+
bucket.retrieve
|
44
|
+
end
|
45
|
+
alias :find :find_first
|
46
|
+
|
47
|
+
def find_all
|
48
|
+
proxy_target
|
49
|
+
end
|
50
|
+
|
51
|
+
def reload
|
52
|
+
proxy_owner.buckets(true)
|
53
|
+
end
|
54
|
+
|
55
|
+
def destroy_all(force = false)
|
56
|
+
proxy_target.each do |bucket|
|
57
|
+
begin
|
58
|
+
bucket.destroy
|
59
|
+
rescue Error::BucketNotEmpty
|
60
|
+
if force
|
61
|
+
bucket.objects.destroy_all
|
62
|
+
retry
|
63
|
+
else
|
64
|
+
raise
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def inspect
|
72
|
+
"#<#{self.class}:#@access_key_id>"
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def service_request(method, options = {})
|
78
|
+
connection.request(method, options.merge(:path => "/#{options[:path]}"))
|
79
|
+
end
|
80
|
+
|
81
|
+
def connection
|
82
|
+
if @connection.nil?
|
83
|
+
@connection = Connection.new
|
84
|
+
@connection.access_key_id = @access_key_id
|
85
|
+
@connection.secret_access_key = @secret_access_key
|
86
|
+
@connection.use_ssl = @use_ssl
|
87
|
+
@connection.timeout = @timeout
|
88
|
+
@connection.debug = @debug
|
89
|
+
end
|
90
|
+
@connection
|
91
|
+
end
|
92
|
+
|
93
|
+
def parse_buckets(xml_body)
|
94
|
+
xml = XmlSimple.xml_in(xml_body)
|
95
|
+
buckets = xml["Buckets"].first["Bucket"]
|
96
|
+
if buckets
|
97
|
+
buckets_names = buckets.map { |bucket| bucket["Name"].first }
|
98
|
+
buckets_names.map do |bucket_name|
|
99
|
+
Bucket.new(self, bucket_name)
|
100
|
+
end
|
101
|
+
else
|
102
|
+
[]
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
data/lib/s3/signature.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
module S3
|
2
|
+
class Signature
|
3
|
+
|
4
|
+
# Required options: host, request, access_key_id, secret_access_key
|
5
|
+
def self.generate(options)
|
6
|
+
request = options[:request]
|
7
|
+
host = options[:host]
|
8
|
+
access_key_id = options[:access_key_id]
|
9
|
+
secret_access_key = options[:secret_access_key]
|
10
|
+
|
11
|
+
http_verb = request.method
|
12
|
+
content_md5 = request["content-md5"] || ""
|
13
|
+
content_type = request["content-type"] || ""
|
14
|
+
date = request["x-amz-date"].nil? ? request["date"] : ""
|
15
|
+
canonicalized_resource = canonicalized_resource(host, request)
|
16
|
+
canonicalized_amz_headers = canonicalized_amz_headers(request)
|
17
|
+
|
18
|
+
string_to_sign = ""
|
19
|
+
string_to_sign << http_verb
|
20
|
+
string_to_sign << "\n"
|
21
|
+
string_to_sign << content_md5
|
22
|
+
string_to_sign << "\n"
|
23
|
+
string_to_sign << content_type
|
24
|
+
string_to_sign << "\n"
|
25
|
+
string_to_sign << date
|
26
|
+
string_to_sign << "\n"
|
27
|
+
string_to_sign << canonicalized_amz_headers
|
28
|
+
string_to_sign << canonicalized_resource
|
29
|
+
|
30
|
+
digest = OpenSSL::Digest::Digest.new('sha1')
|
31
|
+
hmac = OpenSSL::HMAC.digest(digest, secret_access_key, string_to_sign)
|
32
|
+
base64 = Base64.encode64(hmac)
|
33
|
+
signature = base64.chomp
|
34
|
+
|
35
|
+
"AWS #{access_key_id}:#{signature}"
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def self.canonicalized_amz_headers(request)
|
41
|
+
headers = []
|
42
|
+
|
43
|
+
# 1. Convert each HTTP header name to lower-case. For example,
|
44
|
+
# 'X-Amz-Date' becomes 'x-amz-date'.
|
45
|
+
request.each { |key, value| headers << [key.downcase, value] if key =~ /\Ax-amz-/io }
|
46
|
+
#=> [["c", 0], ["a", 1], ["a", 2], ["b", 3]]
|
47
|
+
|
48
|
+
# 2. Sort the collection of headers lexicographically by header
|
49
|
+
# name.
|
50
|
+
headers.sort!
|
51
|
+
#=> [["a", 1], ["a", 2], ["b", 3], ["c", 0]]
|
52
|
+
|
53
|
+
# 3. Combine header fields with the same name into one
|
54
|
+
# "header-name:comma-separated-value-list" pair as prescribed by
|
55
|
+
# RFC 2616, section 4.2, without any white-space between
|
56
|
+
# values. For example, the two metadata headers
|
57
|
+
# 'x-amz-meta-username: fred' and 'x-amz-meta-username: barney'
|
58
|
+
# would be combined into the single header 'x-amz-meta-username:
|
59
|
+
# fred,barney'.
|
60
|
+
groupped_headers = headers.group_by { |i| i.first }
|
61
|
+
#=> {"a"=>[["a", 1], ["a", 2]], "b"=>[["b", 3]], "c"=>[["c", 0]]}
|
62
|
+
combined_headers = groupped_headers.map do |key, value|
|
63
|
+
values = value.map { |e| e.last }
|
64
|
+
[key, values.join(",")]
|
65
|
+
end
|
66
|
+
#=> [["a", "1,2"], ["b", "3"], ["c", "0"]]
|
67
|
+
|
68
|
+
# 4. "Un-fold" long headers that span multiple lines (as allowed
|
69
|
+
# by RFC 2616, section 4.2) by replacing the folding white-space
|
70
|
+
# (including new-line) by a single space.
|
71
|
+
unfolded_headers = combined_headers.map do |header|
|
72
|
+
key = header.first
|
73
|
+
value = header.last
|
74
|
+
value.gsub!(/\s+/, " ")
|
75
|
+
[key, value]
|
76
|
+
end
|
77
|
+
|
78
|
+
# 5. Trim any white-space around the colon in the header. For
|
79
|
+
# example, the header 'x-amz-meta-username: fred,barney' would
|
80
|
+
# become 'x-amz-meta-username:fred,barney'
|
81
|
+
joined_headers = unfolded_headers.map do |header|
|
82
|
+
key = header.first.strip
|
83
|
+
value = header.last.strip
|
84
|
+
"#{key}:#{value}"
|
85
|
+
end
|
86
|
+
|
87
|
+
# 6. Finally, append a new-line (U+000A) to each canonicalized
|
88
|
+
# header in the resulting list. Construct the
|
89
|
+
# CanonicalizedResource element by concatenating all headers in
|
90
|
+
# this list into a single string.
|
91
|
+
joined_headers << "" unless joined_headers.empty?
|
92
|
+
joined_headers.join("\n")
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.canonicalized_resource(host, request)
|
96
|
+
# 1. Start with the empty string ("").
|
97
|
+
string = ""
|
98
|
+
|
99
|
+
# 2. If the request specifies a bucket using the HTTP Host
|
100
|
+
# header (virtual hosted-style), append the bucket name preceded
|
101
|
+
# by a "/" (e.g., "/bucketname"). For path-style requests and
|
102
|
+
# requests that don't address a bucket, do nothing. For more
|
103
|
+
# information on virtual hosted-style requests, see Virtual
|
104
|
+
# Hosting of Buckets.
|
105
|
+
bucket_name = host.sub(/\.?s3\.amazonaws\.com\Z/, "")
|
106
|
+
string << "/#{bucket_name}" unless bucket_name.empty?
|
107
|
+
|
108
|
+
# 3. Append the path part of the un-decoded HTTP Request-URI,
|
109
|
+
# up-to but not including the query string.
|
110
|
+
uri = URI.parse(request.path)
|
111
|
+
string << uri.path
|
112
|
+
|
113
|
+
# 4. If the request addresses a sub-resource, like ?location,
|
114
|
+
# ?acl, or ?torrent, append the sub-resource including question
|
115
|
+
# mark.
|
116
|
+
string << "?#{$1}" if uri.query =~ /&?(acl|torrent|logging|location)(?:&|=|\Z)/
|
117
|
+
string
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
data/s3.gemspec
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{s3}
|
5
|
+
s.version = "0.0.3"
|
6
|
+
|
7
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
8
|
+
s.authors = ["Jakub Kuźma", "Mirosław Boruta"]
|
9
|
+
s.date = %q{2009-07-03}
|
10
|
+
s.default_executable = %q{s3cmd.rb}
|
11
|
+
s.email = %q{qoobaa@gmail.com}
|
12
|
+
s.executables = ["s3cmd.rb"]
|
13
|
+
s.extra_rdoc_files = [
|
14
|
+
"LICENSE",
|
15
|
+
"README.rdoc"
|
16
|
+
]
|
17
|
+
s.files = [
|
18
|
+
".document",
|
19
|
+
".gitignore",
|
20
|
+
"LICENSE",
|
21
|
+
"README.rdoc",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"bin/s3cmd.rb",
|
25
|
+
"lib/s3.rb",
|
26
|
+
"lib/s3/bucket.rb",
|
27
|
+
"lib/s3/connection.rb",
|
28
|
+
"lib/s3/exceptions.rb",
|
29
|
+
"lib/s3/object.rb",
|
30
|
+
"lib/s3/roxy/moxie.rb",
|
31
|
+
"lib/s3/roxy/proxy.rb",
|
32
|
+
"lib/s3/service.rb",
|
33
|
+
"lib/s3/signature.rb",
|
34
|
+
"s3.gemspec",
|
35
|
+
"test/bucket_test.rb",
|
36
|
+
"test/connection_test.rb",
|
37
|
+
"test/s3_test.rb",
|
38
|
+
"test/service_test.rb",
|
39
|
+
"test/signature_test.rb",
|
40
|
+
"test/test_helper.rb"
|
41
|
+
]
|
42
|
+
s.homepage = %q{http://github.com/qoobaa/s3}
|
43
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
44
|
+
s.require_paths = ["lib"]
|
45
|
+
s.rubygems_version = %q{1.3.4}
|
46
|
+
s.summary = %q{Library for accessing S3 objects and buckets, with command line tool}
|
47
|
+
s.test_files = [
|
48
|
+
"test/s3_test.rb",
|
49
|
+
"test/bucket_test.rb",
|
50
|
+
"test/service_test.rb",
|
51
|
+
"test/signature_test.rb",
|
52
|
+
"test/connection_test.rb",
|
53
|
+
"test/test_helper.rb"
|
54
|
+
]
|
55
|
+
|
56
|
+
if s.respond_to? :specification_version then
|
57
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
58
|
+
s.specification_version = 3
|
59
|
+
|
60
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
61
|
+
else
|
62
|
+
end
|
63
|
+
else
|
64
|
+
end
|
65
|
+
end
|
data/test/bucket_test.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class BucketTest < Test::Unit::TestCase
|
4
|
+
# TODO fix or remove
|
5
|
+
# def test_parse_name_without_bucket
|
6
|
+
# host, prefix = S3::Bucket.parse_name("", "s3.amazonaws.com")
|
7
|
+
# assert_equal "s3.amazonaws.com", host
|
8
|
+
# assert_equal "", prefix
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# def test_parse_name_with_vhost_name
|
12
|
+
# host, prefix = S3::Bucket.parse_name("data.synergypeople.net", "s3.amazonaws.com")
|
13
|
+
# assert_equal "data.synergypeople.net.s3.amazonaws.com", host
|
14
|
+
# assert_equal "", prefix
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# def test_parse_name_with_path_based_name
|
18
|
+
# host, prefix = S3::Bucket.parse_name("synergypeople_net", "s3.amazonaws.com")
|
19
|
+
# assert_equal "s3.amazonaws.com", host
|
20
|
+
# assert_equal "/synergypeople_net", prefix
|
21
|
+
# end
|
22
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ConnectionTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@connection = S3::Connection.new(
|
6
|
+
:access_key_id => "12345678901234567890",
|
7
|
+
:secret_access_key => "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDF"
|
8
|
+
)
|
9
|
+
@http_request = Net::HTTP.new("")
|
10
|
+
@response_ok = Net::HTTPOK.new("1.1", "200", "OK")
|
11
|
+
@response_not_found = Net::HTTPNotFound.new("1.1", "404", "Not Found")
|
12
|
+
stub(@connection).http { @http_request }
|
13
|
+
stub(@http_request).start { @response_ok }
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_handle_response_not_modify_response_when_ok
|
17
|
+
assert_nothing_raised do
|
18
|
+
response = @connection.request(
|
19
|
+
:get,
|
20
|
+
:host => "s3.amazonaws.com",
|
21
|
+
:path => "/"
|
22
|
+
)
|
23
|
+
assert_equal @response_ok, response
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_handle_response_throws_exception_when_not_ok
|
28
|
+
response_body = <<-EOFakeBody
|
29
|
+
<?xml version=\"1.0\" encoding=\"UTF-8\"?>
|
30
|
+
<SomeResult xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\">
|
31
|
+
<Code>NoSuchBucket</Code>
|
32
|
+
<Message>The specified bucket does not exist</Message>
|
33
|
+
</SomeResult>
|
34
|
+
EOFakeBody
|
35
|
+
stub(@http_request).start { @response_not_found }
|
36
|
+
stub(@response_not_found).body { response_body }
|
37
|
+
|
38
|
+
assert_raise S3::Error::NoSuchBucket do
|
39
|
+
response = @connection.request(
|
40
|
+
:get,
|
41
|
+
:host => "data.example.com.s3.amazonaws.com",
|
42
|
+
:path => "/"
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_handle_response_throws_standard_exception_when_not_ok
|
48
|
+
stub(@http_request).start { @response_not_found }
|
49
|
+
stub(@response_not_found).body { nil }
|
50
|
+
assert_raise S3::Error::ResponseError do
|
51
|
+
response = @connection.request(
|
52
|
+
:get,
|
53
|
+
:host => "data.example.com.s3.amazonaws.com",
|
54
|
+
:path => "/"
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
stub(@response_not_found).body { "" }
|
59
|
+
assert_raise S3::Error::ResponseError do
|
60
|
+
response = @connection.request(
|
61
|
+
:get,
|
62
|
+
:host => "data.example.com.s3.amazonaws.com",
|
63
|
+
:path => "/"
|
64
|
+
)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_parse_params_empty
|
69
|
+
expected = ""
|
70
|
+
actual = S3::Connection.parse_params({})
|
71
|
+
assert_equal expected, actual
|
72
|
+
end
|
73
|
+
|
74
|
+
def test_parse_params_only_interesting_params
|
75
|
+
expected = ""
|
76
|
+
actual = S3::Connection.parse_params(:param1 => "1", :maxkeys => "2")
|
77
|
+
assert_equal expected, actual
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_parse_params_remove_underscore
|
81
|
+
expected = "max-keys=100"
|
82
|
+
actual = S3::Connection.parse_params(:max_keys => 100)
|
83
|
+
assert_equal expected, actual
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_parse_params_with_and_without_values
|
87
|
+
expected = "max-keys=100&prefix"
|
88
|
+
actual = S3::Connection.parse_params(:max_keys => 100, :prefix => nil)
|
89
|
+
assert_equal expected, actual
|
90
|
+
end
|
91
|
+
|
92
|
+
def test_headers_headers_empty
|
93
|
+
expected = {}
|
94
|
+
actual = S3::Connection.parse_headers({})
|
95
|
+
assert_equal expected, actual
|
96
|
+
end
|
97
|
+
|
98
|
+
def test_parse_headers_only_interesting_headers
|
99
|
+
expected = {}
|
100
|
+
actual = S3::Connection.parse_headers(
|
101
|
+
:accept => "text/*, text/html, text/html;level=1, */*",
|
102
|
+
:accept_charset => "iso-8859-2, unicode-1-1;q=0.8"
|
103
|
+
)
|
104
|
+
assert_equal expected, actual
|
105
|
+
end
|
106
|
+
|
107
|
+
def test_parse_headers_remove_underscore
|
108
|
+
expected = {
|
109
|
+
"content-type" => nil,
|
110
|
+
"x-amz-acl" => nil,
|
111
|
+
"if-modified-since" => nil,
|
112
|
+
"if-unmodified-since" => nil,
|
113
|
+
"if-match" => nil,
|
114
|
+
"if-none-match" => nil,
|
115
|
+
"content-disposition" => nil,
|
116
|
+
"content-encoding" => nil
|
117
|
+
}
|
118
|
+
actual = S3::Connection.parse_headers(
|
119
|
+
:content_type => nil,
|
120
|
+
:x_amz_acl => nil,
|
121
|
+
:if_modified_since => nil,
|
122
|
+
:if_unmodified_since => nil,
|
123
|
+
:if_match => nil,
|
124
|
+
:if_none_match => nil,
|
125
|
+
:content_disposition => nil,
|
126
|
+
:content_encoding => nil
|
127
|
+
)
|
128
|
+
assert_equal expected, actual
|
129
|
+
end
|
130
|
+
|
131
|
+
def test_parse_headers_with_values
|
132
|
+
expected = {
|
133
|
+
"content-type" => "text/html",
|
134
|
+
"x-amz-acl" => "public-read",
|
135
|
+
"if-modified-since" => "today",
|
136
|
+
"if-unmodified-since" => "tomorrow",
|
137
|
+
"if-match" => "1234",
|
138
|
+
"if-none-match" => "1243",
|
139
|
+
"content-disposition" => "inline",
|
140
|
+
"content-encoding" => "gzip"
|
141
|
+
}
|
142
|
+
actual = S3::Connection.parse_headers(
|
143
|
+
:content_type => "text/html",
|
144
|
+
:x_amz_acl => "public-read",
|
145
|
+
:if_modified_since => "today",
|
146
|
+
:if_unmodified_since => "tomorrow",
|
147
|
+
:if_match => "1234",
|
148
|
+
:if_none_match => "1243",
|
149
|
+
:content_disposition => "inline",
|
150
|
+
:content_encoding => "gzip"
|
151
|
+
)
|
152
|
+
assert_equal expected, actual
|
153
|
+
end
|
154
|
+
|
155
|
+
def test_parse_headers_with_range
|
156
|
+
expected = {
|
157
|
+
"range" => "bytes=0-100"
|
158
|
+
}
|
159
|
+
actual = S3::Connection.parse_headers(
|
160
|
+
:range => 0..100
|
161
|
+
)
|
162
|
+
assert_equal expected, actual
|
163
|
+
end
|
164
|
+
end
|