aws-s3 0.3.0 → 0.4.0

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/README CHANGED
@@ -329,7 +329,7 @@ and specifying the object's key without a special authenticated url:
329
329
 
330
330
  http://s3.amazonaws.com/marcel/kiss.jpg
331
331
 
332
- ==== Building costum access policies
332
+ ==== Building custum access policies
333
333
 
334
334
  For both buckets and objects, you can use the <tt>acl</tt> method to see its access control policy:
335
335
 
@@ -444,6 +444,15 @@ If you have an object handy, you can use its <tt>url</tt> method with the same o
444
444
 
445
445
  song.url(:expires_in => 30)
446
446
 
447
+ To get an unauthenticated url for the object, such as in the case
448
+ when the object is publicly readable, pass the
449
+ <tt>:authenticated</tt> option with a value of <tt>false</tt>.
450
+
451
+ S3Object.url_for('beluga_baby.jpg',
452
+ 'marcel',
453
+ :authenticated => false)
454
+ # => http://s3.amazonaws.com/marcel/beluga_baby.jpg
455
+
447
456
 
448
457
  == Logging
449
458
  ==== Tracking requests made on a bucket
data/Rakefile CHANGED
@@ -140,7 +140,7 @@ namespace :dist do
140
140
  rubyforge.login
141
141
 
142
142
  version_already_released = lambda do
143
- releases = rubyforge.config['rubyforge']['release_ids']
143
+ releases = rubyforge.userconfig['rubyforge']['release_ids']
144
144
  releases.has_key?(spec.name) && releases[spec.name][spec.version]
145
145
  end
146
146
 
data/lib/aws/s3/acl.rb CHANGED
@@ -24,7 +24,7 @@ module AWS
24
24
  #
25
25
  # http://s3.amazonaws.com/marcel/kiss.jpg
26
26
  #
27
- # ==== Building costum access policies
27
+ # ==== Building custum access policies
28
28
  #
29
29
  # For both buckets and objects, you can use the <tt>acl</tt> method to see its access control policy:
30
30
  #
@@ -445,7 +445,7 @@ module AWS
445
445
  end
446
446
 
447
447
  def inspect #:nodoc:
448
- "#<%s:0x%s %s>" [self.class, object_id, type_representation || '(type not set yet)']
448
+ "#<%s:0x%s %s>" % [self.class, object_id, type_representation || '(type not set yet)']
449
449
  end
450
450
 
451
451
  private
@@ -633,4 +633,4 @@ module AWS
633
633
  end
634
634
  end
635
635
  end
636
- end
636
+ end
data/lib/aws/s3/base.rb CHANGED
@@ -66,7 +66,7 @@ module AWS #:nodoc:
66
66
  def request(verb, path, options = {}, body = nil, attempts = 0, &block)
67
67
  Service.response = nil
68
68
  process_options!(options, verb)
69
- response = response_class.new(connection.request(verb, path, options, body, &block))
69
+ response = response_class.new(connection.request(verb, path, options, body, attempts, &block))
70
70
  Service.response = response
71
71
 
72
72
  Error::Response.new(response.response).error.raise if response.error?
@@ -75,7 +75,6 @@ module AWS #:nodoc:
75
75
  # errors are few and far between the request method will rescue InternalErrors the first three times they encouter them
76
76
  # and will retry the request again. Most of the time the second attempt will work.
77
77
  rescue *retry_exceptions
78
- body.rewind if body.respond_to?(:rewind)
79
78
  attempts == 3 ? raise : (attempts += 1; retry)
80
79
  end
81
80
 
@@ -176,7 +175,7 @@ module AWS #:nodoc:
176
175
  end
177
176
 
178
177
  def retry_exceptions
179
- [InternalError, Timeout::Error, Errno::EPIPE, Errno::EINVAL, RequestTimeout]
178
+ [InternalError, RequestTimeout]
180
179
  end
181
180
 
182
181
  class RequestOptions < Hash #:nodoc:
@@ -23,7 +23,9 @@ module AWS
23
23
  connect
24
24
  end
25
25
 
