s3-ruby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,8 @@
1
+ == 0.1.0 / 2006-11-23
2
+
3
+ * Enhancements over the Sample Code
4
+ * Stream Support
5
+ * Added convenience methods
6
+ * Packaged as a gem
7
+ * Several warnings removed
8
+ * Support for HEAD requests on objects
@@ -0,0 +1,5 @@
1
+ History.txt
2
+ Manifest.txt
3
+ README.txt
4
+ Rakefile
5
+ lib/s3-ruby.rb
@@ -0,0 +1,54 @@
1
+ s3-ruby
2
+ by Chris Carter <cdcarter@gmail.com>
3
+ http://metacampsite.com/s3-ruby
4
+ based on the Amazon sample code
5
+
6
+ == SYNOPSIS:
7
+
8
+ s3-ruby is a library to access the Amazon S3 (Simple Storage Service) REST API.
9
+
10
+ == FEATURES/PROBLEMS:
11
+
12
+ * Full REST API support
13
+ * Support for streams
14
+ * no tests at this point
15
+
16
+ == DESCRIPTION:
17
+
18
+ s3-ruby interfaces with the REST API of Amazon's S3 (Simple Storage Service)
19
+ to give your ruby applications full access to the service.
20
+
21
+ == REQUIREMENTS:
22
+
23
+ + ruby/hmac
24
+
25
+ == INSTALL:
26
+
27
+ + sudo gem install s3-ruby
28
+
29
+ == LICENSE:
30
+
31
+ (The MIT License)
32
+
33
+ Copyright (c) 2006 Concentration Studios
34
+
35
+ Base code (c) 2006 Amazon Digital Services, Inc. or its affiliates.
36
+
37
+ Permission is hereby granted, free of charge, to any person obtaining
38
+ a copy of this software and associated documentation files (the
39
+ 'Software'), to deal in the Software without restriction, including
40
+ without limitation the rights to use, copy, modify, merge, publish,
41
+ distribute, sublicense, and/or sell copies of the Software, and to
42
+ permit persons to whom the Software is furnished to do so, subject to
43
+ the following conditions:
44
+
45
+ The above copyright notice and this permission notice shall be
46
+ included in all copies or substantial portions of the Software.
47
+
48
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
49
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
50
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
51
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
52
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
53
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
54
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,15 @@
1
+ # -*- ruby -*-
2
+
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require './lib/s3-ruby.rb'
6
+
7
+ Hoe.new('s3-ruby', S3::RubyBindings::VERSION) do |p|
8
+ p.rubyforge_name = 's3-ruby'
9
+ p.summary = 'A library for the Amazon S3 (Simple Storage Service) web service REST API'
10
+ p.description = p.paragraphs_of('README.txt', 2..4 ).join("\n\n")
11
+ p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1]
12
+ p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
13
+ end
14
+
15
+ # vim: syntax=Ruby
@@ -0,0 +1,661 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Base Code is Copyright (c) 2006 Amazon Digital Services, Inc.
4
+ #
5
+ # Modifications made by Concentration Studios under the MIT license
6
+
7
+ require 'base64'
8
+ require 'cgi'
9
+ require 'openssl'
10
+ require 'digest/sha1'
11
+ require 'net/https'
12
+ require 'rexml/document'
13
+ require 'time'
14
+
15
+ # this wasn't added until v 1.8.3
16
+ if (RUBY_VERSION < '1.8.3')
17
+ class Net::HTTP::Delete < Net::HTTPRequest
18
+ METHOD = 'DELETE'
19
+ REQUEST_HAS_BODY = false
20
+ RESPONSE_HAS_BODY = true
21
+ end
22
+ end
23
+
24
+ # Fix Net::HTTP to allow streaming
25
+ module Net
26
+ class HTTP
27
+ alias __request__ request
28
+
29
+ def request(req, body = nil, &block)
30
+ if body != nil && body.respond_to?(:read)
31
+ req.body_stream = body
32
+ return __request__(req, nil, &block)
33
+ else
34
+ return __request__(req, body, &block)
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ # this module has two big classes: AWSAuthConnection and
41
+ # QueryStringAuthGenerator. both use identical apis, but the first actually
42
+ # performs the operation, while the second simply outputs urls with the
43
+ # appropriate authentication query string parameters, which could be used
44
+ # in another tool (such as your web browser for GETs).
45
+ module S3
46
+ DEFAULT_HOST = 's3.amazonaws.com'
47
+ PORTS_BY_SECURITY = { true => 443, false => 80 }
48
+ METADATA_PREFIX = 'x-amz-meta-'
49
+ AMAZON_HEADER_PREFIX = 'x-amz-'
50
+
51
+ class RubyBindings
52
+ VERSION = '0.1.0'
53
+ end
54
+
55
+ # builds the canonical string for signing.
56
+ def S3.canonical_string(method, path, headers={}, expires=nil)
57
+ interesting_headers = {}
58
+ headers.each do |key, value|
59
+ lk = key.downcase
60
+ if (lk == 'content-md5' or
61
+ lk == 'content-type' or
62
+ lk == 'date' or
63
+ lk =~ /^#{AMAZON_HEADER_PREFIX}/o)
64
+ interesting_headers[lk] = value.to_s.strip
65
+ end
66
+ end
67
+
68
+ # these fields get empty strings if they don't exist.
69
+ interesting_headers['content-type'] ||= ''
70
+ interesting_headers['content-md5'] ||= ''
71
+
72
+ # just in case someone used this. it's not necessary in this lib.
73
+ if interesting_headers.has_key? 'x-amz-date'
74
+ interesting_headers['date'] = ''
75
+ end
76
+
77
+ # if you're using expires for query string auth, then it trumps date
78
+ # (and x-amz-date)
79
+ if not expires.nil?
80
+ interesting_headers['date'] = expires
81
+ end
82
+
83
+ buf = "#{method}\n"
84
+ interesting_headers.sort { |a, b| a[0] <=> b[0] }.each do |key, value|
85
+ if key =~ /^#{AMAZON_HEADER_PREFIX}/o
86
+ buf << "#{key}:#{value}\n"
87
+ else
88
+ buf << "#{value}\n"
89
+ end
90
+ end
91
+
92
+ # ignore everything after the question mark...
93
+ buf << path.gsub(/\?.*$/, '')
94
+
95
+ # ...unless there is an acl or torrent parameter
96
+ if path =~ /[&?]acl($|&|=)/
97
+ buf << '?acl'
98
+ elsif path =~ /[&?]torrent($|&|=)/
99
+ buf << '?torrent'
100
+ elsif path =~ /[&?]logging($|&|=)/
101
+ buf << '?logging'
102
+ end
103
+
104
+ return buf
105
+ end
106
+
107
+ # encodes the given string with the aws_secret_access_key, by taking the
108
+ # hmac-sha1 sum, and then base64 encoding it. optionally, it will also
109
+ # url encode the result of that to protect the string if it's going to
110
+ # be used as a query string parameter.
111
+ def S3.encode(aws_secret_access_key, str, urlencode=false)
112
+ digest = OpenSSL::Digest::Digest.new('sha1')
113
+ b64_hmac =
114
+ Base64.encode64(
115
+ OpenSSL::HMAC.digest(digest, aws_secret_access_key, str)).strip
116
+
117
+ if urlencode
118
+ return CGI::escape(b64_hmac)
119
+ else
120
+ return b64_hmac
121
+ end
122
+ end
123
+
124
+
125
+ # uses Net::HTTP to interface with S3. note that this interface should only
126
+ # be used for smaller objects, as it does not stream the data. if you were
127
+ # to download a 1gb file, it would require 1gb of memory. also, this class
128
+ # creates a new http connection each time. it would be greatly improved with
129
+ # some connection pooling.
130
+ class AWSAuthConnection
131
+ def initialize(aws_access_key_id, aws_secret_access_key, is_secure=true,
132
+ server=DEFAULT_HOST, port=PORTS_BY_SECURITY[is_secure])
133
+ @aws_access_key_id = aws_access_key_id
134
+ @aws_secret_access_key = aws_secret_access_key
135
+ @http = Net::HTTP.new(server, port)
136
+ @http.use_ssl = is_secure
137
+ end
138
+
139
+ def create_bucket(bucket, headers={})
140
+ return Response.new(make_request('PUT', bucket, headers))
141
+ end
142
+
143
+ # takes options :prefix, :marker, :max_keys, and :delimiter
144
+ def list_bucket(bucket, options={}, headers={})
145
+ path = bucket
146
+ if options.size > 0
147
+ path += '?' + options.map { |k, v| "#{k}=#{CGI::escape v.to_s}" }.join('&')
148
+ end
149
+
150
+ return ListBucketResponse.new(make_request('GET', path, headers))
151
+ end
152
+
153
+ def delete_bucket(bucket, headers={})
154
+ return Response.new(make_request('DELETE', bucket, headers))
155
+ end
156
+
157
+ def put(bucket, key, object, headers={})
158
+ object = S3Object.new(object) if not object.instance_of? S3Object
159
+
160
+ return Response.new(
161
+ make_request('PUT', "#{bucket}/#{CGI::escape key}", headers, object.data, object.metadata)
162
+ )
163
+ end
164
+
165
+ def get(bucket, key, headers={})
166
+ return GetResponse.new(make_request('GET', "#{bucket}/#{CGI::escape key}", headers))
167
+ end
168
+
169
+ def delete(bucket, key, headers={})
170
+ return Response.new(make_request('DELETE', "#{bucket}/#{CGI::escape key}", headers))
171
+ end
172
+
173
+ def head(bucket, key, headers={})
174
+ return GetResponse.new(make_request('HEAD',"#{bucket}/#{CGI::escape key}", headers))
175
+ end
176
+
177
+ # a convenience method to put an object to S3 publically readbale
178
+ def put_public(bucket,key,object,headers)
179
+ put(
180
+ bucket,
181
+ key,
182
+ object,
183
+ { 'x-amz-acl' => 'public-read'}.merge(headers)
184
+ )
185
+ end
186
+
187
+ # a convenience method that returns an array with the list of all your bucket names
188
+ def list_buckets
189
+ list_all_my_buckets.entries.map { |entry| entry.name }
190
+ end
191
+
192
+ # a convenience method that returns an array with the list of all the keys in a bucket
193
+ def list_bucket_contents(bucket)
194
+ list_bucket(bucket).entries.map { |entry| entry.key }
195
+ end
196
+
197
+ def make_public(bucket,object,headers={})
198
+ put_acl(bucket,object,'', { 'a-amz-acl' => 'public-read' }.merge(headers) )
199
+ end
200
+
201
+ def make_private(bucket,object,headers={})
202
+ put_acl(bucket,object,'', { 'a-amz-acl' => 'private' }.merge(headers) )
203
+ end
204
+
205
+ def get_bucket_logging(bucket, headers={})
206
+ return GetResponse.new(make_request('GET', "#{bucket}?logging", headers))
207
+ end
208
+
209
+ def put_bucket_logging(bucket, logging_xml_doc, headers={})
210
+ return Response.new(make_request('PUT', "#{bucket}?logging", headers, logging_xml_doc))
211
+ end
212
+
213
+ def get_bucket_acl(bucket, headers={})
214
+ return get_acl(bucket, '', headers)
215
+ end
216
+
217
+ # returns an xml document representing the access control list.
218
+ # this could be parsed into an object.
219
+ def get_acl(bucket, key, headers={})
220
+ return GetResponse.new(make_request('GET', "#{bucket}/#{CGI::escape key}?acl", headers))
221
+ end
222
+
223
+ def put_bucket_acl(bucket, acl_xml_doc, headers={})
224
+ return put_acl(bucket, '', acl_xml_doc, headers)
225
+ end
226
+
227
+ # sets the access control policy for the given resource. acl_xml_doc must
228
+ # be a string in the acl xml format.
229
+ def put_acl(bucket, key, acl_xml_doc, headers={})
230
+ return Response.new(
231
+ make_request('PUT', "#{bucket}/#{CGI::escape key}?acl", headers, acl_xml_doc, {})
232
+ )
233
+ end
234
+
235
+ def list_all_my_buckets(headers={})
236
+ return ListAllMyBucketsResponse.new(make_request('GET', '', headers))
237
+ end
238
+
239
+ private
240
+ def make_request(method, path, headers={}, data='', metadata={})
241
+ @http.start do
242
+ req = method_to_request_class(method).new("/#{path}")
243
+
244
+ set_headers(req, headers)
245
+ set_headers(req, metadata, METADATA_PREFIX)
246
+
247
+ set_aws_auth_header(req, @aws_access_key_id, @aws_secret_access_key)
248
+ if req.request_body_permitted?
249
+ return @http.request(req, data)
250
+ else
251
+ return @http.request(req)
252
+ end
253
+ end
254
+ end
255
+
256
+ def method_to_request_class(method)
257
+ case method
258
+ when 'GET'
259
+ return Net::HTTP::Get
260
+ when 'PUT'
261
+ return Net::HTTP::Put
262
+ when 'DELETE'
263
+ return Net::HTTP::Delete
264
+ when 'HEAD'
265
+ return Net::HTTP::Head
266
+ else
267
+ raise "Unsupported method #{method}"
268
+ end
269
+ end
270
+
271
+ # set the Authorization header using AWS signed header authentication
272
+ def set_aws_auth_header(request, aws_access_key_id, aws_secret_access_key)
273
+ # we want to fix the date here if it's not already been done.
274
+ request['Date'] ||= Time.now.httpdate
275
+
276
+ # ruby will automatically add a random content-type on some verbs, so
277
+ # here we add a dummy one to 'supress' it. change this logic if having
278
+ # an empty content-type header becomes semantically meaningful for any
279
+ # other verb.
280
+ request['Content-Type'] ||= ''
281
+
282
+ canonical_string =
283
+ S3.canonical_string(request.method, request.path, request.to_hash)
284
+ encoded_canonical = S3.encode(aws_secret_access_key, canonical_string)
285
+
286
+ request['Authorization'] = "AWS #{aws_access_key_id}:#{encoded_canonical}"
287
+ end
288
+
289
+ def set_headers(request, headers, prefix='')
290
+ headers.each do |key, value|
291
+ request[prefix + key] = value
292
+ end
293
+ end
294
+ end
295
+
296
+
297
+ # This interface mirrors the AWSAuthConnection class above, but instead
298
+ # of performing the operations, this class simply returns a url that can
299
+ # be used to perform the operation with the query string authentication
300
+ # parameters set.
301
+ class QueryStringAuthGenerator
302
+ attr_reader :server, :expires, :expires_in, :port
303
+
304
+ # by default, expire in 1 minute
305
+ DEFAULT_EXPIRES_IN = 60
306
+
307
+ def initialize(aws_access_key_id, aws_secret_access_key, is_secure=true, server=DEFAULT_HOST, port=PORTS_BY_SECURITY[is_secure])
308
+ @aws_access_key_id = aws_access_key_id
309
+ @aws_secret_access_key = aws_secret_access_key
310
+ @protocol = is_secure ? 'https' : 'http'
311
+ @server = server
312
+ @port = port
313
+ # by default expire
314
+ @expires_in = DEFAULT_EXPIRES_IN
315
+ end
316
+
317
+ # set the expires value to be a fixed time. the argument can
318
+ # be either a Time object or else seconds since epoch.
319
+ def expires=(value)
320
+ @expires = value
321
+ @expires_in = nil
322
+ end
323
+
324
+ # set the expires value to expire at some point in the future
325
+ # relative to when the url is generated. value is in seconds.
326
+ def expires_in=(value)
327
+ @expires_in = value
328
+ @expires = nil
329
+ end
330
+
331
+ def create_bucket(bucket, headers={})
332
+ return generate_url('PUT', bucket, headers)
333
+ end
334
+
335
+ # takes options :prefix, :marker, :max_keys, and :delimiter
336
+ def list_bucket(bucket, options={}, headers={})
337
+ path = bucket
338
+ if options.size > 0
339
+ path += '?' + options.map { |k, v| "#{k}=#{CGI::escape v}" }.join('&')
340
+ end
341
+
342
+ return generate_url('GET', path, headers)
343
+ end
344
+
345
+ def delete_bucket(bucket, headers={})
346
+ return generate_url('DELETE', bucket, headers)
347
+ end
348
+
349
+ # don't really care what object data is. it's just for conformance with the
350
+ # other interface. If this doesn't work, check tcpdump to see if the client is
351
+ # putting a Content-Type header on the wire.
352
+ def put(bucket, key, object=nil, headers={})
353
+ object = S3Object.new(object) if not object.instance_of? S3Object
354
+ return generate_url('PUT', "#{bucket}/#{CGI::escape key}", merge_meta(headers, object))
355
+ end
356
+
357
+ def get(bucket, key, headers={})
358
+ return generate_url('GET', "#{bucket}/#{CGI::escape key}", headers)
359
+ end
360
+
361
+ def delete(bucket, key, headers={})
362
+ return generate_url('DELETE', "#{bucket}/#{CGI::escape key}", headers)
363
+ end
364
+
365
+ def get_bucket_logging(bucket, headers={})
366
+ return generate_url('GET', "#{bucket}?logging", headers)
367
+ end
368
+
369
+ def put_bucket_logging(bucket, logging_xml_doc, headers={})
370
+ return generate_url('PUT', "#{bucket}?logging", headers)
371
+ end
372
+
373
+ def get_acl(bucket, key='', headers={})
374
+ return generate_url('GET', "#{bucket}/#{CGI::escape key}?acl", headers)
375
+ end
376
+
377
+ def get_bucket_acl(bucket, headers={})
378
+ return get_acl(bucket, '', headers)
379
+ end
380
+
381
+ # don't really care what acl_xml_doc is.
382
+ # again, check the wire for Content-Type if this fails.
383
+ def put_acl(bucket, key, acl_xml_doc, headers={})
384
+ return generate_url('PUT', "#{bucket}/#{CGI::escape key}?acl", headers)
385
+ end
386
+
387
+ def put_bucket_acl(bucket, acl_xml_doc, headers={})
388
+ return put_acl(bucket, '', acl_xml_doc, headers)
389
+ end
390
+
391
+ def list_all_my_buckets(headers={})
392
+ return generate_url('GET', '', headers)
393
+ end
394
+
395
+ def get_torrent(bucket, key, headers={})
396
+ return generate_url('GET', "#{bucket}/#{CGI::escape key}?torrent", headers)
397
+ end
398
+
399
+ private
400
+ # generate a url with the appropriate query string authentication
401
+ # parameters set.
402
+ def generate_url(method, path, headers)
403
+ expires = 0
404
+ if not @expires_in.nil?
405
+ expires = Time.now.to_i + @expires_in
406
+ elsif not @expires.nil?
407
+ expires = @expires
408
+ else
409
+ raise "invalid expires state"
410
+ end
411
+
412
+ canonical_string =
413
+ S3::canonical_string(method, "/" + path, headers, expires)
414
+ encoded_canonical =
415
+ S3::encode(@aws_secret_access_key, canonical_string, true)
416
+
417
+ arg_sep = path.index('?') ? '&' : '?'
418
+
419
+ return "#{@protocol}://#{@server}:#{@port}/#{path}#{arg_sep}Signature=#{encoded_canonical}&Expires=#{expires}&AWSAccessKeyId=#{@aws_access_key_id}"
420
+ end
421
+
422
+ def merge_meta(headers, object)
423
+ final_headers = headers.clone
424
+ if not object.nil? and not object.metadata.nil?
425
+ object.metadata.each do |k, v|
426
+ final_headers[METADATA_PREFIX + k] = v
427
+ end
428
+ end
429
+ return final_headers
430
+ end
431
+ end
432
+
433
+ class S3Object
434
+ attr_accessor :data
435
+ attr_accessor :metadata
436
+ def initialize(data, metadata={})
437
+ @data, @metadata = data, metadata
438
+ end
439
+ end
440
+
441
+ class Owner
442
+ attr_accessor :id
443
+ attr_accessor :display_name
444
+ end
445
+
446
+ class ListEntry
447
+ attr_accessor :key
448
+ attr_accessor :last_modified
449
+ attr_accessor :etag
450
+ attr_accessor :size
451
+ attr_accessor :storage_class
452
+ attr_accessor :owner
453
+ end
454
+
455
+ class ListProperties
456
+ attr_accessor :name
457
+ attr_accessor :prefix
458
+ attr_accessor :marker
459
+ attr_accessor :max_keys
460
+ attr_accessor :delimiter
461
+ attr_accessor :is_truncated
462
+ attr_accessor :next_marker
463
+ end
464
+
465
+ class CommonPrefixEntry
466
+ attr_accessor :prefix
467
+ end
468
+
469
+ # Parses the list bucket output into a list of ListEntry objects, and
470
+ # a list of CommonPrefixEntry objects if applicable.
471
+ class ListBucketParser
472
+ attr_reader :properties
473
+ attr_reader :entries
474
+ attr_reader :common_prefixes
475
+
476
+ def initialize
477
+ reset
478
+ end
479
+
480
+ def tag_start(name, attributes)
481
+ if name == 'ListBucketResult'
482
+ @properties = ListProperties.new
483
+ elsif name == 'Contents'
484
+ @curr_entry = ListEntry.new
485
+ elsif name == 'Owner'
486
+ @curr_entry.owner = Owner.new
487
+ elsif name == 'CommonPrefixes'
488
+ @common_prefix_entry = CommonPrefixEntry.new
489
+ end
490
+ end
491
+
492
+ # we have one, add him to the entries list
493
+ def tag_end(name)
494
+ # this prefix is the one we echo back from the request
495
+ if name == 'Name'
496
+ @properties.name = @curr_text
497
+ elsif name == 'Prefix' && @is_echoed_prefix
498
+ @properties.prefix = @curr_text
499
+ @is_echoed_prefix = nil
500
+ elsif name == 'Marker'
501
+ @properties.marker = @curr_text
502
+ elsif name == 'MaxKeys'
503
+ @properties.max_keys = @curr_text.to_i
504
+ elsif name == 'Delimiter'
505
+ @properties.delimiter = @curr_text
506
+ elsif name == 'IsTruncated'
507
+ @properties.is_truncated = @curr_text == 'true'
508
+ elsif name == 'NextMarker'
509
+ @properties.next_marker = @curr_text
510
+ elsif name == 'Contents'
511
+ @entries << @curr_entry
512
+ elsif name == 'Key'
513
+ @curr_entry.key = @curr_text
514
+ elsif name == 'LastModified'
515
+ @curr_entry.last_modified = @curr_text
516
+ elsif name == 'ETag'
517
+ @curr_entry.etag = @curr_text
518
+ elsif name == 'Size'
519
+ @curr_entry.size = @curr_text.to_i
520
+ elsif name == 'StorageClass'
521
+ @curr_entry.storage_class = @curr_text
522
+ elsif name == 'ID'
523
+ @curr_entry.owner.id = @curr_text
524
+ elsif name == 'DisplayName'
525
+ @curr_entry.owner.display_name = @curr_text
526
+ elsif name == 'CommonPrefixes'
527
+ @common_prefixes << @common_prefix_entry
528
+ elsif name == 'Prefix'
529
+ # this is the common prefix for keys that match up to the delimiter
530
+ @common_prefix_entry.prefix = @curr_text
531
+ end
532
+ @curr_text = ''
533
+ end
534
+
535
+ def text(text)
536
+ @curr_text += text
537
+ end
538
+
539
+ def xmldecl(version, encoding, standalone)
540
+ # ignore
541
+ end
542
+
543
+ # get ready for another parse
544
+ def reset
545
+ @is_echoed_prefix = true;
546
+ @entries = []
547
+ @curr_entry = nil
548
+ @common_prefixes = []
549
+ @common_prefix_entry = nil
550
+ @curr_text = ''
551
+ end
552
+ end
553
+
554
+ class Bucket
555
+ attr_accessor :name
556
+ attr_accessor :creation_date
557
+ end
558
+
559
+ class ListAllMyBucketsParser
560
+ attr_reader :entries
561
+
562
+ def initialize
563
+ reset
564
+ end
565
+
566
+ def tag_start(name, attributes)
567
+ if name == 'Bucket'
568
+ @curr_bucket = Bucket.new
569
+ end
570
+ end
571
+
572
+ # we have one, add him to the entries list
573
+ def tag_end(name)
574
+ if name == 'Bucket'
575
+ @entries << @curr_bucket
576
+ elsif name == 'Name'
577
+ @curr_bucket.name = @curr_text
578
+ elsif name == 'CreationDate'
579
+ @curr_bucket.creation_date = @curr_text
580
+ end
581
+ @curr_text = ''
582
+ end
583
+
584
+ def text(text)
585
+ @curr_text += text
586
+ end
587
+
588
+ def xmldecl(version, encoding, standalone)
589
+ # ignore
590
+ end
591
+
592
+ # get ready for another parse
593
+ def reset
594
+ @entries = []
595
+ @owner = nil
596
+ @curr_bucket = nil
597
+ @curr_text = ''
598
+ end
599
+ end
600
+
601
+ class Response
602
+ attr_reader :http_response
603
+ def initialize(response)
604
+ @http_response = response
605
+ end
606
+ end
607
+
608
+ class GetResponse < Response
609
+ attr_reader :object
610
+ def initialize(response)
611
+ super(response)
612
+ metadata = get_aws_metadata(response)
613
+ data = response.body
614
+ @object = S3Object.new(data, metadata)
615
+ end
616
+
617
+ # parses the request headers and pulls out the s3 metadata into a hash
618
+ def get_aws_metadata(response)
619
+ metadata = {}
620
+ response.each do |key, value|
621
+ if key =~ /^#{METADATA_PREFIX}(.*)$/oi
622
+ metadata[$1] = value
623
+ end
624
+ end
625
+ return metadata
626
+ end
627
+ end
628
+
629
+ class ListBucketResponse < Response
630
+ attr_reader :properties
631
+ attr_reader :entries
632
+ attr_reader :common_prefix_entries
633
+
634
+ def initialize(response)
635
+ super(response)
636
+ if response.is_a? Net::HTTPSuccess
637
+ parser = ListBucketParser.new
638
+ REXML::Document.parse_stream(response.body, parser)
639
+ @properties = parser.properties
640
+ @entries = parser.entries
641
+ @common_prefix_entries = parser.common_prefixes
642
+ else
643
+ @entries = []
644
+ end
645
+ end
646
+ end
647
+
648
+ class ListAllMyBucketsResponse < Response
649
+ attr_reader :entries
650
+ def initialize(response)
651
+ super(response)
652
+ if response.is_a? Net::HTTPSuccess
653
+ parser = ListAllMyBucketsParser.new
654
+ REXML::Document.parse_stream(response.body, parser)
655
+ @entries = parser.entries
656
+ else
657
+ @entries = []
658
+ end
659
+ end
660
+ end
661
+ end
File without changes
metadata ADDED
@@ -0,0 +1,57 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.8.11
3
+ specification_version: 1
4
+ name: s3-ruby
5
+ version: !ruby/object:Gem::Version
6
+ version: 0.1.0
7
+ date: 2006-11-23 00:00:00 -06:00
8
+ summary: A library for the Amazon S3 (Simple Storage Service) web service REST API
9
+ require_paths:
10
+ - lib
11
+ email: ryand-ruby@zenspider.com
12
+ homepage: " by Chris Carter <cdcarter@gmail.com>"
13
+ rubyforge_project: s3-ruby
14
+ description: "== FEATURES/PROBLEMS: * Full REST API support * Support for streams * no tests at this point == DESCRIPTION: s3-ruby interfaces with the REST API of Amazon's S3 (Simple Storage Service) to give your ruby applications full access to the service."
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ authors:
29
+ - Ryan Davis
30
+ files:
31
+ - History.txt
32
+ - Manifest.txt
33
+ - README.txt
34
+ - Rakefile
35
+ - lib/s3-ruby.rb
36
+ test_files:
37
+ - test/test_s3-ruby.rb
38
+ rdoc_options: []
39
+
40
+ extra_rdoc_files: []
41
+
42
+ executables: []
43
+
44
+ extensions: []
45
+
46
+ requirements: []
47
+
48
+ dependencies:
49
+ - !ruby/object:Gem::Dependency
50
+ name: hoe
51
+ version_requirement:
52
+ version_requirements: !ruby/object:Gem::Version::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: 1.1.4
57
+ version: