s33r 0.4.2 → 0.5

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.
Files changed (114) hide show
  1. data/examples/cli/instant_download_server.rb +88 -0
  2. data/examples/cli/s3cli.rb +31 -52
  3. data/examples/cli/simple.rb +16 -6
  4. data/examples/fores33r/app/controllers/browser_controller.rb +12 -10
  5. data/examples/fores33r/app/helpers/application_helper.rb +2 -1
  6. data/examples/fores33r/app/views/browser/_upload.rhtml +1 -1
  7. data/examples/fores33r/app/views/browser/index.rhtml +4 -4
  8. data/examples/fores33r/config/environment.rb +5 -3
  9. data/examples/fores33r/log/development.log +2259 -0
  10. data/examples/fores33r/log/mongrel.log +59 -0
  11. data/examples/s3.yaml +2 -6
  12. data/lib/s33r/bucket.rb +103 -0
  13. data/lib/s33r/bucket_listing.rb +33 -76
  14. data/lib/s33r/client.rb +305 -446
  15. data/lib/s33r/networking.rb +197 -0
  16. data/lib/s33r/s33r_exception.rb +29 -18
  17. data/lib/s33r/s33r_http.rb +36 -18
  18. data/lib/s33r/s3_acl.rb +32 -52
  19. data/lib/s33r/s3_logging.rb +117 -0
  20. data/lib/s33r/s3_obj.rb +124 -69
  21. data/lib/s33r/utility.rb +447 -0
  22. data/test/cases/spec_acl.rb +10 -40
  23. data/test/cases/spec_bucket_listing.rb +12 -32
  24. data/test/cases/spec_logging.rb +47 -0
  25. data/test/cases/spec_networking.rb +11 -0
  26. data/test/cases/spec_s3_object.rb +44 -5
  27. data/test/cases/spec_utility.rb +264 -0
  28. data/test/files/acl.xml +0 -6
  29. data/test/files/config.yaml +5 -0
  30. data/test/files/logging_status_disabled.xml +3 -0
  31. data/test/files/logging_status_enabled.xml +7 -0
  32. data/test/test_setup.rb +7 -2
  33. metadata +16 -94
  34. data/examples/cli/acl_x.rb +0 -41
  35. data/examples/cli/logging_x.rb +0 -20
  36. data/examples/fores33r/README +0 -183
  37. data/html/classes/MIME.html +0 -120
  38. data/html/classes/MIME/InvalidContentType.html +0 -119
  39. data/html/classes/MIME/Type.html +0 -1173
  40. data/html/classes/MIME/Types.html +0 -566
  41. data/html/classes/Net.html +0 -108
  42. data/html/classes/Net/HTTPGenericRequest.html +0 -233
  43. data/html/classes/Net/HTTPResponse.html +0 -271
  44. data/html/classes/S33r.html +0 -986
  45. data/html/classes/S33r/BucketListing.html +0 -434
  46. data/html/classes/S33r/Client.html +0 -1575
  47. data/html/classes/S33r/LoggingResource.html +0 -222
  48. data/html/classes/S33r/NamedBucket.html +0 -693
  49. data/html/classes/S33r/OrderlyXmlMarkup.html +0 -165
  50. data/html/classes/S33r/S33rException.html +0 -124
  51. data/html/classes/S33r/S33rException/BucketListingMaxKeysError.html +0 -111
  52. data/html/classes/S33r/S33rException/BucketNotLogTargetable.html +0 -119
  53. data/html/classes/S33r/S33rException/InvalidBucketListing.html +0 -111
  54. data/html/classes/S33r/S33rException/InvalidPermission.html +0 -111
  55. data/html/classes/S33r/S33rException/InvalidS3GroupType.html +0 -111
  56. data/html/classes/S33r/S33rException/MalformedBucketName.html +0 -111
  57. data/html/classes/S33r/S33rException/MethodNotAvailable.html +0 -111
  58. data/html/classes/S33r/S33rException/MissingBucketName.html +0 -111
  59. data/html/classes/S33r/S33rException/MissingRequiredHeaders.html +0 -111
  60. data/html/classes/S33r/S33rException/MissingResource.html +0 -111
  61. data/html/classes/S33r/S33rException/S3FallenOver.html +0 -111
  62. data/html/classes/S33r/S33rException/TryingToPutEmptyResource.html +0 -117
  63. data/html/classes/S33r/S33rException/UnsupportedCannedACL.html +0 -111
  64. data/html/classes/S33r/S33rException/UnsupportedHTTPMethod.html +0 -111
  65. data/html/classes/S33r/S3ACL.html +0 -125
  66. data/html/classes/S33r/S3ACL/ACLDoc.html +0 -521
  67. data/html/classes/S33r/S3ACL/AmazonCustomer.html +0 -168
  68. data/html/classes/S33r/S3ACL/CanonicalUser.html +0 -212
  69. data/html/classes/S33r/S3ACL/Grant.html +0 -403
  70. data/html/classes/S33r/S3ACL/Grantee.html +0 -239
  71. data/html/classes/S33r/S3ACL/Group.html +0 -178
  72. data/html/classes/S33r/S3Object.html +0 -618
  73. data/html/classes/S33r/Sync.html +0 -152
  74. data/html/classes/XML.html +0 -202
  75. data/html/classes/XML/Document.html +0 -125
  76. data/html/classes/XML/Node.html +0 -124
  77. data/html/created.rid +0 -1
  78. data/html/files/CHANGELOG.html +0 -107
  79. data/html/files/MIT-LICENSE.html +0 -129
  80. data/html/files/README_txt.html +0 -259
  81. data/html/files/lib/s33r/bucket_listing_rb.html +0 -101
  82. data/html/files/lib/s33r/builder_rb.html +0 -108
  83. data/html/files/lib/s33r/client_rb.html +0 -111
  84. data/html/files/lib/s33r/core_rb.html +0 -113
  85. data/html/files/lib/s33r/libxml_extensions_rb.html +0 -101
  86. data/html/files/lib/s33r/libxml_loader_rb.html +0 -109
  87. data/html/files/lib/s33r/logging_rb.html +0 -108
  88. data/html/files/lib/s33r/mimetypes_rb.html +0 -120
  89. data/html/files/lib/s33r/named_bucket_rb.html +0 -101
  90. data/html/files/lib/s33r/s33r_exception_rb.html +0 -101
  91. data/html/files/lib/s33r/s33r_http_rb.html +0 -108
  92. data/html/files/lib/s33r/s3_acl_rb.html +0 -108
  93. data/html/files/lib/s33r/s3_obj_rb.html +0 -108
  94. data/html/files/lib/s33r/sync_rb.html +0 -101
  95. data/html/files/lib/s33r_rb.html +0 -101
  96. data/html/fr_class_index.html +0 -66
  97. data/html/fr_file_index.html +0 -44
  98. data/html/fr_method_index.html +0 -183
  99. data/html/index.html +0 -24
  100. data/html/rdoc-style.css +0 -208
  101. data/lib/s33r/core.rb +0 -296
  102. data/lib/s33r/logging.rb +0 -43
  103. data/lib/s33r/named_bucket.rb +0 -148
  104. data/lib/s33r/sync.rb +0 -13
  105. data/test/cases/spec_all_buckets.rb +0 -28
  106. data/test/cases/spec_client.rb +0 -101
  107. data/test/cases/spec_core.rb +0 -128
  108. data/test/cases/spec_namedbucket.rb +0 -46
  109. data/test/cases/spec_sync.rb +0 -34
  110. data/test/files/all_buckets.xml +0 -21
  111. data/test/files/client_config.yml +0 -5
  112. data/test/files/namedbucket_config.yml +0 -8
  113. data/test/files/namedbucket_config2.yml +0 -8
  114. data/test/test_bucket_setup.rb +0 -41