26
- def request(verb, path, headers = {}, body = nil, &block)
26
+ def request(verb, path, headers = {}, body = nil, attempts = 0, &block)
27
+ body.rewind if body.respond_to?(:rewind) unless attempts.zero?
28
+
27
29
  requester = Proc.new do
28
30
  path = self.class.prepare_path(path)
29
31
  request = request_method(verb).new(path, headers)
@@ -47,13 +49,21 @@ module AWS
47
49
  else
48
50
  http.start(&requester)
49
51
  end
52
+ rescue Errno::EPIPE, Timeout::Error, Errno::EPIPE, Errno::EINVAL
53
+ @http = create_connection
54
+ attempts == 3 ? raise : (attempts += 1; retry)
50
55
  end
51
56
 
52
57
  def url_for(path, options = {})
58
+ authenticate = options.delete(:authenticated)
59
+ # Default to true unless explicitly false
60
+ authenticate = true if authenticate.nil?
53
61
  path = self.class.prepare_path(path)
54
62
  request = request_method(:get).new(path, {})
55
63
  query_string = query_string_authentication(request, options)
56
- "#{protocol(options)}#{http.address}#{path}?#{query_string}"
64
+ returning "#{protocol(options)}#{http.address}#{port_string}#{path}" do |url|
65
+ url << "?#{query_string}" if authenticate
66
+ end
57
67
  end
58
68
 
59
69
  def subdomain
@@ -77,14 +87,31 @@ module AWS
77
87
  raise MissingAccessKey.new(missing_keys) unless missing_keys.empty?
78
88
  end
79
89
 
90
+ def create_connection
91
+ http = http_class.new(options[:server], options[:port])
92
+ http.use_ssl = !options[:use_ssl].nil? || options[:port] == 443
93
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
94
+ http
95
+ end
96
+
97
+ def http_class
98
+ if options.connecting_through_proxy?
99
+ Net::HTTP::Proxy(*options.proxy_settings)
100
+ else
101
+ Net::HTTP
102
+ end
103
+ end
104
+
80
105
  def connect
81
106
  extract_keys!
82
- @http = Net::HTTP.new(options[:server], options[:port])
83
- @http.use_ssl = !options[:use_ssl].nil? || options[:port] == 443
84
- @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
85
- @http
107
+ @http = create_connection
86
108
  end
87
-
109
+
110
+ def port_string
111
+ default_port = options[:use_ssl] ? 443 : 80
112
+ http.port == default_port ? '' : ":#{http.port}"
113
+ end
114
+
88
115
  def ensure_content_type!(request)
89
116
  request['Content-Type'] ||= 'binary/octet-stream'
90
117
  end
@@ -152,6 +179,13 @@ module AWS
152
179
  # * <tt>:persistent</tt> - Whether to use a persistent connection to the server. Having this on provides around a two fold
153
180
  # performance increase but for long running processes some firewalls may find the long lived connection suspicious and close the connection.
154
181
  # If you run into connection errors, try setting <tt>:persistent</tt> to false. Defaults to true.
182
+ # * <tt>:proxy</tt> - If you need to connect through a proxy, you can specify your proxy settings by specifying a <tt>:host</tt>, <tt>:port</tt>, <tt>:user</tt>, and <tt>:password</tt>
183
+ # with the <tt>:proxy</tt> option.
184
+ # The <tt>:host</tt> setting is required if specifying a <tt>:proxy</tt>.
185
+ #
186
+ # AWS::S3::Bucket.established_connection!(:proxy => {
187
+ # :host => '...', :port => 8080, :user => 'marcel', :password => 'secret'
188
+ # })
155
189
  def establish_connection!(options = {})
156
190
  # After you've already established the default connection, just specify
157
191
  # the difference for subsequent connections
@@ -208,7 +242,7 @@ module AWS
208
242
  class Options < Hash #:nodoc:
209
243
  class << self
210
244
  def valid_options
211
- [:access_key_id, :secret_access_key, :server, :port, :use_ssl, :persistent]
245
+ [:access_key_id, :secret_access_key, :server, :port, :use_ssl, :persistent, :proxy]
212
246
  end
