aws-sdk 1.6.2 → 1.6.3
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/aws/core.rb +13 -2
- data/lib/aws/core/autoloader.rb +1 -1
- data/lib/aws/core/client.rb +69 -30
- data/lib/aws/core/configuration.rb +12 -1
- data/lib/aws/core/http/handler.rb +28 -16
- data/lib/aws/core/http/net_http_handler.rb +31 -11
- data/lib/aws/core/http/request.rb +52 -16
- data/lib/aws/core/http/response.rb +20 -16
- data/lib/aws/core/indifferent_hash.rb +14 -14
- data/lib/aws/core/query_client.rb +1 -0
- data/lib/aws/core/response.rb +32 -14
- data/lib/aws/core/signature/version_2.rb +1 -0
- data/lib/aws/core/signature/version_4.rb +16 -16
- data/lib/aws/dynamo_db/client.rb +2 -2
- data/lib/aws/dynamo_db/request.rb +0 -6
- data/lib/aws/ec2/security_group/ip_permission.rb +4 -1
- data/lib/aws/rails.rb +10 -10
- data/lib/aws/s3.rb +44 -29
- data/lib/aws/s3/bucket.rb +171 -6
- data/lib/aws/s3/cipher_io.rb +119 -0
- data/lib/aws/s3/client.rb +75 -45
- data/lib/aws/s3/config.rb +6 -0
- data/lib/aws/s3/data_options.rb +136 -49
- data/lib/aws/s3/encryption_utils.rb +144 -0
- data/lib/aws/s3/errors.rb +14 -0
- data/lib/aws/s3/multipart_upload.rb +7 -4
- data/lib/aws/s3/object_collection.rb +2 -2
- data/lib/aws/s3/policy.rb +1 -1
- data/lib/aws/s3/request.rb +21 -33
- data/lib/aws/s3/s3_object.rb +797 -237
- data/lib/aws/simple_email_service/request.rb +0 -2
- data/lib/aws/simple_workflow/request.rb +0 -3
- data/lib/net/http/connection_pool.rb +63 -75
- data/lib/net/http/connection_pool/connection.rb +69 -15
- data/lib/net/http/connection_pool/session.rb +39 -6
- metadata +4 -2
data/lib/aws/core.rb
CHANGED
@@ -69,7 +69,7 @@ require 'aws/core/autoloader'
|
|
69
69
|
module AWS
|
70
70
|
|
71
71
|
# Current version of the AWS SDK for Ruby
|
72
|
-
VERSION = "1.6.
|
72
|
+
VERSION = "1.6.3"
|
73
73
|
|
74
74
|
register_autoloads(self) do
|
75
75
|
autoload :Errors, 'errors'
|
@@ -299,7 +299,7 @@ module AWS
|
|
299
299
|
# +true+, requests will always use path style. This can be useful
|
300
300
|
# for testing environments.
|
301
301
|
#
|
302
|
-
# @option options [Integer] :s3_multipart_max_parts (
|
302
|
+
# @option options [Integer] :s3_multipart_max_parts (10000) The maximum
|
303
303
|
# number of parts to split a file into when uploading in parts to S3.
|
304
304
|
#
|
305
305
|
# @option options [Integer] :s3_multipart_threshold (16777216) When
|
@@ -328,6 +328,17 @@ module AWS
|
|
328
328
|
# * {S3::S3Object#presigned_post}
|
329
329
|
# * {S3::Bucket#presigned_post}
|
330
330
|
#
|
331
|
+
# @option options [OpenSSL::PKey::RSA, String] :s3_encryption_key (nil)
|
332
|
+
# If this is set, AWS::S3::S3Object #read and #write methods will always
|
333
|
+
# perform client-side encryption with this key. The key can be overridden
|
334
|
+
# at runtime by using the :encryption_key option. A value of nil
|
335
|
+
# means that client-side encryption will not be used.
|
336
|
+
#
|
337
|
+
# @option options [Symbol] :s3_encryption_materials_location (:metadata)
|
338
|
+
# When set to +:instruction_file+, AWS::S3::S3Object will store
|
339
|
+
# encryption materials in a seperate object, instead of the object
|
340
|
+
# metadata.
|
341
|
+
#
|
331
342
|
# @option options [String] :simple_db_endpoint ('sdb.amazonaws.com')
|
332
343
|
# The service endpoint for Amazon SimpleDB.
|
333
344
|
#
|
data/lib/aws/core/autoloader.rb
CHANGED
data/lib/aws/core/client.rb
CHANGED
@@ -21,6 +21,11 @@ module AWS
|
|
21
21
|
# Base client class for all of the Amazon AWS service clients.
|
22
22
|
class Client
|
23
23
|
|
24
|
+
# Raised when a request failed due to a networking issue (e.g.
|
25
|
+
# EOFError, IOError, Errno::ECONNRESET, Errno::EPIPE,
|
26
|
+
# Timeout::Error, etc)
|
27
|
+
class NetworkError < StandardError; end
|
28
|
+
|
24
29
|
extend Naming
|
25
30
|
|
26
31
|
# @private
|
@@ -155,6 +160,19 @@ module AWS
|
|
155
160
|
response
|
156
161
|
end
|
157
162
|
|
163
|
+
# Logs the warning to the configured logger, otherwise to stderr.
|
164
|
+
# @param [String] warning
|
165
|
+
# @return [nil]
|
166
|
+
def log_warning warning
|
167
|
+
message = '[aws-sdk-gem-warning] ' + warning
|
168
|
+
if config.logger
|
169
|
+
config.logger.warn(message)
|
170
|
+
else
|
171
|
+
$stderr.puts(message)
|
172
|
+
end
|
173
|
+
nil
|
174
|
+
end
|
175
|
+
|
158
176
|
protected
|
159
177
|
|
160
178
|
def new_request
|
@@ -204,13 +222,30 @@ module AWS
|
|
204
222
|
|
205
223
|
end
|
206
224
|
|
207
|
-
def make_sync_request response
|
225
|
+
def make_sync_request response, &read_block
|
208
226
|
retry_server_errors do
|
209
227
|
|
210
|
-
response.http_response =
|
211
|
-
|
228
|
+
response.http_response = Http::Response.new
|
229
|
+
|
230
|
+
@http_handler.handle(
|
231
|
+
response.http_request,
|
232
|
+
response.http_response,
|
233
|
+
&read_block)
|
234
|
+
|
235
|
+
if
|
236
|
+
block_given? and
|
237
|
+
response.http_response.status < 300 and
|
238
|
+
response.http_response.body
|
239
|
+
then
|
240
|
+
|
241
|
+
msg = ":http_handler read the entire http response body into "
|
242
|
+
msg << "memory, it should have instead yielded chunks"
|
243
|
+
log_warning(msg)
|
244
|
+
|
245
|
+
# go ahead and yield the body on behalf of the handler
|
246
|
+
yield(response.http_response.body)
|
212
247
|
|
213
|
-
|
248
|
+
end
|
214
249
|
|
215
250
|
populate_error(response)
|
216
251
|
response.signal_success unless response.error
|
@@ -227,7 +262,6 @@ module AWS
|
|
227
262
|
while should_retry?(response)
|
228
263
|
break if sleeps.empty?
|
229
264
|
Kernel.sleep(sleeps.shift)
|
230
|
-
# rebuild the request to get a fresh signature
|
231
265
|
rebuild_http_request(response)
|
232
266
|
response = yield
|
233
267
|
end
|
@@ -256,8 +290,16 @@ module AWS
|
|
256
290
|
end
|
257
291
|
|
258
292
|
def should_retry? response
|
293
|
+
if retryable_error?(response)
|
294
|
+
response.safe_to_retry?
|
295
|
+
else
|
296
|
+
false
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def retryable_error? response
|
259
301
|
expired_credentials?(response) or
|
260
|
-
response.
|
302
|
+
response.network_error? or
|
261
303
|
response.throttled? or
|
262
304
|
response.error.kind_of?(Errors::ServerError)
|
263
305
|
end
|
@@ -303,7 +345,7 @@ module AWS
|
|
303
345
|
end
|
304
346
|
|
305
347
|
# Logs the response to the configured logger.
|
306
|
-
# @param [
|
348
|
+
# @param [Response] response
|
307
349
|
# @return [nil]
|
308
350
|
def log_response response
|
309
351
|
if config.logger
|
@@ -336,10 +378,10 @@ module AWS
|
|
336
378
|
]
|
337
379
|
|
338
380
|
case
|
339
|
-
when response.
|
340
|
-
when error_code
|
341
|
-
when status >= 500
|
342
|
-
when status >= 300
|
381
|
+
when response.network_error? then NetworkError.new
|
382
|
+
when error_code then error_class(error_code).new(*error_args)
|
383
|
+
when status >= 500 then Errors::ServerError.new(*error_args)
|
384
|
+
when status >= 300 then Errors::ClientError.new(*error_args)
|
343
385
|
else nil # no error
|
344
386
|
end
|
345
387
|
|
@@ -371,21 +413,25 @@ module AWS
|
|
371
413
|
AWS.const_get(self.class.to_s[/(\w+)::Client/, 1])::Errors
|
372
414
|
end
|
373
415
|
|
374
|
-
def client_request name, options, &
|
416
|
+
def client_request name, options, &read_block
|
375
417
|
return_or_raise(options) do
|
376
418
|
log_client_request(options) do
|
377
419
|
|
378
420
|
if config.stub_requests?
|
379
421
|
|
380
422
|
response = stub_for(name)
|
381
|
-
response.http_request = build_request(name, options
|
423
|
+
response.http_request = build_request(name, options)
|
382
424
|
response.request_options = options
|
383
425
|
response
|
384
426
|
|
385
427
|
else
|
386
428
|
|
387
429
|
client = self
|
388
|
-
|
430
|
+
|
431
|
+
response = new_response do
|
432
|
+
client.send(:build_request, name, options)
|
433
|
+
end
|
434
|
+
|
389
435
|
response.request_type = name
|
390
436
|
response.request_options = options
|
391
437
|
|
@@ -399,8 +445,8 @@ module AWS
|
|
399
445
|
else
|
400
446
|
# process the http request
|
401
447
|
options[:async] ?
|
402
|
-
make_async_request(response) :
|
403
|
-
make_sync_request(response)
|
448
|
+
make_async_request(response, &read_block) :
|
449
|
+
make_sync_request(response, &read_block)
|
404
450
|
|
405
451
|
# process the http response
|
406
452
|
response.on_success do
|
@@ -424,7 +470,7 @@ module AWS
|
|
424
470
|
self.class::CACHEABLE_REQUESTS.include?(name)
|
425
471
|
end
|
426
472
|
|
427
|
-
def build_request
|
473
|
+
def build_request name, options
|
428
474
|
|
429
475
|
# we dont want to pass the async option to the configure block
|
430
476
|
opts = options.dup
|
@@ -445,7 +491,7 @@ module AWS
|
|
445
491
|
http_request.ssl_ca_file = config.ssl_ca_file if config.ssl_ca_file
|
446
492
|
http_request.ssl_ca_path = config.ssl_ca_path if config.ssl_ca_path
|
447
493
|
|
448
|
-
send("configure_#{name}_request", http_request, opts
|
494
|
+
send("configure_#{name}_request", http_request, opts)
|
449
495
|
|
450
496
|
http_request.headers["user-agent"] = user_agent_string
|
451
497
|
http_request.add_authorization!(credential_provider)
|
@@ -474,16 +520,18 @@ module AWS
|
|
474
520
|
#
|
475
521
|
def self.add_client_request_method method_name, options = {}, &block
|
476
522
|
|
477
|
-
|
523
|
+
operations << method_name
|
478
524
|
|
479
525
|
ClientRequestMethodBuilder.new(self, method_name, &block)
|
480
526
|
|
481
|
-
|
527
|
+
method_def = <<-METHOD
|
482
528
|
def #{method_name}(*args, &block)
|
483
529
|
options = args.first ? args.first : {}
|
484
530
|
client_request(#{method_name.inspect}, options, &block)
|
485
531
|
end
|
486
|
-
|
532
|
+
METHOD
|
533
|
+
|
534
|
+
module_eval(method_def)
|
487
535
|
|
488
536
|
end
|
489
537
|
|
@@ -518,15 +566,6 @@ module AWS
|
|
518
566
|
def configure_request options = {}, &block
|
519
567
|
name = "configure_#{@method_name}_request"
|
520
568
|
MetaUtils.class_extend_method(@client_class, name, &block)
|
521
|
-
if block.arity == 3
|
522
|
-
m = Module.new
|
523
|
-
m.module_eval(<<-END)
|
524
|
-
def #{name}(req, options, &block)
|
525
|
-
super(req, options, block)
|
526
|
-
end
|
527
|
-
END
|
528
|
-
@client_class.send(:include, m)
|
529
|
-
end
|
530
569
|
end
|
531
570
|
|
532
571
|
def process_response &block
|
@@ -128,7 +128,7 @@ module AWS
|
|
128
128
|
# +true+, requests will always use path style. This can be useful
|
129
129
|
# for testing environments.
|
130
130
|
#
|
131
|
-
# @attr_reader [Integer] s3_multipart_max_parts (
|
131
|
+
# @attr_reader [Integer] s3_multipart_max_parts (10000)
|
132
132
|
# The maximum number of parts to split a file into when uploading
|
133
133
|
# in parts to S3.
|
134
134
|
#
|
@@ -163,6 +163,17 @@ module AWS
|
|
163
163
|
#
|
164
164
|
# s3 = AWS::S3.new(:s3_server_side_encryption => :aes256)
|
165
165
|
#
|
166
|
+
# @attr_reader [OpenSSL::PKey::RSA, String] s3_encryption_key
|
167
|
+
# If this is set, AWS::S3::S3Object #read and #write methods will always
|
168
|
+
# perform client-side encryption with this key. The key can be overridden
|
169
|
+
# at runtime by using the :encryption_key option. A value of nil
|
170
|
+
# means that client-side encryption will not be used.
|
171
|
+
#
|
172
|
+
# @attr_reader [Symbol] s3_encryption_materials_location
|
173
|
+
# When set to +:instruction_file+, AWS::S3::S3Object will store
|
174
|
+
# encryption materials in a seperate object, instead of the object
|
175
|
+
# metadata.
|
176
|
+
#
|
166
177
|
# @attr_reader [String] simple_db_endpoint ('sdb.amazonaws.com')
|
167
178
|
# The service endpoint for Amazon SimpleDB.
|
168
179
|
#
|
@@ -14,27 +14,39 @@
|
|
14
14
|
module AWS
|
15
15
|
module Core
|
16
16
|
module Http
|
17
|
-
|
17
|
+
|
18
18
|
# @private
|
19
19
|
class Handler
|
20
|
-
|
20
|
+
|
21
21
|
attr_reader :base
|
22
|
-
|
22
|
+
|
23
23
|
def initialize(base, &block)
|
24
24
|
@base = base
|
25
25
|
if base.respond_to?(:handle)
|
26
|
-
|
27
|
-
unless block.arity
|
28
|
-
raise ArgumentError, 'passed block must accept 2 arguments'
|
26
|
+
|
27
|
+
unless [2,3].include?(block.arity)
|
28
|
+
raise ArgumentError, 'passed block must accept 2 or 3 arguments'
|
29
29
|
end
|
30
|
+
|
30
31
|
MetaUtils.extend_method(self, :handle, &block)
|
31
|
-
|
32
|
+
|
33
|
+
if block.arity == 3
|
34
|
+
m = Module.new do
|
35
|
+
eval(<<-DEF)
|
36
|
+
def handle req, resp, &read_block
|
37
|
+
super(req, resp, read_block)
|
38
|
+
end
|
39
|
+
DEF
|
40
|
+
end
|
41
|
+
self.extend(m)
|
42
|
+
end
|
43
|
+
|
32
44
|
elsif base.respond_to?(:handle_async)
|
33
|
-
|
45
|
+
|
34
46
|
unless block.arity == 3
|
35
47
|
raise ArgumentError, 'passed block must accept 3 arguments'
|
36
48
|
end
|
37
|
-
|
49
|
+
|
38
50
|
MetaUtils.extend_method(self, :handle_async) do |req, resp, handle|
|
39
51
|
@base.handle_async(req, resp, handle)
|
40
52
|
end
|
@@ -44,16 +56,16 @@ module AWS
|
|
44
56
|
end
|
45
57
|
define_method(:handle_async, &block)
|
46
58
|
end
|
47
|
-
|
59
|
+
|
48
60
|
else
|
49
61
|
raise ArgumentError, 'base must respond to #handle or #handle_async'
|
50
62
|
end
|
51
63
|
end
|
52
|
-
|
53
|
-
def handle(request, http_response)
|
54
|
-
@base.handle(request, http_response)
|
64
|
+
|
65
|
+
def handle(request, http_response, &read_block)
|
66
|
+
@base.handle(request, http_response, &read_block)
|
55
67
|
end
|
56
|
-
|
68
|
+
|
57
69
|
def handle_async(request, http_response, handle)
|
58
70
|
Thread.new do
|
59
71
|
begin
|
@@ -65,12 +77,12 @@ module AWS
|
|
65
77
|
end
|
66
78
|
end
|
67
79
|
end
|
68
|
-
|
80
|
+
|
69
81
|
def sleep_with_callback seconds, &block
|
70
82
|
Kernel.sleep(seconds)
|
71
83
|
yield
|
72
84
|
end
|
73
|
-
|
85
|
+
|
74
86
|
end
|
75
87
|
end
|
76
88
|
end
|
@@ -25,6 +25,18 @@ module AWS
|
|
25
25
|
#
|
26
26
|
class NetHttpHandler
|
27
27
|
|
28
|
+
# @private
|
29
|
+
NETWORK_ERRORS = [
|
30
|
+
EOFError,
|
31
|
+
IOError,
|
32
|
+
Errno::ECONNABORTED,
|
33
|
+
Errno::ECONNRESET,
|
34
|
+
Errno::EPIPE,
|
35
|
+
Errno::EINVAL,
|
36
|
+
Timeout::Error,
|
37
|
+
Errno::ETIMEDOUT,
|
38
|
+
]
|
39
|
+
|
28
40
|
# (see Net::HTTP::ConnectionPool.new)
|
29
41
|
def initialize options = {}
|
30
42
|
@pool = Net::HTTP::ConnectionPool.new(options)
|
@@ -39,7 +51,7 @@ module AWS
|
|
39
51
|
# @param [Request] request
|
40
52
|
# @param [Response] response
|
41
53
|
# @return [nil]
|
42
|
-
def handle request, response
|
54
|
+
def handle request, response, &read_block
|
43
55
|
|
44
56
|
options = {}
|
45
57
|
options[:port] = request.port
|
@@ -49,17 +61,25 @@ module AWS
|
|
49
61
|
options[:ssl_ca_file] = request.ssl_ca_file if request.ssl_ca_file
|
50
62
|
options[:ssl_ca_path] = request.ssl_ca_path if request.ssl_ca_path
|
51
63
|
|
52
|
-
connection = pool.connection_for(request.host, options)
|
53
|
-
connection.read_timeout = request.read_timeout
|
54
|
-
|
55
64
|
begin
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
65
|
+
|
66
|
+
connection = pool.connection_for(request.host, options)
|
67
|
+
connection.read_timeout = request.read_timeout
|
68
|
+
|
69
|
+
connection.request(build_net_http_request(request)) do |http_resp|
|
70
|
+
response.status = http_resp.code.to_i
|
71
|
+
response.headers = http_resp.to_hash
|
72
|
+
if block_given? and response.status < 300
|
73
|
+
http_resp.read_body(&read_block)
|
74
|
+
else
|
75
|
+
response.body = http_resp.read_body
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
rescue *NETWORK_ERRORS
|
80
|
+
response.network_error = true
|
62
81
|
end
|
82
|
+
|
63
83
|
nil
|
64
84
|
|
65
85
|
end
|
@@ -90,7 +110,7 @@ module AWS
|
|
90
110
|
end
|
91
111
|
|
92
112
|
net_http_req = request_class.new(request.uri, headers)
|
93
|
-
net_http_req.
|
113
|
+
net_http_req.body_stream = request.body_stream
|
94
114
|
net_http_req
|
95
115
|
|
96
116
|
end
|
@@ -36,7 +36,7 @@ module AWS
|
|
36
36
|
# 60 seconds.
|
37
37
|
attr_accessor :default_read_timeout
|
38
38
|
|
39
|
-
# @return [String]
|
39
|
+
# @return [String] hostname of the request
|
40
40
|
attr_accessor :host
|
41
41
|
|
42
42
|
# @return [Integer] Returns the port number this request will be
|
@@ -47,7 +47,7 @@ module AWS
|
|
47
47
|
# 'POST', 'HEAD' or 'DELETE'). Defaults to 'POST'.
|
48
48
|
attr_accessor :http_method
|
49
49
|
|
50
|
-
# @return [
|
50
|
+
# @return [CaseInsensitiveHash] request headers
|
51
51
|
attr_accessor :headers
|
52
52
|
|
53
53
|
# @return [String] Returns the request URI (path + querystring).
|
@@ -57,24 +57,18 @@ module AWS
|
|
57
57
|
# to be populated for requests against signature v4 endpoints.
|
58
58
|
attr_accessor :region
|
59
59
|
|
60
|
-
# @return [String]
|
60
|
+
# @return [String] Returns the AWS access key ID used to authorize the
|
61
|
+
# request.
|
61
62
|
# @private
|
62
63
|
attr_accessor :access_key_id
|
63
64
|
|
64
|
-
# @private
|
65
65
|
# @return [Array<Param>] Returns an array of request params. Requests
|
66
66
|
# that use signature version 2 add params to the request and then
|
67
67
|
# sign those before building the {#body}. Normally the {#body}
|
68
68
|
# should be set directly with the HTTP payload.
|
69
|
+
# @private
|
69
70
|
attr_accessor :params
|
70
71
|
|
71
|
-
# @return [String] Returns the HTTP request payload (body).
|
72
|
-
attr_accessor :body
|
73
|
-
|
74
|
-
# @return [String] Returns the AWS access key ID used to authorize the
|
75
|
-
# request.
|
76
|
-
attr_accessor :access_key_id
|
77
|
-
|
78
72
|
# @return [String] The name of the service for Signature v4 signing.
|
79
73
|
# This does not always match the ruby name (e.g.
|
80
74
|
# simple_email_service and ses do not match).
|
@@ -125,11 +119,6 @@ module AWS
|
|
125
119
|
default_read_timeout
|
126
120
|
end
|
127
121
|
|
128
|
-
# @return [String,nil] Returns the request body (payload).
|
129
|
-
def body
|
130
|
-
@body || url_encoded_params
|
131
|
-
end
|
132
|
-
|
133
122
|
# @return [String] Returns the HTTP request path.
|
134
123
|
def path
|
135
124
|
uri.split(/\?/)[0]
|
@@ -167,6 +156,53 @@ module AWS
|
|
167
156
|
params.empty? ? nil : params.sort.collect(&:encoded).join('&')
|
168
157
|
end
|
169
158
|
|
159
|
+
# @param [String] body
|
160
|
+
def body= body
|
161
|
+
@body = body
|
162
|
+
if body
|
163
|
+
headers['content-length'] = body.size if body
|
164
|
+
else
|
165
|
+
headers.delete('content-length')
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# @note Calling #body on a request with a #body_stream
|
170
|
+
# will cause the entire stream to be read into memory.
|
171
|
+
# @return [String,nil] Returns the request body.
|
172
|
+
def body
|
173
|
+
if @body
|
174
|
+
@body
|
175
|
+
elsif @body_stream
|
176
|
+
@body = @body_stream.read
|
177
|
+
if @body_stream.respond_to?(:rewind)
|
178
|
+
@body_stream.rewind
|
179
|
+
else
|
180
|
+
@body_stream = StringIO.new(@body)
|
181
|
+
end
|
182
|
+
@body
|
183
|
+
else
|
184
|
+
nil
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Sets the request body as an IO object that will be streamed.
|
189
|
+
# @note You must also set the #headers['content-length']
|
190
|
+
# @param [IO] stream An object that responds to #read and #eof.
|
191
|
+
def body_stream= stream
|
192
|
+
@body_stream = stream
|
193
|
+
end
|
194
|
+
|
195
|
+
# @return [IO,nil]
|
196
|
+
def body_stream
|
197
|
+
if @body_stream
|
198
|
+
@body_stream
|
199
|
+
elsif @body
|
200
|
+
StringIO.new(@body)
|
201
|
+
else
|
202
|
+
nil
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
170
206
|
# @private
|
171
207
|
class CaseInsensitiveHash < Hash
|
172
208
|
|