@@ -3,7 +3,7 @@ require base + '/../test_setup'
3
3
 
4
4
  context 'S33r ACL functions' do
5
5
  setup do
6
- @acl_grant_xml1 = clean_xml(File.open(File.join(File.dirname(__FILE__), '../files/acl_grant1.xml')).read)
6
+ @acl_grant_xml1 = load_test_xml('acl_grant1.xml')
7
7
  @canonical_user = CanonicalUser.new('a9a7b886d6fd24a52fe8ca5bef65f89a64e0193f23000e241bf9b1c61be666e9',
8
8
  'chriscustomer')
9
9
  @canonical_user_grant = Grant.new(@canonical_user, :all)
@@ -11,24 +11,22 @@ context 'S33r ACL functions' do
11
11
  'mimbo')
12
12
  @canonical_user_grant2 = Grant.new(@canonical_user2, :read)
13
13
 
14
- @acl_grant_xml2 = clean_xml(File.open(File.join(File.dirname(__FILE__), '../files/acl_grant2.xml')).read)
14
+ @acl_grant_xml2 = load_test_xml('acl_grant2.xml')
15
15
  @amazon_grantee = AmazonCustomer.new('elliot@test.org')
16
16
  @amazon_grant = Grant.new(@amazon_grantee, :read)
17
- @amazon_grantee2 = AmazonCustomer.new('bingo@blinky.com')
18
- @amazon_grant2 = Grant.new(@amazon_grantee2, :write)
19
17
 
20
- @acl_grant_xml3 = clean_xml(File.open(File.join(File.dirname(__FILE__), '../files/acl_grant3.xml')).read)
18
+ @acl_grant_xml3 = load_test_xml('acl_grant3.xml')
21
19
  @group_grantee = Group.new(:all_users)
22
20
  @group_grant = Grant.new(@group_grantee, :write)
23
21
 
