aws-sdk-s3 1.75.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|