213
247
  end
214
248
 
@@ -217,17 +251,41 @@ module AWS
217
251
  super()
218
252
  @options = options
219
253
  validate!
254
+ extract_proxy_settings!
220
255
  extract_persistent!
221
256
  extract_server!
222
257
  extract_port!
223
258
  extract_remainder!
224
259
  end
225
260
 
261
+ def connecting_through_proxy?
262
+ !self[:proxy].nil?
263
+ end
264
+
265
+ def proxy_settings
266
+ proxy_setting_keys.map do |proxy_key|
267
+ self[:proxy][proxy_key]
268
+ end
269
+ end
270
+
226
271
  private
272
+ def proxy_setting_keys
273
+ [:host, :port, :user, :password]
274
+ end
275
+
276
+ def missing_proxy_settings?
277
+ !self[:proxy].keys.include?(:host)
278
+ end
279
+
227
280
  def extract_persistent!
228
281
  self[:persistent] = options.has_key?(:persitent) ? options[:persitent] : true
229
282
  end
230
283
 
284
+ def extract_proxy_settings!
285
+ self[:proxy] = options.delete(:proxy) if options.include?(:proxy)
286
+ validate_proxy_settings!
287
+ end
288
+
231
289
  def extract_server!
232
290
  self[:server] = options.delete(:server) || DEFAULT_HOST
233
291
  end
@@ -244,7 +302,13 @@ module AWS
244
302
  invalid_options = options.keys.select {|key| !self.class.valid_options.include?(key)}
245
303
  raise InvalidConnectionOption.new(invalid_options) unless invalid_options.empty?
246
304
  end
305
+
306
+ def validate_proxy_settings!
307
+ if connecting_through_proxy? && missing_proxy_settings?
308
+ raise ArgumentError, "Missing proxy settings. Must specify at least :host."
309
+ end
310
+ end
247
311
  end
248
312
  end
249
313
  end
250
- end
314
+ end
@@ -76,7 +76,8 @@ class CoercibleString < String
76
76
  case self
77
77
  when 'true': true
78
78
  when 'false': false
79
- when /^\d+$/: Integer(self)
79
+ # Don't coerce numbers that start with zero
80
+ when /^[1-9]+\d*$/: Integer(self)
80
81
  when datetime_format: Time.parse(self)
81
82
  else
82
83
  self
@@ -127,6 +128,13 @@ module Kernel
127
128
  end
128
129
  end
129
130
 
131
+ class Object
132
+ def returning(value)
133
+ yield(value)
134
+ value
135
+ end
136
+ end
137
+
130
138
  class Module
131
139
  def memoized(method_name)
132
140
  original_method = "unmemoized_#{method_name}_#{Time.now.to_i}"
@@ -47,7 +47,7 @@ module AWS
47
47
  #
48
48
  # See the documentation for Logging::Management::ClassMethods for more information on how to get the logging status of a bucket.
49
49
  class Status
50
- include SelectiveAttributeProxy #:nodoc:
50
+ include SelectiveAttributeProxy
51
51
  attr_reader :enabled
52
52
  alias_method :logging_enabled?, :enabled
53
53
 
data/lib/aws/s3/object.rb CHANGED
@@ -276,6 +276,15 @@ module AWS
276
276
  # If you have an object handy, you can use its <tt>url</tt> method with the same objects:
277
277
  #
278
278
  # song.url(:expires_in => 30)
279
+ #
280
+ # To get an unauthenticated url for the object, such as in the case
281
+ # when the object is publicly readable, pass the
282
+ # <tt>:authenticated</tt> option with a value of <tt>false</tt>.
283
+ #
284
+ # S3Object.url_for('beluga_baby.jpg',
285
+ # 'marcel',
286
+ # :authenticated => false)
287
+ # # => http://s3.amazonaws.com/marcel/beluga_baby.jpg
279
288
  def url_for(name, bucket = nil, options = {})
280
289
  connection.url_for(path!(bucket, name, options), options) # Do not normalize options
281
290
  end
@@ -2,11 +2,11 @@ module AWS
2
2
  module S3