24
- @acl_grant_xml4 = clean_xml(File.open(File.join(File.dirname(__FILE__), '../files/acl_grant4.xml')).read)
22
+ @acl_grant_xml4 = load_test_xml('acl_grant4.xml')
25
23
  @group_grantee2 = Group.new(:authenticated_users)
26
24
  @group_grant2 = Grant.new(@group_grantee2, :read_acl)
27
25
 
28
- @full_acl_xml = clean_xml(File.open(File.join(File.dirname(__FILE__), '../files/acl.xml')).read)
29
- @full_acl_doc = ACLDoc.new(@canonical_user)
26
+ @full_acl_xml = load_test_xml('acl.xml')
27
+ @full_acl_doc = Policy.new(@canonical_user)
30
28
  @full_acl_doc.grants = [@canonical_user_grant, @canonical_user_grant2, @amazon_grant,
31
- @amazon_grant2, @group_grant, @group_grant2]
29
+ @group_grant, @group_grant2]
32
30
 
33
31
  # Grants created using convenience methods
34
32
  @con_canonical_user_grant = Grant.for_canonical_user('a9a7b886d6fd24a52fe8ca5bef65f89a64e0193f23000e241bf9b1c61be666e9',
@@ -36,22 +34,11 @@ context 'S33r ACL functions' do
36
34
  @con_canonical_user_grant2 = Grant.for_canonical_user('baa7b886d6fd24a52fe8ca5bef65f89a64e0193f23000e241bf9b1c61be666e9',
37
35
  'mimbo', :read)
38
36
  @con_amazon_grant = Grant.for_amazon_customer('elliot@test.org', :read)
39
- @con_amazon_grant2 = Grant.for_amazon_customer('bingo@blinky.com', :write)
40
37
  @con_group_grant1 = Grant.for_group(:all_users, :write)
41
38
  @con_group_grant2 = Grant.for_group(:authenticated_users, :read_acl)
42
- @con_full_acl_doc = ACLDoc.new(@canonical_user)
39
+ @con_full_acl_doc = Policy.new(@canonical_user)
43
40
  @con_full_acl_doc.grants = [@con_canonical_user_grant, @con_canonical_user_grant2,
44
- @con_amazon_grant, @con_amazon_grant2, @con_group_grant1, @con_group_grant2]
45
-
46
- # Tests for logging ACLs
47
- @logging_acl_xml = clean_xml(File.open(File.join(File.dirname(__FILE__), '../files/logging_acl.xml')).read)
48
- @logging_acl_doc = ACLDoc.from_xml(@logging_acl_xml)
49
- @log_delivery_write = Grant.for_group(:log_delivery, :write)
50
- @log_delivery_read_acl = Grant.for_group(:log_delivery, :read_acl)
51
-
52
- # clone without the logging grants
53
- @logging_acl_doc_no_grants = ACLDoc.from_xml(@logging_acl_xml)
54
- @logging_acl_doc_no_grants.grants.delete_if { |g| @log_delivery_write == g or @log_delivery_read_acl == g }
41
+ @con_amazon_grant, @con_group_grant1, @con_group_grant2]
55
42
  end
56
43
 
57
44
  specify 'should represent a grant for a CanonicalUser' do
@@ -119,28 +106,11 @@ context 'S33r ACL functions' do
119
106
  end
120
107
 
121
108
  specify 'should parse AccessControlPolicy documents' do
122
- acl_doc_from_xml = ACLDoc.from_xml(@full_acl_xml)
109
+ acl_doc_from_xml = Policy.from_xml(@full_acl_xml)
123
110
  acl_doc_from_xml.owner.should.equal @full_acl_doc.owner
124
111
  @full_acl_doc.grants.each do |g|
125
112
  acl_doc_from_xml.grants = acl_doc_from_xml.grants.delete_if { |i| i == g }
126
113
  end
127
114
  acl_doc_from_xml.grants.should.be.empty
128
115
  end
129
-
130
- specify 'should represent LogDelivery grants so buckets can act as logging targets' do
131
- @logging_acl_doc.grants.should.include @log_delivery_write
132
- @logging_acl_doc.grants.should.include @log_delivery_read_acl
133
- end
134
-
135
- specify 'should be able to report whether an ACL indicates bucket is a logging target' do
136
- @logging_acl_doc.log_targetable?.should.be true
137
-
138
- # check the variant where the grants have been removed
139
- @logging_acl_doc_no_grants.log_targetable?.should.be false
140
- end
141
-
142
- specify 'should be able to simply add logging target grants to an ACL' do
143
- @logging_acl_doc_no_grants.add_log_target_grants
144
- clean_xml(@logging_acl_doc_no_grants.to_xml).should.equal @logging_acl_xml
145
- end
146
116
  end
@@ -1,19 +1,13 @@
1
- base = File.dirname(__FILE__)
2
- require base + '/../test_setup'
1
+ require File.dirname(__FILE__) + '/../test_setup'
3
2
  require 'set'
4
3
 
5
4
  context 'S33r bucket listing' do
