s3 0.3.11 → 0.3.12

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1e94aa6f4e9214e728160e133f28264d1bd00b25
4
+ data.tar.gz: e3e15a25e2ef8f5882f088ec487ebb177cbc56b9
5
+ SHA512:
6
+ metadata.gz: 2faf2eb87b1bf05b904dea7134e4efddd14b118963a7a0bb817d26e3277a43072c180bb4fe92a1bec92ac5fc97f42f558144e33e27d890487bdbffdfff4969fb
7
+ data.tar.gz: e19ef4917499f81051c263883660353a5a17c1c334cae53cc212f251c2ded50520d0cc1970debc09f76876bcd03b0818f93958d27f3e2b7bc152337e6cfc40a6
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- s3 (0.3.11)
4
+ s3 (0.3.12)
5
5
  proxies (~> 0.2.0)
6
6
 
7
7
  GEM
data/README.rdoc CHANGED
@@ -28,6 +28,15 @@ It supports both: European and US buckets through the {REST API}[http://docs.ama
28
28
  first_bucket = service.buckets.find("first-bucket")
29
29
  #=> #<S3::Bucket:first-bucket>
30
30
 
31
+ === Create bucket
32
+
33
+ new_bucket = service.buckets.build("newbucketname")
34
+ new_bucket.save(:location => :eu)
35
+
36
+ Remember that bucket name for EU can't include "_" (underscore).
37
+
38
+ Please refer to: http://docs.aws.amazon.com/AmazonS3/latest/dev/BucketRestrictions.html for more information about bucket name restrictions.
39
+
31
40
  === List objects in a bucket
32
41
 
33
42
  first_bucket.objects
@@ -67,6 +76,24 @@ It supports both: European and US buckets through the {REST API}[http://docs.ama
67
76
  Please note that new objects are created with "public-read" ACL by
68
77
  default.
69
78
 
79
+ === Fetch ACL
80
+
81
+ object = bucket.objects.find('lenna.png')
82
+ object.request_acl # or bucket.request_acl
83
+
84
+ This will return hash with all users/groups and theirs permissions
85
+
86
+ === Modify ACL
87
+
88
+ object = bucket.objects.find("lenna.png")
89
+ object.copy(:key => "lenna.png", :bucket => bucket, :acl => :private)
90
+
91
+ === Upload file direct to amazon
92
+
93
+ ==== Rails 3
94
+
95
+ To do that you just send file using proper form direct to amazon. You can create simple halper for that, for example like this one: https://gist.github.com/3169039
96
+
70
97
  == See also
71
98
 
72
99
  * rubygems[http://rubygems.org/gems/s3]
data/lib/s3/bucket.rb CHANGED
@@ -4,14 +4,14 @@ module S3
4
4
  include Proxies
5
5
  extend Forwardable
6
6
 
7
- attr_reader :name, :service
7
+ attr_reader :name, :service, :acl
8
8
 
9
9
  def_instance_delegators :service, :service_request
10
10
  private_class_method :new
11
11
 
12
12
  # Retrieves the bucket information from the server. Raises an
13
13
  # S3::Error exception if the bucket doesn't exist or you don't
14
- # have access to it, etc.
14
+ # have access to it.
15
15
  def retrieve
16
16
  bucket_headers
17
17
  self
@@ -30,11 +30,34 @@ module S3
30
30
  self.name == other.name and self.service == other.service
31
31
  end
32
32
 
33
+ # Retrieves acl for bucket from the server.
34
+ #
35
+ # Return:
36
+ # hash: user|group => permission
37
+ def request_acl
38
+ body = bucket_request(:get, :params => "acl").body
39
+ parse_acl(body)
40
+ end
41
+
42
+ # Assigns a new ACL to the bucket. Please note that ACL is not
43
+ # retrieved from the server and set to "public-read" by default.
44
+ #
45
+ # Valid Values: :private | :public_read | :public_read_write | authenticated_read
46
+ #
47
+ # ==== Example
48
+ # bucket.acl = :public_read
49
+ def acl=(acl)
50
+ @acl = acl.to_s.gsub("_","-") if acl
51
+ end
52
+
33
53
  # Similar to retrieve, but catches S3::Error::NoSuchBucket
34
- # exceptions and returns false instead.
54
+ # exceptions and returns false instead. Also catch S3::Error::ForbiddenBucket
55
+ # and return true
35
56
  def exists?
36
57
  retrieve
37
58
  true
59
+ rescue Error::ForbiddenBucket
60
+ true
38
61
  rescue Error::NoSuchBucket
39
62
  false
40
63
  end
@@ -101,6 +124,14 @@ module S3
101
124
  "#<#{self.class}:#{name}>"
102
125
  end
103
126
 
127
+ def save_acl(options = {})
128
+ headers = {}
129
+ headers[:content_length] = 0
130
+ headers[:x_amz_acl] = options[:acl] || acl || "public-read"
131
+
132
+ response = bucket_request(:put, :headers => headers, :path => name)
133
+ end
134
+
104
135
  private
105
136
 
106
137
  attr_writer :service
@@ -135,10 +166,13 @@ module S3
135
166
  def bucket_headers(options = {})
136
167
  response = bucket_request(:head, :params => options)
137
168
  rescue Error::ResponseError => e
138
- if e.response.code.to_i == 404
139
- raise Error::ResponseError.exception("NoSuchBucket").new("The specified bucket does not exist.", nil)
140
- else
141
- raise e
169
+ case e.response.code.to_i
170
+ when 404
171
+ raise Error::ResponseError.exception("NoSuchBucket").new("The specified bucket does not exist.", nil)
172
+ when 403
173
+ raise Error::ResponseError.exception("ForbiddenBucket").new("The specified bucket exist but you do not have access to it.", nil)
174
+ else
175
+ raise e
142
176
  end
143
177
  end
144
178
 
@@ -174,5 +208,6 @@ module S3
174
208
  def name_valid?(name)
175
209
  name =~ /\A[a-z0-9][a-z0-9\._-]{2,254}\Z/i and name !~ /\A#{URI::REGEXP::PATTERN::IPV4ADDR}\Z/
176
210
  end
211
+
177
212
  end
178
213
  end
@@ -5,10 +5,13 @@ module S3
5
5
  Bucket.send(:new, proxy_owner, name)
6
6
  end
7
7
 
8
- # Finds the bucket with given name
8
+ # Finds the bucket with given name (only those which exist and You have access to it)
9
+ # return nil in case Error::NoSuchBucket or Error::ForbiddenBucket
9
10
  def find_first(name)
10
11
  bucket = build(name)
11
12
  bucket.retrieve
13
+ rescue Error::ForbiddenBucket, Error::NoSuchBucket
14
+ nil
12
15
  end
13
16
  alias :find :find_first
14
17
 
data/lib/s3/connection.rb CHANGED
@@ -128,7 +128,7 @@ module S3
128
128
  # Hash of headers translated from symbol to string, containing
129
129
  # only interesting headers
130
130
  def self.parse_headers(headers)
131
- interesting_keys = [:content_type, :cache_control, :x_amz_acl, :x_amz_storage_class, :range,
131
+ interesting_keys = [:content_type, :content_length, :cache_control, :x_amz_acl, :x_amz_storage_class, :range,
132
132
  :if_modified_since, :if_unmodified_since,
133
133
  :if_match, :if_none_match,
134
134
  :content_disposition, :content_encoding,
data/lib/s3/exceptions.rb CHANGED
@@ -52,6 +52,7 @@ module S3
52
52
  class EntityTooSmall < ResponseError; end
53
53
  class EntityTooLarge < ResponseError; end
54
54
  class ExpiredToken < ResponseError; end
55
+ class ForbiddenBucket < ResponseError; end
55
56
  class IncompleteBody < ResponseError; end
56
57
  class IncorrectNumberOfFilesInPostRequestPOST < ResponseError; end
57
58
  class InlineDataTooLarge < ResponseError; end
data/lib/s3/object.rb CHANGED
@@ -52,9 +52,11 @@ module S3
52
52
  end
53
53
 
54
54
  # Retrieves the object from the server. Method is used to download
55
- # object information only (content type, size and so on). It does
56
- # NOT download the content of the object (use the #content method
57
- # to do it).
55
+ # object information only (content type, size).
56
+ # Notice: It does NOT download the content of the object
57
+ # (use the #content method to do it).
58
+ # Notice: this do not fetch acl information, use #request_acl
59
+ # method for that.
58
60
  def retrieve
59
61
  object_headers
60
62
  self
@@ -70,6 +72,15 @@ module S3
70
72
  false
71
73
  end
72
74
 
75
+ # Retrieves acl for object from the server.
76
+ #
77
+ # Return:
78
+ # hash: user|group => permission
79
+ def request_acl
80
+ response = object_request(:get, :params => "acl")
81
+ parse_acl(response.body)
82
+ end
83
+
73
84
  # Downloads the content of the object, and caches it. Pass true to
74
85
  # clear the cache and download the object again.
75
86
  def content(reload = false)
@@ -107,7 +118,7 @@ module S3
107
118
  # Returns Object's URL using protocol specified in service,
108
119
  # e.g. <tt>http://domain.com.s3.amazonaws.com/key/with/path.extension</tt>
109
120
  def url
110
- URI.escape("#{protocol}#{host}/#{path_prefix}#{key}")
121
+ "#{protocol}#{host}/#{path_prefix}#{URI.escape(key, /[^#{URI::REGEXP::PATTERN::UNRESERVED}\/]/)}"
111
122
  end
112
123
 
113
124
  # Returns a temporary url to the object that expires on the
data/lib/s3/parser.rb CHANGED
@@ -48,5 +48,43 @@ module S3
48
48
  def parse_is_truncated xml
49
49
  rexml_document(xml).elements["ListBucketResult/IsTruncated"].text =='true'
50
50
  end
51
+
52
+
53
+ # Parse acl response and return hash with grantee and their permissions
54
+ def parse_acl(xml)
55
+ grants = {}
56
+ rexml_document(xml).elements.each("AccessControlPolicy/AccessControlList/Grant") do |grant|
57
+ grants.merge!(extract_grantee(grant))
58
+ end
59
+ grants
60
+ end
61
+
62
+ private
63
+
64
+ def convert_uri_to_group_name(uri)
65
+ case uri
66
+ when "http://acs.amazonaws.com/groups/global/AllUsers"
67
+ return "Everyone"
68
+ when "http://acs.amazonaws.com/groups/global/AuthenticatedUsers"
69
+ return "Authenticated Users"
70
+ when "http://acs.amazonaws.com/groups/s3/LogDelivery"
71
+ return "Log Delivery"
72
+ else
73
+ return uri
74
+ end
75
+ end
76
+
77
+ def extract_grantee(grant)
78
+ grants = {}
79
+ grant.each_element_with_attribute("xsi:type", "Group") do |grantee|
80
+ group_name = convert_uri_to_group_name(grantee.get_text("URI").value)
81
+ grants[group_name] = grant.get_text("Permission").value
82
+ end
83
+ grant.each_element_with_attribute("xsi:type", "CanonicalUser") do |grantee|
84
+ user_name = grantee.get_text("DisplayName").value
85
+ grants[user_name] = grant.get_text("Permission").value
86
+ end
87
+ grants
88
+ end
51
89
  end
52
90
  end
data/lib/s3/signature.rb CHANGED
@@ -25,7 +25,9 @@ module S3
25
25
  request = options[:request]
26
26
  access_key_id = options[:access_key_id]
27
27
 
28
- options.merge!(:headers => request, :method => request.method, :resource => request.path)
28
+ options.merge!(:headers => request,
29
+ :method => request.method,
30
+ :resource => request.path)
29
31
 
30
32
  signature = canonicalized_signature(options)
31
33
 
@@ -45,6 +47,8 @@ module S3
45
47
  # the resource, defaults to GET
46
48
  # * <tt>:headers</tt> - Any additional HTTP headers you intend
47
49
  # to use when requesting the resource
50
+ # * <tt>:add_bucket_to_host</tt> - Use in case of virtual-host style,
51
+ # defaults to false
48
52
  def self.generate_temporary_url_signature(options)
49
53
  bucket = options[:bucket]
50
54
  resource = options[:resource]
@@ -54,7 +58,10 @@ module S3
54
58
  headers = options[:headers] || {}
55
59
  headers.merge!("date" => expires.to_i.to_s)
56
60
 
57
- options.merge!(:resource => "/#{bucket}/#{URI.escape(resource)}",
61
+ resource = "/#{URI.escape(resource, /[^#{URI::REGEXP::PATTERN::UNRESERVED}\/]/)}"
62
+ resource = "/#{bucket}" + resource unless options[:add_bucket_to_host]
63
+
64
+ options.merge!(:resource => resource,
58
65
  :method => options[:method] || :get,
59
66
  :headers => headers)
60
67
  signature = canonicalized_signature(options)
@@ -76,14 +83,25 @@ module S3
76
83
  # the resource, defaults to GET
77
84
  # * <tt>:headers</tt> - Any additional HTTP headers you intend
78
85
  # to use when requesting the resource
86
+ # * <tt>:add_bucket_to_host</tt> - Use in case of virtual-host style,
87
+ # defaults to false
79
88
  def self.generate_temporary_url(options)
80
89
  bucket = options[:bucket]
81
90
  resource = options[:resource]
82
91
  access_key = options[:access_key]
83
92
  expires = options[:expires_at].to_i
93
+ host = S3::HOST
94
+
95
+ if options[:add_bucket_to_host]
96
+ host = bucket + '.' + host
97
+ url = "http://#{host}/#{resource}"
98
+ else
99
+ url = "http://#{host}/#{bucket}/#{resource}"
100
+ end
101
+
102
+ options[:host] = host
84
103
  signature = generate_temporary_url_signature(options)
85
104
 
86
- url = "http://#{S3::HOST}/#{bucket}/#{resource}"
87
105
  url << "?AWSAccessKeyId=#{access_key}"
88
106
  url << "&Expires=#{expires}"
89
107
  url << "&Signature=#{signature}"
@@ -209,7 +227,7 @@ module S3
209
227
  # requests that don't address a bucket, do nothing. For more
210
228
  # information on virtual hosted-style requests, see Virtual
211
229
  # Hosting of Buckets.
212
- bucket_name = host.sub(/\.?s3\.amazonaws\.com\Z/, "")
230
+ bucket_name = host.sub(/\.?#{S3::HOST}\Z/, "")
213
231
  string << "/#{bucket_name}" unless bucket_name.empty?
214
232
 
215
233
  # 3. Append the path part of the un-decoded HTTP Request-URI,
data/lib/s3/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module S3
2
- VERSION = "0.3.11"
2
+ VERSION = "0.3.12"
3
3
  end
data/s3.gemspec CHANGED
@@ -9,9 +9,9 @@ Gem::Specification.new do |s|
9
9
  s.name = "s3"
10
10
  s.version = S3::VERSION
11
11
  s.platform = Gem::Platform::RUBY
12
- s.authors = ["Jakub Kuźma"]
13
- s.email = ["qoobaa@gmail.com"]
14
- s.homepage = "http://jah.pl/projects/s3.html"
12
+ s.authors = ["Kuba Kuźma"]
13
+ s.email = ["kuba@jah.pl"]
14
+ s.homepage = "http://github.com/qoobaa/s3"
15
15
  s.summary = "Library for accessing S3 objects and buckets"
16
16
  s.description = "S3 library provides access to Amazon's Simple Storage Service. It supports both: European and US buckets through REST API."
17
17
 
data/test/object_test.rb CHANGED
@@ -68,6 +68,11 @@ class ObjectTest < Test::Unit::TestCase
68
68
  expected = "http://images.s3.amazonaws.com/Lena%20S%C3%B6derberg.png"
69
69
  actual = object12.url
70
70
  assert_equal expected, actual
71
+
72
+ object13 = S3::Object.send(:new, bucket1, :key => "Lena Söderberg [1].png")
73
+ expected = "http://images.s3.amazonaws.com/Lena%20S%C3%B6derberg%20%5B1%5D.png"
74
+ actual = object13.url
75
+ assert_equal expected, actual
71
76
 
72
77
  bucket2 = S3::Bucket.send(:new, @service, "images_new")
73
78
 
data/test/service_test.rb CHANGED
@@ -87,10 +87,8 @@ class ServiceTest < Test::Unit::TestCase
87
87
  end
88
88
  end
89
89
 
90
- test "buckets find first fail" do
91
- assert_raise S3::Error::NoSuchBucket do
92
- @service_bucket_not_exists.buckets.find_first("data2.example.com")
93
- end
90
+ test "buckets find first return nil" do
91
+ assert_equal nil, @service_bucket_not_exists.buckets.find_first("data2.example.com")
94
92
  end
95
93
 
96
94
  test "buckets find all on empty list" do
@@ -151,6 +151,17 @@ class SignatureTest < Test::Unit::TestCase
151
151
  expected = "gs6xNznrLJ4Bd%2B1y9pcy2HOSVeg%3D"
152
152
  assert_equal expected, actual
153
153
  end
154
+
155
+ test "temporary signature for object get with non-unreserved URI characters" do
156
+ actual = S3::Signature.generate_temporary_url_signature(
157
+ :bucket => "johnsmith",
158
+ :resource => "photos/puppy[1].jpg",
159
+ :secret_access_key => "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o",
160
+ :expires_at => 1175046589
161
+ )
162
+ expected = "gwCM0mVb9IrEPiUf8iaml6EISPc%3D"
163
+ assert_equal expected, actual
164
+ end
154
165
 
155
166
  test "temporary signature for object post" do
156
167
  actual = S3::Signature.generate_temporary_url_signature(
@@ -189,6 +200,18 @@ class SignatureTest < Test::Unit::TestCase
189
200
  assert_equal expected, actual
190
201
  end
191
202
 
203
+ test "temporary url for object get with bucket in the hostname" do
204
+ actual = S3::Signature.generate_temporary_url(
205
+ :bucket => "johnsmith",
206
+ :resource => "photos/puppy.jpg",
207
+ :secret_access_key => "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o",
208
+ :expires_at => 1175046589,
209
+ :add_bucket_to_host => true
210
+ )
211
+ expected = "http://johnsmith.s3.amazonaws.com/photos/puppy.jpg?AWSAccessKeyId=&Expires=1175046589&Signature=gs6xNznrLJ4Bd%2B1y9pcy2HOSVeg%3D"
212
+ assert_equal expected, actual
213
+ end
214
+
192
215
  test "temporary url for object put with headers" do
193
216
  actual = S3::Signature.generate_temporary_url(
194
217
  :bucket => "johnsmith",
metadata CHANGED
@@ -1,64 +1,75 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: s3
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.11
5
- prerelease:
4
+ version: 0.3.12
6
5
  platform: ruby
7
6
  authors:
8
- - Jakub Kuźma
7
+ - Kuba Kuźma
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2011-12-24 00:00:00.000000000 Z
11
+ date: 2013-06-05 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: proxies
16
- requirement: &6899940 !ruby/object:Gem::Requirement
17
- none: false
15
+ requirement: !ruby/object:Gem::Requirement
18
16
  requirements:
19
17
  - - ~>
20
18
  - !ruby/object:Gem::Version
21
19
  version: 0.2.0
22
20
  type: :runtime
23
21
  prerelease: false
24
- version_requirements: *6899940
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 0.2.0
25
27
  - !ruby/object:Gem::Dependency
26
28
  name: test-unit
27
- requirement: &6952760 !ruby/object:Gem::Requirement
28
- none: false
29
+ requirement: !ruby/object:Gem::Requirement
29
30
  requirements:
30
- - - ! '>='
31
+ - - '>='
31
32
  - !ruby/object:Gem::Version
32
33
  version: '2.0'
33
34
  type: :development
34
35
  prerelease: false
35
- version_requirements: *6952760
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
36
41
  - !ruby/object:Gem::Dependency
37
42
  name: mocha
38
- requirement: &6952100 !ruby/object:Gem::Requirement
39
- none: false
43
+ requirement: !ruby/object:Gem::Requirement
40
44
  requirements:
41
- - - ! '>='
45
+ - - '>='
42
46
  - !ruby/object:Gem::Version
43
47
  version: '0'
44
48
  type: :development
45
49
  prerelease: false
46
- version_requirements: *6952100
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
47
55
  - !ruby/object:Gem::Dependency
48
56
  name: bundler
49
- requirement: &6950720 !ruby/object:Gem::Requirement
50
- none: false
57
+ requirement: !ruby/object:Gem::Requirement
51
58
  requirements:
52
- - - ! '>='
59
+ - - '>='
53
60
  - !ruby/object:Gem::Version
54
61
  version: 1.0.0
55
62
  type: :development
56
63
  prerelease: false
57
- version_requirements: *6950720
58
- description: ! 'S3 library provides access to Amazon''s Simple Storage Service. It
59
- supports both: European and US buckets through REST API.'
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: 1.0.0
69
+ description: 'S3 library provides access to Amazon''s Simple Storage Service. It supports
70
+ both: European and US buckets through REST API.'
60
71
  email:
61
- - qoobaa@gmail.com
72
+ - kuba@jah.pl
62
73
  executables: []
63
74
  extensions: []
64
75
  extra_rdoc_files: []
@@ -88,31 +99,27 @@ files:
88
99
  - test/service_test.rb
89
100
  - test/signature_test.rb
90
101
  - test/test_helper.rb
91
- homepage: http://jah.pl/projects/s3.html
102
+ homepage: http://github.com/qoobaa/s3
92
103
  licenses: []
104
+ metadata: {}
93
105
  post_install_message:
94
106
  rdoc_options: []
95
107
  require_paths:
96
108
  - lib
97
109
  required_ruby_version: !ruby/object:Gem::Requirement
98
- none: false
99
110
  requirements:
100
- - - ! '>='
111
+ - - '>='
101
112
  - !ruby/object:Gem::Version
102
113
  version: '0'
103
- segments:
104
- - 0
105
- hash: -3173124590106976282
106
114
  required_rubygems_version: !ruby/object:Gem::Requirement
107
- none: false
108
115
  requirements:
109
- - - ! '>='
116
+ - - '>='
110
117
  - !ruby/object:Gem::Version
111
118
  version: 1.3.6
112
119
  requirements: []
113
120
  rubyforge_project: s3
114
- rubygems_version: 1.8.10
121
+ rubygems_version: 2.0.2
115
122
  signing_key:
116
- specification_version: 3
123
+ specification_version: 4
117
124
  summary: Library for accessing S3 objects and buckets
118
125
  test_files: []