3
3
  module VERSION #:nodoc:
4
4
  MAJOR = '0'
5
- MINOR = '3'
5
+ MINOR = '4'
6
6
  TINY = '0'
7
7
  BETA = nil # Time.now.to_i.to_s
8
8
  end
9
9
 
10
10
  Version = [VERSION::MAJOR, VERSION::MINOR, VERSION::TINY, VERSION::BETA].compact * '.'
11
11
  end
12
- end
12
+ end
@@ -22,34 +22,61 @@ require 'rubygems'
22
22
  require 'xml/libxml'
23
23
 
24
24
  class FasterXmlSimple
25
+ Version = '0.5.0'
25
26
  class << self
27
+ # Take an string containing XML, and returns a hash representing that
28
+ # XML document. For example:
29
+ #
30
+ # FasterXmlSimple.xml_in("<root><something>1</something></root>")
31
+ # {"root"=>{"something"=>{"__content__"=>"1"}}}
32
+ #
33
+ # Faster XML Simple is designed to be a drop in replacement for the xml_in
34
+ # functionality of http://xml-simple.rubyforge.org
35
+ #
36
+ # The following options are supported:
37
+ #
38
+ # * <tt>contentkey</tt>: The key to use for the content of text elements,
39
+ # defaults to '\_\_content__'
40
+ # * <tt>forcearray</tt>: The list of elements which should always be returned
41
+ # as arrays. Under normal circumstances single element arrays are inlined.
42
+ # * <tt>suppressempty</tt>: The value to return for empty elements, pass +true+
43
+ # to remove empty elements entirely.
44
+ # * <tt>keeproot</tt>: By default the hash returned has a single key with the
45
+ # name of the root element. If the name of the root element isn't
46
+ # interesting to you, pass +false+.
47
+ # * <tt>forcecontent</tt>: By default a text element with no attributes, will
48
+ # be collapsed to just a string instead of a hash with a single key.
49
+ # Pass +true+ to prevent this.
50
+ #
51
+ #
26
52
  def xml_in(string, options={})
27
53
  new(string, options).out
28
54
  end
29
55
  end
30
56
 
31
- attr_reader :doc, :options, :current_element
32
-
33
- def initialize(string, options)
34
- @doc = parse(string)
35
- @options = default_options.merge options
36
- @current_element = nil
57
+ def initialize(string, options) #:nodoc:
58
+ @doc = parse(string)
59
+ @options = default_options.merge options
37
60
  end
38
61
 
39
- def out
40
- {doc.root.name => collapse(doc.root)}
62
+ def out #:nodoc:
63
+ if @options['keeproot']
64
+ {@doc.root.name => collapse(@doc.root)}
65
+ else
66
+ collapse(@doc.root)
67
+ end
41
68
  end
42
69
 
43
70
  private
44
71
  def default_options
45
- {'contentkey' => '__content__', 'forcearray' => []}
72
+ {'contentkey' => '__content__', 'forcearray' => [], 'keeproot'=>true}
46
73
  end
47
74
 
48
75
  def collapse(element)
49
76
  result = hash_of_attributes(element)
50
77
  if text_node? element
51
78
  text = collapse_text(element)
52
- result.merge!(content_key => text) if text =~ /\S/
79
+ result[content_key] = text if text =~ /\S/
53
80
  elsif element.children?
54
81
  element.inject(result) do |hash, child|
55
82
  unless child.text?
@@ -64,26 +91,24 @@ class FasterXmlSimple
64
91
  end
65
92
  # Compact them to ensure it complies with the user's requests
66
93
  inline_single_element_arrays(result)
67
- suppress_empty_content(result) unless force_content?
68
94
  remove_empty_elements(result) if suppress_empty?
69
-
70
- result
71
- end
72
-
73
- def empty_element
74
- if !options.has_key? 'suppressempty'
75
- {}
95
+ if content_only?(result) && !force_content?
96
+ result[content_key]
76
97
  else
77
- options['suppressempty']
98
+ result
78
99
  end
79
100
  end
101
+
102
+ def content_only?(result)
103
+ result.keys == [content_key]
104
+ end
80
105
 