6
5
  setup do
7
- xml_file = File.join(base, '../files/bucket_listing.xml')
8
- @with_bucket_listing_xml = File.open(xml_file) { |f| f.read }
9
- xml_file2 = File.join(base, '../files/bucket_listing2.xml')
10
- @with_bucket_listing_xml2 = File.open(xml_file2) { |f| f.read }
11
- xml_file3 = File.join(base, '../files/bucket_listing3.xml')
12
- @with_empty_bucket_listing_xml = File.open(xml_file3) { |f| f.read }
13
- xml_file4 = File.join(base, '../files/bucket_listing_broken.xml')
14
- @with_broken_bucket_listing_xml = File.open(xml_file4) { |f| f.read }
15
- xml_file5 = File.join(base, '../files/suspect_bucket_listing.xml')
16
- @with_suspect_bl_xml = File.open(xml_file5) { |f| f.read }
6
+ @with_bucket_listing_xml = load_test_xml('bucket_listing.xml')
7
+ @with_bucket_listing_xml2 = load_test_xml('bucket_listing2.xml')
8
+ @with_empty_bucket_listing_xml = load_test_xml('bucket_listing3.xml')
9
+ @with_broken_bucket_listing_xml = load_test_xml('bucket_listing_broken.xml')
10
+ @with_suspect_bl_xml = load_test_xml('suspect_bucket_listing.xml')
17
11
 
18
12
  @bucket_listing = BucketListing.new(@with_bucket_listing_xml)
19
13
  @bucket_properties = %w(name prefix marker max_keys is_truncated)
@@ -25,11 +19,11 @@ context 'S33r bucket listing' do
25
19
  end
26
20
 
27
21
  specify 'cannot be created from invalid XML' do
28
- lambda { BucketListing.new(nil) }.should.raise S33rException::InvalidBucketListing
22
+ lambda { BucketListing.new(nil) }.should.raise S3Exception::InvalidBucketListing
29
23
  end
30
24
 
31
25
  specify 'should recover gracefully from broken bucket listing XML' do
32
- lambda { BucketListing.new(@with_broken_bucket_listing_xml) }.should.raise S33rException::InvalidBucketListing
26
+ lambda { BucketListing.new(@with_broken_bucket_listing_xml) }.should.raise S3Exception::InvalidBucketListing
33
27
  end
34
28
 
35
29
  specify 'should cope if bucket is empty (i.e. no <Contents> elements)' do
@@ -69,30 +63,16 @@ context 'S33r bucket listing' do
69
63
  end
70
64
 
71
65
  specify 'should store resources (<Contents> elements) in a hash' do
72
- @bucket_listing.contents.size.should_be 10
73
- first_obj = @bucket_listing.contents['/home/ell/dir1/four.txt']
66
+ @bucket_listing.size.should_be 10
67
+ first_obj = @bucket_listing['/home/ell/dir1/four.txt']
74
68
  first_obj.should_be_instance_of S3Object
75
69
  end
76
-
77
- specify 'should enable access to metadata for a resource by its key' do
78
- obj = @bucket_listing['/home/ell/dir1/four.txt']
79
- obj.should_be_instance_of S3Object
80
- obj.etag.should_equal '24ce59274b89287b3960c184153ac24b'
81
- end
82
-
83
- specify 'should build a full S3Object representation when given XML from a GET on a resource key' do
84
- todo
85
- end
86
-
87
- specify 'should provide easy access to <CommonPrefixes> elements as a hash' do
88
- todo
89
- end
90
70
 
91
71
  # attempting to fix ListBucketResult errors reported by Alex Payne
92
72
  specify 'should handle suspect bucket listing' do
93
73
  puts @with_suspect_bl_xml
94
74
  bl = BucketListing.new(@with_suspect_bl_xml)
95
- bl.contents.size.should_be 1
96
- bl.contents.keys.should.include 'orly.jpg'
75
+ bl.size.should_be 1
76
+ bl.keys.should.include 'orly.jpg'
97
77
  end
98
78
  end
