aws-sdk-s3 1.75.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/aws-sdk-s3.rb +73 -0
- data/lib/aws-sdk-s3/bucket.rb +861 -0
- data/lib/aws-sdk-s3/bucket_acl.rb +277 -0
- data/lib/aws-sdk-s3/bucket_cors.rb +262 -0
- data/lib/aws-sdk-s3/bucket_lifecycle.rb +264 -0
- data/lib/aws-sdk-s3/bucket_lifecycle_configuration.rb +283 -0
- data/lib/aws-sdk-s3/bucket_logging.rb +251 -0
- data/lib/aws-sdk-s3/bucket_notification.rb +293 -0
- data/lib/aws-sdk-s3/bucket_policy.rb +242 -0
- data/lib/aws-sdk-s3/bucket_region_cache.rb +81 -0
- data/lib/aws-sdk-s3/bucket_request_payment.rb +236 -0
- data/lib/aws-sdk-s3/bucket_tagging.rb +251 -0
- data/lib/aws-sdk-s3/bucket_versioning.rb +312 -0
- data/lib/aws-sdk-s3/bucket_website.rb +292 -0
- data/lib/aws-sdk-s3/client.rb +11818 -0
- data/lib/aws-sdk-s3/client_api.rb +3014 -0
- data/lib/aws-sdk-s3/customizations.rb +34 -0
- data/lib/aws-sdk-s3/customizations/bucket.rb +162 -0
- data/lib/aws-sdk-s3/customizations/multipart_upload.rb +44 -0
- data/lib/aws-sdk-s3/customizations/object.rb +389 -0
- data/lib/aws-sdk-s3/customizations/object_summary.rb +85 -0
- data/lib/aws-sdk-s3/customizations/types/list_object_versions_output.rb +13 -0
- data/lib/aws-sdk-s3/encryption.rb +21 -0
- data/lib/aws-sdk-s3/encryption/client.rb +375 -0
- data/lib/aws-sdk-s3/encryption/decrypt_handler.rb +190 -0
- data/lib/aws-sdk-s3/encryption/default_cipher_provider.rb +65 -0
- data/lib/aws-sdk-s3/encryption/default_key_provider.rb +40 -0
- data/lib/aws-sdk-s3/encryption/encrypt_handler.rb +61 -0
- data/lib/aws-sdk-s3/encryption/errors.rb +15 -0
- data/lib/aws-sdk-s3/encryption/io_auth_decrypter.rb +58 -0
- data/lib/aws-sdk-s3/encryption/io_decrypter.rb +36 -0
- data/lib/aws-sdk-s3/encryption/io_encrypter.rb +71 -0
- data/lib/aws-sdk-s3/encryption/key_provider.rb +31 -0
- data/lib/aws-sdk-s3/encryption/kms_cipher_provider.rb +75 -0
- data/lib/aws-sdk-s3/encryption/materials.rb +60 -0
- data/lib/aws-sdk-s3/encryption/utils.rb +81 -0
- data/lib/aws-sdk-s3/encryptionV2/client.rb +388 -0
- data/lib/aws-sdk-s3/encryptionV2/decrypt_handler.rb +198 -0
- data/lib/aws-sdk-s3/encryptionV2/default_cipher_provider.rb +103 -0
- data/lib/aws-sdk-s3/encryptionV2/default_key_provider.rb +38 -0
- data/lib/aws-sdk-s3/encryptionV2/encrypt_handler.rb +66 -0
- data/lib/aws-sdk-s3/encryptionV2/errors.rb +13 -0
- data/lib/aws-sdk-s3/encryptionV2/io_auth_decrypter.rb +56 -0
- data/lib/aws-sdk-s3/encryptionV2/io_decrypter.rb +35 -0
- data/lib/aws-sdk-s3/encryptionV2/io_encrypter.rb +71 -0
- data/lib/aws-sdk-s3/encryptionV2/key_provider.rb +29 -0
- data/lib/aws-sdk-s3/encryptionV2/kms_cipher_provider.rb +99 -0
- data/lib/aws-sdk-s3/encryptionV2/materials.rb +58 -0
- data/lib/aws-sdk-s3/encryptionV2/utils.rb +116 -0
- data/lib/aws-sdk-s3/encryption_v2.rb +20 -0
- data/lib/aws-sdk-s3/errors.rb +115 -0
- data/lib/aws-sdk-s3/event_streams.rb +69 -0
- data/lib/aws-sdk-s3/file_downloader.rb +142 -0
- data/lib/aws-sdk-s3/file_part.rb +78 -0
- data/lib/aws-sdk-s3/file_uploader.rb +70 -0
- data/lib/aws-sdk-s3/legacy_signer.rb +189 -0
- data/lib/aws-sdk-s3/multipart_file_uploader.rb +227 -0
- data/lib/aws-sdk-s3/multipart_stream_uploader.rb +173 -0
- data/lib/aws-sdk-s3/multipart_upload.rb +401 -0
- data/lib/aws-sdk-s3/multipart_upload_error.rb +18 -0
- data/lib/aws-sdk-s3/multipart_upload_part.rb +423 -0
- data/lib/aws-sdk-s3/object.rb +1422 -0
- data/lib/aws-sdk-s3/object_acl.rb +333 -0
- data/lib/aws-sdk-s3/object_copier.rb +101 -0
- data/lib/aws-sdk-s3/object_multipart_copier.rb +182 -0
- data/lib/aws-sdk-s3/object_summary.rb +1181 -0
- data/lib/aws-sdk-s3/object_version.rb +550 -0
- data/lib/aws-sdk-s3/plugins/accelerate.rb +87 -0
- data/lib/aws-sdk-s3/plugins/bucket_arn.rb +212 -0
- data/lib/aws-sdk-s3/plugins/bucket_dns.rb +91 -0
- data/lib/aws-sdk-s3/plugins/bucket_name_restrictions.rb +45 -0
- data/lib/aws-sdk-s3/plugins/dualstack.rb +74 -0
- data/lib/aws-sdk-s3/plugins/expect_100_continue.rb +28 -0
- data/lib/aws-sdk-s3/plugins/get_bucket_location_fix.rb +25 -0
- data/lib/aws-sdk-s3/plugins/http_200_errors.rb +55 -0
- data/lib/aws-sdk-s3/plugins/iad_regional_endpoint.rb +62 -0
- data/lib/aws-sdk-s3/plugins/location_constraint.rb +35 -0
- data/lib/aws-sdk-s3/plugins/md5s.rb +84 -0
- data/lib/aws-sdk-s3/plugins/redirects.rb +45 -0
- data/lib/aws-sdk-s3/plugins/s3_host_id.rb +30 -0
- data/lib/aws-sdk-s3/plugins/s3_signer.rb +222 -0
- data/lib/aws-sdk-s3/plugins/sse_cpk.rb +70 -0
- data/lib/aws-sdk-s3/plugins/streaming_retry.rb +118 -0
- data/lib/aws-sdk-s3/plugins/url_encoded_keys.rb +97 -0
- data/lib/aws-sdk-s3/presigned_post.rb +686 -0
- data/lib/aws-sdk-s3/presigner.rb +253 -0
- data/lib/aws-sdk-s3/resource.rb +117 -0
- data/lib/aws-sdk-s3/types.rb +13154 -0
- data/lib/aws-sdk-s3/waiters.rb +243 -0
- metadata +184 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# WARNING ABOUT GENERATED CODE
|
4
|
+
#
|
5
|
+
# This file is generated. See the contributing guide for more information:
|
6
|
+
# https://github.com/aws/aws-sdk-ruby/blob/master/CONTRIBUTING.md
|
7
|
+
#
|
8
|
+
# WARNING ABOUT GENERATED CODE
|
9
|
+
|
10
|
+
module Aws::S3
|
11
|
+
module EventStreams
|
12
|
+
class SelectObjectContentEventStream
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@event_emitter = Aws::EventEmitter.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def on_records_event(&block)
|
19
|
+
@event_emitter.on(:records, block) if block_given?
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_stats_event(&block)
|
23
|
+
@event_emitter.on(:stats, block) if block_given?
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_progress_event(&block)
|
27
|
+
@event_emitter.on(:progress, block) if block_given?
|
28
|
+
end
|
29
|
+
|
30
|
+
def on_cont_event(&block)
|
31
|
+
@event_emitter.on(:cont, block) if block_given?
|
32
|
+
end
|
33
|
+
|
34
|
+
def on_end_event(&block)
|
35
|
+
@event_emitter.on(:end, block) if block_given?
|
36
|
+
end
|
37
|
+
|
38
|
+
def on_error_event(&block)
|
39
|
+
@event_emitter.on(:error, block) if block_given?
|
40
|
+
end
|
41
|
+
|
42
|
+
def on_initial_response_event(&block)
|
43
|
+
@event_emitter.on(:initial_response, block) if block_given?
|
44
|
+
end
|
45
|
+
|
46
|
+
def on_unknown_event(&block)
|
47
|
+
@event_emitter.on(:unknown_event, block) if block_given?
|
48
|
+
end
|
49
|
+
|
50
|
+
def on_event(&block)
|
51
|
+
on_records_event(&block)
|
52
|
+
on_stats_event(&block)
|
53
|
+
on_progress_event(&block)
|
54
|
+
on_cont_event(&block)
|
55
|
+
on_end_event(&block)
|
56
|
+
on_error_event(&block)
|
57
|
+
on_initial_response_event(&block)
|
58
|
+
on_unknown_event(&block)
|
59
|
+
end
|
60
|
+
|
61
|
+
# @api private
|
62
|
+
# @return Aws::EventEmitter
|
63
|
+
attr_reader :event_emitter
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
@@ -0,0 +1,142 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'thread'
|
5
|
+
require 'set'
|
6
|
+
require 'tmpdir'
|
7
|
+
|
8
|
+
module Aws
|
9
|
+
module S3
|
10
|
+
# @api private
|
11
|
+
class FileDownloader
|
12
|
+
|
13
|
+
MIN_CHUNK_SIZE = 5 * 1024 * 1024
|
14
|
+
MAX_PARTS = 10_000
|
15
|
+
THREAD_COUNT = 10
|
16
|
+
|
17
|
+
def initialize(options = {})
|
18
|
+
@client = options[:client] || Client.new
|
19
|
+
end
|
20
|
+
|
21
|
+
# @return [Client]
|
22
|
+
attr_reader :client
|
23
|
+
|
24
|
+
def download(destination, options = {})
|
25
|
+
@path = destination
|
26
|
+
@mode = options[:mode] || 'auto'
|
27
|
+
@thread_count = options[:thread_count] || THREAD_COUNT
|
28
|
+
@chunk_size = options[:chunk_size]
|
29
|
+
@params = {
|
30
|
+
bucket: options[:bucket],
|
31
|
+
key: options[:key],
|
32
|
+
}
|
33
|
+
@params[:version_id] = options[:version_id] if options[:version_id]
|
34
|
+
|
35
|
+
case @mode
|
36
|
+
when 'auto' then multipart_download
|
37
|
+
when 'single_request' then single_request
|
38
|
+
when 'get_range'
|
39
|
+
if @chunk_size
|
40
|
+
resp = @client.head_object(@params)
|
41
|
+
multithreaded_get_by_ranges(construct_chunks(resp.content_length))
|
42
|
+
else
|
43
|
+
msg = 'In :get_range mode, :chunk_size must be provided'
|
44
|
+
raise ArgumentError, msg
|
45
|
+
end
|
46
|
+
else
|
47
|
+
msg = "Invalid mode #{@mode} provided, "\
|
48
|
+
'mode should be :single_request, :get_range or :auto'
|
49
|
+
raise ArgumentError, msg
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def multipart_download
|
56
|
+
resp = @client.head_object(@params.merge(part_number: 1))
|
57
|
+
count = resp.parts_count
|
58
|
+
if count.nil? || count <= 1
|
59
|
+
resp.content_length < MIN_CHUNK_SIZE ?
|
60
|
+
single_request :
|
61
|
+
multithreaded_get_by_ranges(construct_chunks(resp.content_length))
|
62
|
+
else
|
63
|
+
# partNumber is an option
|
64
|
+
resp = @client.head_object(@params)
|
65
|
+
resp.content_length < MIN_CHUNK_SIZE ?
|
66
|
+
single_request :
|
67
|
+
compute_mode(resp.content_length, count)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def compute_mode(file_size, count)
|
72
|
+
chunk_size = compute_chunk(file_size)
|
73
|
+
part_size = (file_size.to_f / count.to_f).ceil
|
74
|
+
if chunk_size < part_size
|
75
|
+
multithreaded_get_by_ranges(construct_chunks(file_size))
|
76
|
+
else
|
77
|
+
multithreaded_get_by_parts(count)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def construct_chunks(file_size)
|
82
|
+
offset = 0
|
83
|
+
default_chunk_size = compute_chunk(file_size)
|
84
|
+
chunks = []
|
85
|
+
while offset <= file_size
|
86
|
+
progress = offset + default_chunk_size
|
87
|
+
chunks << "bytes=#{offset}-#{progress < file_size ? progress : file_size}"
|
88
|
+
offset = progress + 1
|
89
|
+
end
|
90
|
+
chunks
|
91
|
+
end
|
92
|
+
|
93
|
+
def compute_chunk(file_size)
|
94
|
+
if @chunk_size && @chunk_size > file_size
|
95
|
+
raise ArgumentError, ":chunk_size shouldn't exceed total file size."
|
96
|
+
else
|
97
|
+
@chunk_size || [(file_size.to_f / MAX_PARTS).ceil, MIN_CHUNK_SIZE].max.to_i
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def batches(chunks, mode)
|
102
|
+
chunks = (1..chunks) if mode.eql? 'part_number'
|
103
|
+
chunks.each_slice(@thread_count).to_a
|
104
|
+
end
|
105
|
+
|
106
|
+
def multithreaded_get_by_ranges(chunks)
|
107
|
+
thread_batches(chunks, 'range')
|
108
|
+
end
|
109
|
+
|
110
|
+
def multithreaded_get_by_parts(parts)
|
111
|
+
thread_batches(parts, 'part_number')
|
112
|
+
end
|
113
|
+
|
114
|
+
def thread_batches(chunks, param)
|
115
|
+
batches(chunks, param).each do |batch|
|
116
|
+
threads = []
|
117
|
+
batch.each do |chunk|
|
118
|
+
threads << Thread.new do
|
119
|
+
resp = @client.get_object(
|
120
|
+
@params.merge(param.to_sym => chunk)
|
121
|
+
)
|
122
|
+
write(resp)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
threads.each(&:join)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def write(resp)
|
130
|
+
range, _ = resp.content_range.split(' ').last.split('/')
|
131
|
+
head, _ = range.split('-').map {|s| s.to_i}
|
132
|
+
IO.write(@path, resp.body.read, head)
|
133
|
+
end
|
134
|
+
|
135
|
+
def single_request
|
136
|
+
@client.get_object(
|
137
|
+
@params.merge(response_target: @path)
|
138
|
+
)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Aws
|
4
|
+
module S3
|
5
|
+
|
6
|
+
# A utility class that provides an IO-like interface to a portion of a file
|
7
|
+
# on disk.
|
8
|
+
# @api private
|
9
|
+
class FilePart
|
10
|
+
|
11
|
+
# @option options [required, String, Pathname, File, Tempfile] :source
|
12
|
+
# The file to upload.
|
13
|
+
#
|
14
|
+
# @option options [required, Integer] :offset The file part will read
|
15
|
+
# starting at this byte offset.
|
16
|
+
#
|
17
|
+
# @option options [required, Integer] :size The maximum number of bytes to
|
18
|
+
# read from the `:offset`.
|
19
|
+
def initialize(options = {})
|
20
|
+
@source = options[:source]
|
21
|
+
@first_byte = options[:offset]
|
22
|
+
@last_byte = @first_byte + options[:size]
|
23
|
+
@size = options[:size]
|
24
|
+
@file = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [String, Pathname, File, Tempfile]
|
28
|
+
attr_reader :source
|
29
|
+
|
30
|
+
# @return [Integer]
|
31
|
+
attr_reader :first_byte
|
32
|
+
|
33
|
+
# @return [Integer]
|
34
|
+
attr_reader :last_byte
|
35
|
+
|
36
|
+
# @return [Integer]
|
37
|
+
attr_reader :size
|
38
|
+
|
39
|
+
def read(bytes = nil, output_buffer = nil)
|
40
|
+
open_file unless @file
|
41
|
+
read_from_file(bytes, output_buffer)
|
42
|
+
end
|
43
|
+
|
44
|
+
def rewind
|
45
|
+
if @file
|
46
|
+
@file.seek(@first_byte)
|
47
|
+
@position = @first_byte
|
48
|
+
end
|
49
|
+
0
|
50
|
+
end
|
51
|
+
|
52
|
+
def close
|
53
|
+
@file.close if @file
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def open_file
|
59
|
+
@file = File.open(@source, 'rb')
|
60
|
+
rewind
|
61
|
+
end
|
62
|
+
|
63
|
+
def read_from_file(bytes, output_buffer)
|
64
|
+
length = [remaining_bytes, *bytes].min
|
65
|
+
data = @file.read(length, output_buffer)
|
66
|
+
|
67
|
+
@position += data ? data.bytesize : 0
|
68
|
+
|
69
|
+
data.to_s unless bytes && (data.nil? || data.empty?)
|
70
|
+
end
|
71
|
+
|
72
|
+
def remaining_bytes
|
73
|
+
@last_byte - @position
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
|
5
|
+
module Aws
|
6
|
+
module S3
|
7
|
+
# @api private
|
8
|
+
class FileUploader
|
9
|
+
|
10
|
+
FIFTEEN_MEGABYTES = 15 * 1024 * 1024
|
11
|
+
|
12
|
+
# @param [Hash] options
|
13
|
+
# @option options [Client] :client
|
14
|
+
# @option options [Integer] :multipart_threshold (15728640)
|
15
|
+
def initialize(options = {})
|
16
|
+
@options = options
|
17
|
+
@client = options[:client] || Client.new
|
18
|
+
@multipart_threshold = options[:multipart_threshold] ||
|
19
|
+
FIFTEEN_MEGABYTES
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Client]
|
23
|
+
attr_reader :client
|
24
|
+
|
25
|
+
# @return [Integer] Files larger than this in bytes are uploaded
|
26
|
+
# using a {MultipartFileUploader}.
|
27
|
+
attr_reader :multipart_threshold
|
28
|
+
|
29
|
+
# @param [String, Pathname, File, Tempfile] source The file to upload.
|
30
|
+
# @option options [required, String] :bucket The bucket to upload to.
|
31
|
+
# @option options [required, String] :key The key for the object.
|
32
|
+
# @option options [Proc] :progress_callback
|
33
|
+
# A Proc that will be called when each chunk of the upload is sent.
|
34
|
+
# It will be invoked with [bytes_read], [total_sizes]
|
35
|
+
# @return [void]
|
36
|
+
def upload(source, options = {})
|
37
|
+
if File.size(source) >= multipart_threshold
|
38
|
+
MultipartFileUploader.new(@options).upload(source, options)
|
39
|
+
else
|
40
|
+
put_object(source, options)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def open_file(source)
|
47
|
+
if String === source || Pathname === source
|
48
|
+
File.open(source, 'rb') { |file| yield(file) }
|
49
|
+
else
|
50
|
+
yield(source)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def put_object(source, options)
|
55
|
+
if (callback = options.delete(:progress_callback))
|
56
|
+
options[:on_chunk_sent] = single_part_progress(callback)
|
57
|
+
end
|
58
|
+
open_file(source) do |file|
|
59
|
+
@client.put_object(options.merge(body: file))
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def single_part_progress(progress_callback)
|
64
|
+
proc do |_chunk, bytes_read, total_size|
|
65
|
+
progress_callback.call([bytes_read], [total_size])
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,189 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require 'time'
|
5
|
+
require 'openssl'
|
6
|
+
require 'cgi'
|
7
|
+
require 'webrick/httputils'
|
8
|
+
require 'aws-sdk-core/query'
|
9
|
+
|
10
|
+
module Aws
|
11
|
+
module S3
|
12
|
+
# @api private
|
13
|
+
class LegacySigner
|
14
|
+
|
15
|
+
SIGNED_QUERYSTRING_PARAMS = Set.new(%w(
|
16
|
+
|
17
|
+
acl delete cors lifecycle location logging notification partNumber
|
18
|
+
policy requestPayment restore tagging torrent uploadId uploads
|
19
|
+
versionId versioning versions website replication requestPayment
|
20
|
+
accelerate
|
21
|
+
|
22
|
+
response-content-type response-content-language
|
23
|
+
response-expires response-cache-control
|
24
|
+
response-content-disposition response-content-encoding
|
25
|
+
|
26
|
+
))
|
27
|
+
|
28
|
+
def self.sign(context)
|
29
|
+
new(
|
30
|
+
context.config.credentials,
|
31
|
+
context.params,
|
32
|
+
context.config.force_path_style
|
33
|
+
).sign(context.http_request)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param [CredentialProvider] credentials
|
37
|
+
def initialize(credentials, params, force_path_style)
|
38
|
+
@credentials = credentials.credentials
|
39
|
+
@params = Query::ParamList.new
|
40
|
+
params.each_pair do |param_name, param_value|
|
41
|
+
@params.set(param_name, param_value)
|
42
|
+
end
|
43
|
+
@force_path_style = force_path_style
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_reader :credentials, :params
|
47
|
+
|
48
|
+
def sign(request)
|
49
|
+
if token = credentials.session_token
|
50
|
+
request.headers["X-Amz-Security-Token"] = token
|
51
|
+
end
|
52
|
+
request.headers['Authorization'] = authorization(request)
|
53
|
+
end
|
54
|
+
|
55
|
+
def authorization(request)
|
56
|
+
"AWS #{credentials.access_key_id}:#{signature(request)}"
|
57
|
+
end
|
58
|
+
|
59
|
+
def signature(request)
|
60
|
+
string_to_sign = string_to_sign(request)
|
61
|
+
signature = digest(credentials.secret_access_key, string_to_sign)
|
62
|
+
uri_escape(signature)
|
63
|
+
end
|
64
|
+
|
65
|
+
def digest(secret, string_to_sign)
|
66
|
+
Base64.encode64(hmac(secret, string_to_sign)).strip
|
67
|
+
end
|
68
|
+
|
69
|
+
def hmac(key, value)
|
70
|
+
OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha1'), key, value)
|
71
|
+
end
|
72
|
+
|
73
|
+
# From the S3 developer guide:
|
74
|
+
#
|
75
|
+
# StringToSign =
|
76
|
+
# HTTP-Verb ` "\n" `
|
77
|
+
# content-md5 ` "\n" `
|
78
|
+
# content-type ` "\n" `
|
79
|
+
# date ` "\n" `
|
80
|
+
# CanonicalizedAmzHeaders + CanonicalizedResource;
|
81
|
+
#
|
82
|
+
def string_to_sign(request)
|
83
|
+
[
|
84
|
+
request.http_method,
|
85
|
+
request.headers.values_at('Content-Md5', 'Content-Type').join("\n"),
|
86
|
+
signing_string_date(request),
|
87
|
+
canonicalized_headers(request),
|
88
|
+
canonicalized_resource(request.endpoint),
|
89
|
+
].flatten.compact.join("\n")
|
90
|
+
end
|
91
|
+
|
92
|
+
def signing_string_date(request)
|
93
|
+
# if a date is provided via x-amz-date then we should omit the
|
94
|
+
# Date header from the signing string (should appear as a blank line)
|
95
|
+
if request.headers.detect{|k,v| k.to_s =~ /^x-amz-date$/i }
|
96
|
+
''
|
97
|
+
else
|
98
|
+
request.headers['Date'] = Time.now.httpdate
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# CanonicalizedAmzHeaders
|
103
|
+
#
|
104
|
+
# See the developer guide for more information on how this element
|
105
|
+
# is generated.
|
106
|
+
#
|
107
|
+
def canonicalized_headers(request)
|
108
|
+
x_amz = request.headers.select{|k, v| k =~ /^x-amz-/i }
|
109
|
+
x_amz = x_amz.collect{|k, v| [k.downcase, v] }
|
110
|
+
x_amz = x_amz.sort_by{|k, v| k }
|
111
|
+
x_amz = x_amz.collect{|k, v| "#{k}:#{v.to_s.strip}" }.join("\n")
|
112
|
+
x_amz == '' ? nil : x_amz
|
113
|
+
end
|
114
|
+
|
115
|
+
# From the S3 developer guide
|
116
|
+
#
|
117
|
+
# CanonicalizedResource =
|
118
|
+
# [ "/" ` Bucket ] `
|
119
|
+
# <HTTP-Request-URI, protocol name up to the querystring> +
|
120
|
+
# [ sub-resource, if present. e.g. "?acl", "?location",
|
121
|
+
# "?logging", or "?torrent"];
|
122
|
+
#
|
123
|
+
# @api private
|
124
|
+
def canonicalized_resource(endpoint)
|
125
|
+
|
126
|
+
parts = []
|
127
|
+
|
128
|
+
# virtual hosted-style requests require the hostname to appear
|
129
|
+
# in the canonicalized resource prefixed by a forward slash.
|
130
|
+
if bucket = params[:bucket]
|
131
|
+
bucket = bucket.value
|
132
|
+
ssl = endpoint.scheme == 'https'
|
133
|
+
if Plugins::BucketDns.dns_compatible?(bucket, ssl) && !@force_path_style
|
134
|
+
parts << "/#{bucket}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# append the path name (no querystring)
|
139
|
+
parts << endpoint.path
|
140
|
+
|
141
|
+
# lastly any sub resource querystring params need to be appened
|
142
|
+
# in lexigraphical ordered joined by '&' and prefixed by '?'
|
143
|
+
params = signed_querystring_params(endpoint)
|
144
|
+
|
145
|
+
unless params.empty?
|
146
|
+
parts << '?'
|
147
|
+
parts << params.sort.collect{|p| p.to_s }.join('&')
|
148
|
+
end
|
149
|
+
|
150
|
+
parts.join
|
151
|
+
end
|
152
|
+
|
153
|
+
def signed_querystring_params(endpoint)
|
154
|
+
endpoint.query.to_s.split('&').select do |p|
|
155
|
+
SIGNED_QUERYSTRING_PARAMS.include?(p.split('=')[0])
|
156
|
+
end.map { |p| CGI.unescape(p) }
|
157
|
+
end
|
158
|
+
|
159
|
+
def uri_escape(s)
|
160
|
+
|
161
|
+
#URI.escape(s)
|
162
|
+
|
163
|
+
# URI.escape is deprecated, replacing it with escape from webrick
|
164
|
+
# to squelch the massive number of warnings generated from Ruby.
|
165
|
+
# The following script was used to determine the differences
|
166
|
+
# between the various escape methods available. The webrick
|
167
|
+
# escape only had two differences and it is available in the
|
168
|
+
# standard lib.
|
169
|
+
#
|
170
|
+
# (0..255).each {|c|
|
171
|
+
# s = [c].pack("C")
|
172
|
+
# e = [
|
173
|
+
# CGI.escape(s),
|
174
|
+
# ERB::Util.url_encode(s),
|
175
|
+
# URI.encode_www_form_component(s),
|
176
|
+
# WEBrick::HTTPUtils.escape_form(s),
|
177
|
+
# WEBrick::HTTPUtils.escape(s),
|
178
|
+
# URI.escape(s),
|
179
|
+
# ]
|
180
|
+
# next if e.uniq.length == 1
|
181
|
+
# puts("%5s %5s %5s %5s %5s %5s %5s" % ([s.inspect] + e))
|
182
|
+
# }
|
183
|
+
#
|
184
|
+
WEBrick::HTTPUtils.escape(s).gsub('%5B', '[').gsub('%5D', ']')
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|