81
106
  def content_key
82
- options['contentkey']
107
+ @options['contentkey']
83
108
  end
84
109
 
85
110
  def force_array?(key_name)
86
- Array(options['forcearray']).include?(key_name)
111
+ Array(@options['forcearray']).include?(key_name)
87
112
  end
88
113
 
89
114
  def inline_single_element_arrays(result)
@@ -103,21 +128,31 @@ class FasterXmlSimple
103
128
  end
104
129
 
105
130
  def suppress_empty?
106
- options['suppressempty'] == true
131
+ @options['suppressempty'] == true
132
+ end
133
+
134
+ def empty_element
135
+ if !@options.has_key? 'suppressempty'
136
+ {}
137
+ else
138
+ @options['suppressempty']
139
+ end
107
140
  end
108
141
 
109
-
110
-
142
+ # removes the content if it's nothing but blanks, prevents
143
+ # the hash being polluted with lots of content like "\n\t\t\t"
111
144
  def suppress_empty_content(result)
112
145
  result.delete content_key if result[content_key] !~ /\S/
113
146
  end
114
147
 
115
148
  def force_content?
116
- options['forcecontent']
149
+ @options['forcecontent']
117
150
  end
118
151
 
119
152
  # a text node is one with 1 or more child nodes which are
120
- # text nodes, and no non-text children
153
+ # text nodes, and no non-text children, there's no sensible
154
+ # way to support nodes which are text and markup like:
155
+ # <p>Something <b>Bold</b> </p>
121
156
  def text_node?(element)
122
157
  !element.text? && element.all? {|c| c.text?}
123
158
  end
@@ -145,7 +180,7 @@ class FasterXmlSimple
145
180
  end
146
181
  end
147
182
 
148
- class XmlSimple
183
+ class XmlSimple # :nodoc:
149
184
  def self.xml_in(*args)
150
185
  FasterXmlSimple.xml_in *args
151
186
  end
@@ -33,4 +33,15 @@ class RegressionTest < FasterXSTest
33
33
  end
34
34
  end
35
35
 
36
+ def test_keeproot_false
37
+ str = "<asdf><fdsa>1</fdsa></asdf>"
38
+ expected = {"fdsa"=>"1"}
39
+ assert_equal expected, FasterXmlSimple.xml_in(str, 'keeproot'=>false)
40
+ end
41
+
42
+ def test_keeproot_false_with_force_content
43
+ str = "<asdf><fdsa>1</fdsa></asdf>"
44
+ expected = {"fdsa"=>{"__content__"=>"1"}}
45
+ assert_equal expected, FasterXmlSimple.xml_in(str, 'keeproot'=>false, 'forcecontent'=>true)
46
+ end
36
47
  end
@@ -74,6 +74,35 @@ class ConnectionTest < Test::Unit::TestCase
74
74
  assert_equal klass, c.send(:request_method, verb)
75
75
  end
76
76
  end