@@ -0,0 +1,47 @@
1
+ base = File.dirname(__FILE__)
2
+ require base + '/../test_setup'
3
+
4
+ context 'S33r logging' do
5
+ setup do
6
+ # Tests for BucketLoggingStatus generator
7
+ @logging_enabled_xml = load_test_xml('logging_status_enabled.xml')
8
+ @logging_disabled_xml = load_test_xml('logging_status_disabled.xml')
9
+ @logging_resource_to_enable = LoggingResource.new('mylogs', 'mybucket-access_log-')
10
+ @logging_resource_to_disable = LoggingResource.new
11
+
12
+ # Tests for logging ACLs
13
+ @logging_acl_xml = load_test_xml('logging_acl.xml')
14
+ @logging_acl_doc = Policy.from_xml(@logging_acl_xml)
15
+ @log_delivery_write = Grant.for_group(:log_delivery, :write)
16
+ @log_delivery_read_acl = Grant.for_group(:log_delivery, :read_acl)
17
+
18
+ # clone without the logging grants
19
+ @logging_acl_doc_no_grants = Policy.from_xml(@logging_acl_xml)
20
+ @logging_acl_doc_no_grants.grants.delete_if { |g| @log_delivery_write == g or @log_delivery_read_acl == g }
21
+ end
22
+
23
+ specify 'should generate BucketLoggingStatus XML document to enable logging' do
24
+ clean_xml(@logging_resource_to_enable.to_xml).should.equal @logging_enabled_xml
25
+ end
26
+
27
+ specify 'should generate BucketLoggingStatus XML document to disable logging' do
28
+ clean_xml(@logging_resource_to_disable.to_xml).should.equal @logging_disabled_xml
29
+ end
30
+
31
+ specify 'should represent LogDelivery grants so buckets can act as logging targets' do
32
+ @logging_acl_doc.grants.should.include @log_delivery_write
33
+ @logging_acl_doc.grants.should.include @log_delivery_read_acl
34
+ end
35
+
36
+ specify 'should be able to report whether an ACL indicates bucket is a logging target' do
37
+ @logging_acl_doc.log_targetable?.should.be true
38
+
39
+ # check the variant where the grants have been removed
40
+ @logging_acl_doc_no_grants.log_targetable?.should.be false
41
+ end
42
+
43
+ specify 'should be able to simply add logging target grants to an ACL' do
44
+ @logging_acl_doc_no_grants.add_log_target_grants
45
+ clean_xml(@logging_acl_doc_no_grants.to_xml).should.equal @logging_acl_xml
46
+ end
47
+ end
@@ -0,0 +1,11 @@
1
+ base = File.dirname(__FILE__)
2
+ require File.join(base, '../test_setup')
3
+
4
+ context 'S33r networking' do
5
+ setup do
6
+
7
+ end
8
+
9
+ specify 'spec' do
10
+ end
11
+ end
@@ -5,6 +5,21 @@ context 'S3 object' do
5
5
  setup do
6
6
  @s3_object_xml = File.open(File.join(base, '../files/s3_object.xml')).read
7
7
  @s3obj = S3Object.from_xml_string(@s3_object_xml)
8
+ @s3obj.bucket = Bucket.new('test-bucket', :access => Testing::ACCESS_KEY, \
9
+ :secret => Testing::SECRET_ACCESS_KEY)
10
+
11
+ # Orphan s3obj (not attached to a bucket).
12
+ @orphan_obj = S3Object.new('my-key')
13
+
14
+ bucket = Bucket.new('quotes', :access => Testing::ACCESS_KEY, \
15
+ :secret => Testing::SECRET_ACCESS_KEY, :use_ssl => false)
16
+ @s3obj2 = S3Object.new('nelson', nil, :bucket => bucket)
17
+ authenticated_url_end = "s3.amazonaws.com/quotes/nelson?Signature=vjbyPxybdZaNmGa%2ByT272YEAiv4%3D&"+
18
+ "AWSAccessKeyId=44CF9590006BF252F707&Expires=1141889120"
19
+ @correct_authenticated_url = "http://" + authenticated_url_end
20
+ @correct_public_url = "http://s3.amazonaws.com/quotes/nelson"
21
+ @correct_public_url_with_ssl = "https://s3.amazonaws.com/quotes/nelson"
22
+ @correct_public_url_with_subdomain = "http://quotes.s3.amazonaws.com/nelson"
8
23
  end
9
24
 
10
25
  specify 'can be initialised from XML fragment with correct data types' do
@@ -14,6 +29,14 @@ context 'S3 object' do
14
29
  @s3obj.etag.should.equal '24ce59274b89287b3960c184153ac24b'
15
30
  @s3obj.size.should.equal 14
16
31
  end
32
+
33
+ specify 'can be associated with bucket on creation' do
34
+ @s3obj2.bucket.name.should.equal 'quotes'
35
+ end
36
+
37
+ specify 'can be created from a file' do
38
+ todo
39
+ end
17
40
 
18
41
  specify 'should treat the owner as an object in his/her own right' do
19
42
  [@s3obj.owner.user_id, @s3obj.owner.display_name].should.equal \
@@ -21,15 +44,31 @@ context 'S3 object' do
21
44
  @s3obj.owner.should_be_instance_of S3ACL::CanonicalUser
22
45
  end
23
46
 
24
- specify 'can be associated with a NamedBucket' do
47
+ specify 'can be saved' do
25
48
  todo
26
49
  end
27
-
28
- specify 'can be saved by proxing through the NamedBucket it is associated with' do
50
+
51
+ specify 'can be renamed' do
29
52
  todo
30
53
  end
31
-
32
- specify 'cannot be saved unless associated with a NamedBucket' do
54
+
55
+ specify 'can be deleted' do
33
56
  todo
34
57
  end
