aws-s3 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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