77
+
78
+ def test_url_for_uses_default_protocol_server_and_port
79
+ connection = Connection.new(:access_key_id => '123', :secret_access_key => 'abc', :port => 80)
80
+ assert_match %r(^http://s3\.amazonaws\.com/foo\?), connection.url_for('/foo')
81
+
82
+ connection = Connection.new(:access_key_id => '123', :secret_access_key => 'abc', :use_ssl => true, :port => 443)
83
+ assert_match %r(^https://s3\.amazonaws\.com/foo\?), connection.url_for('/foo')
84
+ end
85
+
86
+ def test_url_for_remembers_custom_protocol_server_and_port
87
+ connection = Connection.new(:access_key_id => '123', :secret_access_key => 'abc', :server => 'example.org', :port => 555, :use_ssl => true)
88
+ assert_match %r(^https://example\.org:555/foo\?), connection.url_for('/foo')
89
+ end
90
+
91
+ def test_url_for_with_and_without_authenticated_urls
92
+ connection = Connection.new(:access_key_id => '123', :secret_access_key => 'abc', :server => 'example.org')
93
+ authenticated = lambda {|url| url['?AWSAccessKeyId']}
94
+ assert authenticated[connection.url_for('/foo')]
95
+ assert authenticated[connection.url_for('/foo', :authenticated => true)]
96
+ assert !authenticated[connection.url_for('/foo', :authenticated => false)]
97
+ end
98
+
99
+ def test_connecting_through_a_proxy
100
+ connection = nil
101
+ assert_nothing_raised do
102
+ connection = Connection.new(@keys.merge(:proxy => sample_proxy_settings))
103
+ end
104
+ assert connection.http.proxy?
105
+ end
77
106
  end
78
107
 
79
108
  class ConnectionOptionsTest < Test::Unit::TestCase
@@ -115,8 +144,41 @@ class ConnectionOptionsTest < Test::Unit::TestCase
115
144
  end
116
145
  end
117
146
 
118
- private
147
+ def test_not_specifying_all_required_proxy_settings_raises
148
+ assert_raises(ArgumentError) do
149
+ generate_options(:proxy => {})
150
+ end
151
+ end
152
+
153
+ def test_not_specifying_proxy_option_at_all_does_not_raise
154
+ assert_nothing_raised do
155
+ generate_options
156
+ end
157
+ end
158
+
159
+ def test_specifying_all_required_proxy_settings
160
+ assert_nothing_raised do
161
+ generate_options(:proxy => sample_proxy_settings)
162
+ end
163
+ end
164
+
165
+ def test_only_host_setting_is_required
166
+ assert_nothing_raised do
167
+ generate_options(:proxy => {:host => 'http://google.com'})
168
+ end
169
+ end
170
+
171
+ def test_proxy_settings_are_extracted
172
+ options = generate_options(:proxy => sample_proxy_settings)
173
+ assert_equal sample_proxy_settings.values.map {|value| value.to_s}.sort, options.proxy_settings.map {|value| value.to_s}.sort
174
+ end
119
175
 
176
+ def test_recognizing_that_the_settings_want_to_connect_through_a_proxy
177
+ options = generate_options(:proxy => sample_proxy_settings)
178
+ assert options.connecting_through_proxy?
179
+ end
180
+
181
+ private
120
182
  def assert_key_transfered(key, value, options)
121
183
  assert_equal value, options[key]
122
184
  assert !options.instance_variable_get('@options').has_key?(key)
@@ -125,4 +187,4 @@ class ConnectionOptionsTest < Test::Unit::TestCase
125
187
  def generate_options(options = {})
126
188
  Connection::Options.new(options)
127
189
  end
128
- end
190
+ end
@@ -86,7 +86,8 @@ class CoercibleStringTest < Test::Unit::TestCase
86
86
  ['2006-10-29T23:14:47.000Z', Time.parse('2006-10-29T23:14:47.000Z')],
87
87
  ['Hello!', 'Hello!'],
88
88
  ['false23', 'false23'],
89
- ['03 1-2-3-Apple-Tree.mp3', '03 1-2-3-Apple-Tree.mp3']
89
+ ['03 1-2-3-Apple-Tree.mp3', '03 1-2-3-Apple-Tree.mp3'],
90
+ ['0815', '0815'] # This number isn't coerced because the leading zero would be lost
90
91
  ]
91
92
 
92
93
  coercions.each do |before, after|
data/test/test_helper.rb CHANGED
@@ -79,4 +79,8 @@ end
79
79
 
80
80
  class Test::Unit::TestCase
81
81
  include AWS::S3
82
+
83
+ def sample_proxy_settings
84
+ {:host => 'http://google.com', :port => 8080, :user => 'marcel', :password => 'secret'}
85
+ end
82
86
  end
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
- rubygems_version: 0.9.0
2
+ rubygems_version: 0.9.4
3
3
  specification_version: 1
4
4
  name: aws-s3
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.3.0
7
- date: 2006-12-21 00:00:00 -06:00
6
+ version: 0.4.0
7
+ date: 2007-07-08 00:00:00 -05:00
8
8
  summary: Client library for Amazon's Simple Storage Service's REST API
9
9
  require_paths:
10
10
  - lib