58
+
59
+ specify 'should return authenticated URL' do
60
+ @s3obj2.url(:authenticated => true, :expires => 1141889120).should.equal @correct_authenticated_url
61
+ end
62
+
63
+ specify 'should return public URL' do
64
+ @s3obj2.url.should.equal @correct_public_url
65
+ end
66
+
67
+ specify 'should optionally return HTTPS URL' do
68
+ @s3obj2.url(:use_ssl => true).should.equal @correct_public_url_with_ssl
69
+ end
70
+
71
+ specify 'should optionally return URL with domain name set to bucket name' do
72
+ @s3obj2.url(:subdomain => true).should.equal @correct_public_url_with_subdomain
73
+ end
35
74
  end
@@ -0,0 +1,264 @@
1
+ base = File.dirname(__FILE__)
2
+ require File.join(base, '../test_setup')
3
+ require 'time'
4
+
5
+ context 'S33r utility' do
6
+
7
+ setup do
8
+ @yaml_file = File.join(base, '../files/config.yaml')
9
+
10
+ @for_request_method = 'PUT'
11
+ @for_request_path = "/quotes/nelson"
12
+ @for_request_headers = {
13
+ "Content-Md5" => "c8fdb181845a4ca6b8fec737b3581d76",
14
+ "Content-Type" => "text/html",
15
+ "Date" => "Thu, 17 Nov 2005 18:49:58 GMT",
16
+ "X-Amz-Meta-Author" => "foo@bar.com",
17
+ "X-Amz-Magic" => "abracadabra"
18
+ }
19
+
20
+ # create broken request header hash
21
+ @for_incomplete_headers = @for_request_headers.clone.delete_if do |key,value|
22
+ 'Content-Type' == key or 'Date' == key
23
+ end
24
+
25
+ @correct_canonical_string = "PUT\nc8fdb181845a4ca6b8fec737b3581d76\n" +
26
+ "text/html\nThu, 17 Nov 2005 18:49:58 GMT\nx-amz-magic:abracadabra\n" +
27
+ "x-amz-meta-author:foo@bar.com\n/quotes/nelson"
28
+ @correct_signature = "jZNOcbfWmD/A/f3hSvVzXZjM2HU="
29
+ @correct_auth_header = "AWS #{Testing::ACCESS_KEY}:#{@correct_signature}"
30
+
31
+ @with_invalid_bucket_name = '/badbucket'
32
+ @with_invalid_bucket_name2 = 'badbucket/'
33
+
34
+ authenticated_url_end = "s3.amazonaws.com/quotes/nelson?Signature=vjbyPxybdZaNmGa%2ByT272YEAiv4%3D&"+
35
+ "AWSAccessKeyId=44CF9590006BF252F707&Expires=1141889120"
36
+ @correct_authenticated_url = "http://" + authenticated_url_end
37
+ @correct_authenticated_url_with_ssl = "https://" + authenticated_url_end
38
+
39
+ @correct_public_url = "http://s3.amazonaws.com/quotes/nelson"
40
+ @correct_public_ssl_url = "https://s3.amazonaws.com/quotes/nelson"
41
+ @correct_public_url_with_subdomain = "http://quotes.s3.amazonaws.com/nelson"
42
+ @correct_public_url_with_ssl_and_subdomain = "https://quotes.s3.amazonaws.com/nelson"
43
+ @correct_public_url_without_key = "http://s3.amazonaws.com/quotes/"
44
+ @correct_logging_url = "http://s3.amazonaws.com/quotes/?logging"
45
+ @correct_acl_url = "http://s3.amazonaws.com/quotes/?acl"
46
+ @correct_acl_url_with_key = "http://s3.amazonaws.com/quotes/nelson?acl"
47
+ @correct_public_url_with_qstring = "http://s3.amazonaws.com/quotes/?prefix=%2Fhome&max-keys=400"
48
+ end
49
+
50
+ specify 'should load config files containing standard S33r settings' do
51
+ config, _ = S33r.load_config(@yaml_file)
52
+ config[:access].should.equal Testing::ACCESS_KEY
53
+ end
54
+
55
+ specify 'can parse ERb embedded in config. file' do
56
+ config, _ = S33r.load_config(@yaml_file)
57
+ config[:secret].should.equal Testing::SECRET_ACCESS_KEY
58
+ end
59
+
60
+ specify 'can parse extra application-specific settings in config. file' do
61
+ config, options = S33r.load_config(@yaml_file)
62
+ options[:email_to].should.equal Testing::EMAIL
63
+ # Make sure the 'options' section has not been included in the general config..
64
+ config.keys.should.not.include :options
65
+ end
66
+
67
+ specify 'should generate correct canonical strings' do
68
+ generate_canonical_string(@for_request_method, @for_request_path,
69
+ @for_request_headers).should.equal @correct_canonical_string
70
+ end
71
+
72
+ specify 'should generate correct signatures' do
73
+ generate_signature(Testing::SECRET_ACCESS_KEY,
74
+ @correct_canonical_string).should.equal @correct_signature
75
+ end
76
+
77
+ specify 'should generate correct auth headers' do
78
+ generate_auth_header_value(@for_request_method, @for_request_path, @for_request_headers,
79
+ Testing::ACCESS_KEY, Testing::SECRET_ACCESS_KEY).should.equal @correct_auth_header
80
+ end
81
+
82
+ specify 'should not generate auth header if bad HTTP method passed' do
83
+ lambda { generate_auth_header_value('duff', nil, nil, nil, nil) }.should.raise \
84
+ MethodNotAllowed
85
+ end
86
+
87
+ specify 'should not generate auth header if required headers missing' do
88
+ lambda { generate_auth_header_value('PUT', '/', @for_incomplete_headers,
89
+ nil, nil) }.should.raise MissingRequiredHeaders
90
+ end
91
+
92
+ specify 'when generating auth header, should allow addition of Date and Content-Type headers' do
93
+ now = Time.now
94
+
95
+ fixed_headers = default_headers(@for_incomplete_headers, :date => now,
96
+ :content_type => 'text/html')
97
+
98
+ fixed_headers['Date'].should.equal now.httpdate
99
+ fixed_headers['Content-Type'].should.equal 'text/html'
100
+ end
101
+
102
+ specify 'should generate correct x-amz-meta- headers from a hash' do
103
+ meta = {'myname' => 'elliot', 'myage' => 36}
104
+ headers = metadata_headers(meta)
105
+ headers.should.include 'x-amz-meta-myname'
106
+ headers.should.include 'x-amz-meta-myage'
107
+ headers['x-amz-meta-myname'].should.equal 'elliot'
108
+ headers['x-amz-meta-myage'].should.equal '36'
109
+ end
110
+
111
+ specify 'should not generate canned ACL header if invalid canned ACL supplied' do
112
+ lambda { canned_acl_header('duff') }.should.raise \
113
+ UnsupportedCannedACL
114
+ end
115
+
116
+ specify 'should correctly add canned ACL headers' do
117
+ new_headers = canned_acl_header('private')
118
+ new_headers.should.have(1).keys
119
+ new_headers.keys.should.include 'x-amz-acl'
120
+ new_headers['x-amz-acl'].should.equal 'private'
121
+ end
122
+
123
+ specify 'should set sensible defaults for missing Content-Type and Date headers' do
124
+ fixed_headers = default_headers(@for_incomplete_headers)
125
+ fixed_headers['Content-Type'].should.equal ''
126
+ fixed_headers.include?('Date').should.not.be nil
127
+ end
128
+
129
+ specify 'should default to text/plain mimetype for unknown file types' do
130
+ guess_mime_type('hello.madeup').should.equal('text/plain')
131
+ end
132
+
133
+ specify 'should generate correct Content-Disposition, Content-Type and Content-Transfer-Encoding headers' do
134
+ headers = content_headers('text/plain', 'download.jpg', true)
135
+ headers['Content-Type'].should.equal 'text/plain'
136
+ headers['Content-Disposition'].should.equal "attachment; filename=download.jpg"
137
+
138
+ headers = content_headers('image/jpeg', '/home/you/me.jpg', true)
139
+ headers['Content-Type'].should.equal 'image/jpeg'
140
+ headers['Content-Transfer-Encoding'].should.equal 'binary'
141
+ headers['Content-Disposition'].should.equal "attachment; filename=me.jpg"
142
+ end
143
+
144
+ specify 'should recognise invalid bucket names' do
145
+ lambda { bucket_name_valid?(@with_invalid_bucket_name) }.should.raise \
146
+ MalformedBucketName
147
+ lambda { bucket_name_valid?(@with_invalid_bucket_name2) }.should.raise \
148
+ MalformedBucketName
149
+ end
150
+
151
+ specify 'should return empty string if generating querystring with no key/value pairs' do
152
+ generate_querystring({}).should_equal ''
153
+ end
154
+
155
+ specify 'should correctly format querystring key/value pairs' do
156
+ generate_querystring({'message' => 'Hello world', 'id' => 1, 'page' => '[2,4]'}).should_equal \
157
+ 'message=Hello+world&id=1&page=%5B2%2C4%5D'
158
+ end
159
+
160
+ specify 'should allow symbols as names for querystring variables when generating querystrings' do
161
+ generate_querystring({ :prefix => '/home/ell' }).should.equal('prefix=%2Fhome%2Fell')
162
+ end
163
+
164
+ specify 'should convert integers to strings when generating querystrings' do
165
+ generate_querystring({ 'max-keys' => 400 }).should.equal('max-keys=400')
166
+ end
167
+
168
+ specify 'should generate URLs with authentication parameters' do
169
+ s3_authenticated_url(Testing::ACCESS_KEY, Testing::SECRET_ACCESS_KEY, :bucket => 'quotes', \
170
+ :key => 'nelson', :expires => 1141889120).should.equal @correct_authenticated_url
171
+ end
172
+
173
+ specify 'should be able to pass credentials as an option to the basic URL generator' do
174
+ s3_url(:access => Testing::ACCESS_KEY, :secret => Testing::SECRET_ACCESS_KEY, \
175
+ :authenticated => true, :bucket => 'quotes', :key => 'nelson', \
176
+ :expires => 1141889120).should.equal @correct_authenticated_url
177
+ end
178
+
179
+ specify 'should generate correct public URLs' do
180
+ s3_public_url(:bucket => 'quotes', :key => 'nelson').should.equal @correct_public_url
181
+ end
182
+
183
+ specify 'should correctly append querystring variables' do
184
+ s3_url(:bucket => 'quotes', :querystring => {'max-keys' => 400, 'prefix' => '/home'}).should.equal \
185
+ @correct_public_url_with_qstring
186
+ end
187
+
188
+ specify 'should generate HTTPS URLs' do
189
+ s3_public_url(:bucket => 'quotes', :key => 'nelson', :use_ssl => true).should.equal @correct_public_ssl_url
190
+ end
191
+
192
+ specify 'should generate public URLs with bucket name as subdomain' do
193
+ s3_public_url(:bucket => 'quotes', :key => 'nelson', :subdomain => true).should.equal \
194
+ @correct_public_url_with_subdomain
195
+ s3_public_url(:bucket => 'quotes', :key => 'nelson', :subdomain => true, :use_ssl => true).should.equal \
196
+ @correct_public_url_with_ssl_and_subdomain
197
+ end
198
+
199
+ specify 'should generate URLs for buckets without keys' do
200
+ s3_public_url(:bucket => 'quotes').should.equal @correct_public_url_without_key
201
+ end
202
+
203
+ specify 'should generate logging URLs' do
204
+ s3_url(:bucket => 'quotes', :logging => true).should.equal @correct_logging_url
205
+ end
206
+
207
+ specify 'should generate ACL URLs' do
208
+ s3_url(:bucket => 'quotes', :acl => true).should.equal @correct_acl_url
209
+ s3_url(:bucket => 'quotes', :key => 'nelson', :acl => true).should.equal @correct_acl_url_with_key
210
+ end
211
+
212
+ specify 'should turn off subdomain if authenticated URL' do
213
+ s3_url(:access => Testing::ACCESS_KEY, :secret => Testing::SECRET_ACCESS_KEY, \
214
+ :authenticated => true, :bucket => 'quotes', :key => 'nelson', :subdomain => true, \
215
+ :expires => 1141889120).should.equal @correct_authenticated_url
216
+ end
217
+
218
+ specify 'should turn off subdomain if generating SSL URL' do
219
+ s3_url(:bucket => 'quotes', :key => 'nelson', :use_ssl => true, \
220
+ :subdomain => true).should.equal @correct_public_ssl_url
221
+ end
222
+
223
+ specify 'should produce correct default expiry time if none specified, or time 50 years \
224
+ from now for :far_flung_future' do
225
+ class Time
226
+ alias :old_to_i :to_i
227
+ # N.B. the number below is the seconds since the epoch of now, defined above
228
+ def to_i ; 1168349580 ; end
229
+ end
230
+
231
+ # Default to DEFAULT_EXPIRY_SECS from now
232
+ expected_expiry = Time.now.to_i + DEFAULT_EXPIRY_SECS
233
+ expires = S33r.parse_expiry
234
+ expires.should.equal expected_expiry
235
+ expires.should_be_kind_of Integer
236
+
237
+ # If :far_flung_future is passed, default to 20 years from now.
238
+ expected_expiry = Time.now.to_i +
239
+ (60 * 60 * 24 * 365.25 * 20).to_i
240
+ expires = S33r.parse_expiry(:far_flung_future)
241
+ expires.should.equal expected_expiry
242
+ expires.should_be_kind_of Integer
243
+
244
+ class Time
245
+ remove_method(:to_i)
246
+ alias :to_i :old_to_i
247
+ end
248
+ end
249
+
250
+ specify 'should correctly set expiry from a datetime string' do
251
+ datetime_str = '9th January 2007 13:33'
252
+ expected_expiry = Time.parse(datetime_str).to_i
253
+ expires = S33r.parse_expiry(datetime_str)
254
+ expires.should.equal expected_expiry
255
+ expires.should_be_kind_of Integer
256
+ end
257
+
258
+ specify 'should provide a method for converting string keys of a hash into symbols' do
259
+ h = {'access' => Testing::ACCESS_KEY, 'secret' => Testing::SECRET_ACCESS_KEY}
260
+ symbolised = S33r.keys_to_symbols(h)
261
+ symbolised.should.equal({:access => Testing::ACCESS_KEY, :secret => Testing::SECRET_ACCESS_KEY})
262
+ end
263
+
264
+ end