escher 0.2.1 → 0.3.1
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 +4 -4
- data/escher.gemspec +5 -1
- data/lib/escher/auth.rb +341 -0
- data/lib/escher/request/base.rb +29 -0
- data/lib/escher/request/factory.rb +24 -0
- data/lib/escher/request/hash_request.rb +67 -0
- data/lib/escher/request/legacy_request.rb +157 -0
- data/lib/escher/request/rack_request.rb +42 -0
- data/lib/escher/version.rb +2 -2
- data/lib/escher.rb +6 -2
- data/spec/escher/auth_spec.rb +408 -0
- data/spec/escher/request/factory_spec.rb +20 -0
- data/spec/escher/request/hash_request_spec.rb +121 -0
- data/spec/escher/request/rack_request_spec.rb +114 -0
- metadata +45 -21
- data/lib/escher/base.rb +0 -324
- data/lib/escher/request.rb +0 -83
- data/spec/escher_spec.rb +0 -358
metadata
CHANGED
@@ -1,69 +1,83 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: escher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andras Barthazi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-
|
11
|
+
date: 2014-11-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- -
|
17
|
+
- - ~>
|
18
18
|
- !ruby/object:Gem::Version
|
19
19
|
version: '1.6'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- -
|
24
|
+
- - ~>
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.6'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ~>
|
32
32
|
- !ruby/object:Gem::Version
|
33
33
|
version: '10'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ~>
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '10'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rspec
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- -
|
45
|
+
- - ~>
|
46
46
|
- !ruby/object:Gem::Version
|
47
47
|
version: '2'
|
48
48
|
type: :development
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- -
|
52
|
+
- - ~>
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '2'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rack
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: addressable
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
58
72
|
requirements:
|
59
|
-
- -
|
73
|
+
- - ~>
|
60
74
|
- !ruby/object:Gem::Version
|
61
75
|
version: '2.3'
|
62
76
|
type: :runtime
|
63
77
|
prerelease: false
|
64
78
|
version_requirements: !ruby/object:Gem::Requirement
|
65
79
|
requirements:
|
66
|
-
- -
|
80
|
+
- - ~>
|
67
81
|
- !ruby/object:Gem::Version
|
68
82
|
version: '2.3'
|
69
83
|
description: Escher helps you creating secure HTTP requests (for APIs) by signing
|
@@ -74,16 +88,20 @@ executables: []
|
|
74
88
|
extensions: []
|
75
89
|
extra_rdoc_files: []
|
76
90
|
files:
|
77
|
-
-
|
78
|
-
-
|
91
|
+
- .gitignore
|
92
|
+
- .travis.yml
|
79
93
|
- Gemfile
|
80
94
|
- LICENSE
|
81
95
|
- README.md
|
82
96
|
- Rakefile
|
83
97
|
- escher.gemspec
|
84
98
|
- lib/escher.rb
|
85
|
-
- lib/escher/
|
86
|
-
- lib/escher/request.rb
|
99
|
+
- lib/escher/auth.rb
|
100
|
+
- lib/escher/request/base.rb
|
101
|
+
- lib/escher/request/factory.rb
|
102
|
+
- lib/escher/request/hash_request.rb
|
103
|
+
- lib/escher/request/legacy_request.rb
|
104
|
+
- lib/escher/request/rack_request.rb
|
87
105
|
- lib/escher/version.rb
|
88
106
|
- spec/aws4_testsuite/get-header-key-duplicate.authz
|
89
107
|
- spec/aws4_testsuite/get-header-key-duplicate.creq
|
@@ -261,9 +279,12 @@ files:
|
|
261
279
|
- spec/emarsys_testsuite/post-header-value-spaces.req
|
262
280
|
- spec/emarsys_testsuite/post-header-value-spaces.sreq
|
263
281
|
- spec/emarsys_testsuite/post-header-value-spaces.sts
|
264
|
-
- spec/
|
282
|
+
- spec/escher/auth_spec.rb
|
283
|
+
- spec/escher/request/factory_spec.rb
|
284
|
+
- spec/escher/request/hash_request_spec.rb
|
285
|
+
- spec/escher/request/rack_request_spec.rb
|
265
286
|
- spec/spec_helper.rb
|
266
|
-
homepage:
|
287
|
+
homepage: http://escherauth.io/
|
267
288
|
licenses:
|
268
289
|
- MIT
|
269
290
|
metadata: {}
|
@@ -273,17 +294,17 @@ require_paths:
|
|
273
294
|
- lib
|
274
295
|
required_ruby_version: !ruby/object:Gem::Requirement
|
275
296
|
requirements:
|
276
|
-
- -
|
297
|
+
- - '>='
|
277
298
|
- !ruby/object:Gem::Version
|
278
|
-
version: '
|
299
|
+
version: '1.9'
|
279
300
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
280
301
|
requirements:
|
281
|
-
- -
|
302
|
+
- - '>='
|
282
303
|
- !ruby/object:Gem::Version
|
283
304
|
version: '0'
|
284
305
|
requirements: []
|
285
306
|
rubyforge_project:
|
286
|
-
rubygems_version: 2.
|
307
|
+
rubygems_version: 2.0.14
|
287
308
|
signing_key:
|
288
309
|
specification_version: 4
|
289
310
|
summary: Library for HTTP request signing (Ruby implementation)
|
@@ -464,5 +485,8 @@ test_files:
|
|
464
485
|
- spec/emarsys_testsuite/post-header-value-spaces.req
|
465
486
|
- spec/emarsys_testsuite/post-header-value-spaces.sreq
|
466
487
|
- spec/emarsys_testsuite/post-header-value-spaces.sts
|
467
|
-
- spec/
|
488
|
+
- spec/escher/auth_spec.rb
|
489
|
+
- spec/escher/request/factory_spec.rb
|
490
|
+
- spec/escher/request/hash_request_spec.rb
|
491
|
+
- spec/escher/request/rack_request_spec.rb
|
468
492
|
- spec/spec_helper.rb
|
data/lib/escher/base.rb
DELETED
@@ -1,324 +0,0 @@
|
|
1
|
-
require 'escher/version'
|
2
|
-
|
3
|
-
require 'time'
|
4
|
-
require 'digest'
|
5
|
-
require 'pathname'
|
6
|
-
require 'addressable/uri'
|
7
|
-
|
8
|
-
class EscherError < RuntimeError
|
9
|
-
end
|
10
|
-
|
11
|
-
class Escher
|
12
|
-
|
13
|
-
def initialize(credential_scope, options)
|
14
|
-
@credential_scope = credential_scope
|
15
|
-
@algo_prefix = options[:algo_prefix] || 'ESR'
|
16
|
-
@vendor_key = options[:vendor_key] || 'Escher'
|
17
|
-
@hash_algo = options[:hash_algo] || 'SHA256'
|
18
|
-
@current_time = options[:current_time] || Time.now
|
19
|
-
@auth_header_name = options[:auth_header_name] || 'X-Escher-Auth'
|
20
|
-
@date_header_name = options[:date_header_name] || 'X-Escher-Date'
|
21
|
-
@clock_skew = options[:clock_skew] || 900
|
22
|
-
end
|
23
|
-
|
24
|
-
def sign!(request, client, headers_to_sign = [])
|
25
|
-
uri = Addressable::URI.parse(request[:uri])
|
26
|
-
body = request[:body] || ''
|
27
|
-
headers = request[:headers].map {|k, v| {k.downcase => v} }.reduce({}, &:merge)
|
28
|
-
|
29
|
-
host = headers['host'] || uri.host || request[:host]
|
30
|
-
|
31
|
-
unless headers.has_key? 'host'
|
32
|
-
headers['host'] = host
|
33
|
-
end
|
34
|
-
unless headers.has_key? @date_header_name
|
35
|
-
headers[@date_header_name] = format_date_for_header
|
36
|
-
end
|
37
|
-
|
38
|
-
headers_to_sign |= [@date_header_name.downcase, 'host']
|
39
|
-
|
40
|
-
|
41
|
-
auth_header = generate_auth_header(client, request[:method], uri.path + (uri.query ? '?' + uri.query : ''), body, headers.map { |k, v| [k, v] }, headers_to_sign)
|
42
|
-
|
43
|
-
request[:headers] = headers.merge(@auth_header_name => auth_header)
|
44
|
-
request
|
45
|
-
end
|
46
|
-
|
47
|
-
def is_valid?(*args)
|
48
|
-
begin
|
49
|
-
authenticate(*args)
|
50
|
-
return true
|
51
|
-
rescue
|
52
|
-
return false
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def authenticate(req, key_db)
|
57
|
-
request = EscherRequest.new(req)
|
58
|
-
method = request.method
|
59
|
-
body = request.body
|
60
|
-
headers = request.headers
|
61
|
-
path = request.path
|
62
|
-
query_parts = request.query_values
|
63
|
-
|
64
|
-
signature_from_query = get_signing_param('Signature', query_parts)
|
65
|
-
|
66
|
-
validate_headers(headers, !signature_from_query)
|
67
|
-
|
68
|
-
if method == 'GET' && signature_from_query
|
69
|
-
raw_date = get_signing_param('Date', query_parts)
|
70
|
-
algorithm, api_key_id, short_date, credential_scope, signed_headers, signature, expires = get_auth_parts_from_query(query_parts)
|
71
|
-
|
72
|
-
body = 'UNSIGNED-PAYLOAD'
|
73
|
-
query_parts.delete [query_key_for('Signature'), signature]
|
74
|
-
query_parts = query_parts.map { |k, v| [uri_decode(k), uri_decode(v)] }
|
75
|
-
else
|
76
|
-
raw_date = get_header(@date_header_name, headers)
|
77
|
-
auth_header = get_header(@auth_header_name, headers)
|
78
|
-
algorithm, api_key_id, short_date, credential_scope, signed_headers, signature, expires = get_auth_parts_from_header(auth_header)
|
79
|
-
end
|
80
|
-
|
81
|
-
date = Time.parse(raw_date)
|
82
|
-
api_secret = key_db[api_key_id]
|
83
|
-
|
84
|
-
raise EscherError, 'Invalid API key' unless api_secret
|
85
|
-
raise EscherError, 'Only SHA256 and SHA512 hash algorithms are allowed' unless %w(SHA256 SHA512).include?(algorithm)
|
86
|
-
raise EscherError, 'Invalid request date' unless short_date(date) == short_date
|
87
|
-
raise EscherError, 'The request date is not within the accepted time range' unless is_date_within_range?(date, expires)
|
88
|
-
raise EscherError, 'Invalid credentials' unless credential_scope == @credential_scope
|
89
|
-
raise EscherError, 'Host header is not signed' unless signed_headers.include? 'host'
|
90
|
-
raise EscherError, 'Only the host header should be signed' if signature_from_query && signed_headers != ['host']
|
91
|
-
raise EscherError, 'Date header is not signed' if !signature_from_query && !signed_headers.include?(@date_header_name.downcase)
|
92
|
-
|
93
|
-
escher = reconfig(algorithm, credential_scope, date)
|
94
|
-
expected_signature = escher.generate_signature(api_secret, body, headers, method, signed_headers, path, query_parts)
|
95
|
-
raise EscherError, 'The signatures do not match' unless signature == expected_signature
|
96
|
-
api_key_id
|
97
|
-
end
|
98
|
-
|
99
|
-
def validate_headers(headers, authenticated_by_header)
|
100
|
-
(['Host'] + (authenticated_by_header ? [@auth_header_name, @date_header_name] : [])).each do |header|
|
101
|
-
raise EscherError, 'Missing header: ' + header unless get_header(header, headers)
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
def reconfig(algorithm, credential_scope, date)
|
106
|
-
Escher.new(
|
107
|
-
credential_scope,
|
108
|
-
algo_prefix: @algo_prefix,
|
109
|
-
vendor_key: @vendor_key,
|
110
|
-
hash_algo: algorithm,
|
111
|
-
auth_header_name: @auth_header_name,
|
112
|
-
date_header_name: @date_header_name,
|
113
|
-
current_time: date
|
114
|
-
)
|
115
|
-
end
|
116
|
-
|
117
|
-
def generate_auth_header(client, method, request_uri, body, headers, headers_to_sign)
|
118
|
-
path, query_parts = parse_uri(request_uri)
|
119
|
-
signature = generate_signature(client[:api_secret], body, headers, method, headers_to_sign, path, query_parts)
|
120
|
-
"#{get_algorithm_id} Credential=#{client[:api_key_id]}/#{short_date(@current_time)}/#{@credential_scope}, SignedHeaders=#{prepare_headers_to_sign headers_to_sign}, Signature=#{signature}"
|
121
|
-
end
|
122
|
-
|
123
|
-
def generate_signed_url(url_to_sign, client, expires = 86400)
|
124
|
-
uri = Addressable::URI.parse(url_to_sign)
|
125
|
-
protocol = uri.scheme
|
126
|
-
host = uri.host
|
127
|
-
path = uri.path
|
128
|
-
query_parts = parse_query(uri.query)
|
129
|
-
|
130
|
-
headers = [['host', host]]
|
131
|
-
headers_to_sign = ['host']
|
132
|
-
body = 'UNSIGNED-PAYLOAD'
|
133
|
-
query_parts += get_signing_params(client, expires, headers_to_sign)
|
134
|
-
|
135
|
-
signature = generate_signature(client[:api_secret], body, headers, 'GET', headers_to_sign, path, query_parts)
|
136
|
-
query_parts_with_signature = (query_parts.map { |k, v| [uri_encode(k), uri_encode(v)] } << query_pair('Signature', signature))
|
137
|
-
|
138
|
-
protocol + '://' + host + path + '?' + query_parts_with_signature.map { |k, v| k + '=' + v }.join('&')
|
139
|
-
end
|
140
|
-
|
141
|
-
def get_signing_params(client, expires, headers_to_sign)
|
142
|
-
[
|
143
|
-
['Algorithm', get_algorithm_id],
|
144
|
-
['Credentials', "#{client[:api_key_id]}/#{short_date(@current_time)}/#{@credential_scope}"],
|
145
|
-
['Date', long_date(@current_time)],
|
146
|
-
['Expires', expires.to_s],
|
147
|
-
['SignedHeaders', headers_to_sign.join(';')],
|
148
|
-
].map { |k, v| query_pair(k, v) }
|
149
|
-
end
|
150
|
-
|
151
|
-
def query_pair(k, v)
|
152
|
-
[query_key_for(k), v]
|
153
|
-
end
|
154
|
-
|
155
|
-
def query_key_for(key)
|
156
|
-
"X-#{@vendor_key}-#{key}"
|
157
|
-
end
|
158
|
-
|
159
|
-
def query_key_truncate(key)
|
160
|
-
key[@vendor_key.length + 3..-1]
|
161
|
-
end
|
162
|
-
|
163
|
-
def get_header(header_name, headers)
|
164
|
-
the_header = (headers.detect { |header| header[0].downcase == header_name.downcase })
|
165
|
-
the_header ? the_header[1] : nil
|
166
|
-
end
|
167
|
-
|
168
|
-
def get_signing_param(key, query_parts)
|
169
|
-
the_param = (query_parts.detect { |param| param[0] === query_key_for(key) })
|
170
|
-
the_param ? uri_decode(the_param[1]) : nil
|
171
|
-
end
|
172
|
-
|
173
|
-
def get_auth_parts_from_header(auth_header)
|
174
|
-
m = /#{@algo_prefix}-HMAC-(?<algo>[A-Z0-9\,]+) Credential=(?<api_key_id>[A-Za-z0-9\-_]+)\/(?<short_date>[0-9]{8})\/(?<credentials>[A-Za-z0-9\-_\/]+), SignedHeaders=(?<signed_headers>[A-Za-z\-;]+), Signature=(?<signature>[0-9a-f]+)$/
|
175
|
-
.match auth_header
|
176
|
-
raise EscherError, 'Malformed authorization header' unless m && m['credentials']
|
177
|
-
return m['algo'], m['api_key_id'], m['short_date'], m['credentials'], m['signed_headers'].split(';'), m['signature'], 0
|
178
|
-
end
|
179
|
-
|
180
|
-
def get_auth_parts_from_query(query_parts)
|
181
|
-
expires = get_signing_param('Expires', query_parts).to_i
|
182
|
-
api_key_id, short_date, credential_scope = get_signing_param('Credentials', query_parts).split('/', 3)
|
183
|
-
signed_headers = get_signing_param('SignedHeaders', query_parts).split ';'
|
184
|
-
algorithm = parse_algo(get_signing_param('Algorithm', query_parts))
|
185
|
-
signature = get_signing_param('Signature', query_parts)
|
186
|
-
return algorithm, api_key_id, short_date, credential_scope, signed_headers, signature, expires
|
187
|
-
end
|
188
|
-
|
189
|
-
def generate_signature(api_secret, body, headers, method, signed_headers, path, query_parts)
|
190
|
-
canonicalized_request = canonicalize(method, path, query_parts, body, headers, signed_headers.uniq)
|
191
|
-
string_to_sign = get_string_to_sign(canonicalized_request)
|
192
|
-
signing_key = calculate_signing_key(api_secret)
|
193
|
-
Digest::HMAC.hexdigest(string_to_sign, signing_key, create_algo)
|
194
|
-
end
|
195
|
-
|
196
|
-
def format_date_for_header
|
197
|
-
@date_header_name.downcase == 'date' ? @current_time.utc.rfc2822.sub('-0000', 'GMT') : long_date(@current_time)
|
198
|
-
end
|
199
|
-
|
200
|
-
def canonicalize(method, path, query_parts, body, headers, headers_to_sign) [
|
201
|
-
method,
|
202
|
-
canonicalize_path(path),
|
203
|
-
canonicalize_query(query_parts),
|
204
|
-
canonicalize_headers(headers, headers_to_sign).join("\n"),
|
205
|
-
'',
|
206
|
-
prepare_headers_to_sign(headers_to_sign),
|
207
|
-
create_algo.new.hexdigest(body || '') # TODO: we should set the default value at the same level at every implementation
|
208
|
-
].join "\n"
|
209
|
-
end
|
210
|
-
|
211
|
-
def prepare_headers_to_sign(headers_to_sign)
|
212
|
-
headers_to_sign.sort.uniq.join(';')
|
213
|
-
end
|
214
|
-
|
215
|
-
def parse_uri(request_uri)
|
216
|
-
path, query = request_uri.split '?', 2
|
217
|
-
return path, parse_query(query)
|
218
|
-
end
|
219
|
-
|
220
|
-
def parse_query(query)
|
221
|
-
(query || '')
|
222
|
-
.split('&', -1)
|
223
|
-
.map { |pair| pair.split('=', -1) }
|
224
|
-
.map { |k, v| (k.include?' ') ? [k.str(/\S+/), ''] : [k, v] }
|
225
|
-
end
|
226
|
-
|
227
|
-
def get_string_to_sign(canonicalized_req)
|
228
|
-
[
|
229
|
-
get_algorithm_id,
|
230
|
-
long_date(@current_time),
|
231
|
-
short_date(@current_time) + '/' + @credential_scope,
|
232
|
-
create_algo.new.hexdigest(canonicalized_req)
|
233
|
-
].join("\n")
|
234
|
-
end
|
235
|
-
|
236
|
-
def create_algo
|
237
|
-
case @hash_algo
|
238
|
-
when 'SHA256'
|
239
|
-
return Digest::SHA2.new 256
|
240
|
-
when 'SHA512'
|
241
|
-
return Digest::SHA2.new 512
|
242
|
-
else
|
243
|
-
raise EscherError, 'Unidentified hash algorithm'
|
244
|
-
end
|
245
|
-
end
|
246
|
-
|
247
|
-
def long_date(date)
|
248
|
-
date.utc.strftime('%Y%m%dT%H%M%SZ')
|
249
|
-
end
|
250
|
-
|
251
|
-
def short_date(date)
|
252
|
-
date.utc.strftime('%Y%m%d')
|
253
|
-
end
|
254
|
-
|
255
|
-
def is_date_within_range?(request_date, expires)
|
256
|
-
(request_date - @clock_skew .. request_date + expires + @clock_skew).cover? @current_time
|
257
|
-
end
|
258
|
-
|
259
|
-
def get_algorithm_id
|
260
|
-
@algo_prefix + '-HMAC-' + @hash_algo
|
261
|
-
end
|
262
|
-
|
263
|
-
def parse_algo(algorithm)
|
264
|
-
m = /^#{@algo_prefix}-HMAC-(?<algo>[A-Z0-9\,]+)$/.match(algorithm)
|
265
|
-
m && m['algo']
|
266
|
-
end
|
267
|
-
|
268
|
-
def calculate_signing_key(api_secret)
|
269
|
-
algo = create_algo
|
270
|
-
signing_key = @algo_prefix + api_secret
|
271
|
-
key_parts = [short_date(@current_time)] + @credential_scope.split('/')
|
272
|
-
key_parts.each { |data|
|
273
|
-
signing_key = Digest::HMAC.digest(data, signing_key, algo)
|
274
|
-
}
|
275
|
-
signing_key
|
276
|
-
end
|
277
|
-
|
278
|
-
def canonicalize_path(path)
|
279
|
-
while path.gsub!(%r{([^/]+)/\.\./?}) { |match| $1 == '..' ? match : '' } do end
|
280
|
-
path.gsub(%r{/\./}, '/').sub(%r{/\.\z}, '/').gsub(/\/+/, '/')
|
281
|
-
end
|
282
|
-
|
283
|
-
def canonicalize_headers(raw_headers, headers_to_sign)
|
284
|
-
collect_headers(raw_headers)
|
285
|
-
.sort
|
286
|
-
.select { |h| headers_to_sign.include?(h[0]) }
|
287
|
-
.map { |k, v| k + ':' + v.map { |piece| normalize_white_spaces piece} .join(',') }
|
288
|
-
end
|
289
|
-
|
290
|
-
def normalize_white_spaces(value)
|
291
|
-
value.strip.split('"', -1).map.with_index { |piece, index|
|
292
|
-
is_inside_of_quotes = (index % 2 === 1)
|
293
|
-
is_inside_of_quotes ? piece : piece.gsub(/\s+/, ' ')
|
294
|
-
}.join '"'
|
295
|
-
end
|
296
|
-
|
297
|
-
def collect_headers(raw_headers)
|
298
|
-
headers = {}
|
299
|
-
raw_headers.each do |raw_header|
|
300
|
-
if raw_header[0].downcase != @auth_header_name.downcase
|
301
|
-
if headers[raw_header[0].downcase]
|
302
|
-
headers[raw_header[0].downcase] << raw_header[1]
|
303
|
-
else
|
304
|
-
headers[raw_header[0].downcase] = [raw_header[1]]
|
305
|
-
end
|
306
|
-
end
|
307
|
-
end
|
308
|
-
headers
|
309
|
-
end
|
310
|
-
|
311
|
-
def canonicalize_query(query_parts)
|
312
|
-
query_parts
|
313
|
-
.map { |k, v| uri_encode(k.gsub('+', ' ')) + '=' + uri_encode(v || '') }
|
314
|
-
.sort.join '&'
|
315
|
-
end
|
316
|
-
|
317
|
-
def uri_encode(component)
|
318
|
-
Addressable::URI.encode_component(component, Addressable::URI::CharacterClasses::UNRESERVED)
|
319
|
-
end
|
320
|
-
|
321
|
-
def uri_decode(component)
|
322
|
-
Addressable::URI.unencode_component(component)
|
323
|
-
end
|
324
|
-
end
|
data/lib/escher/request.rb
DELETED
@@ -1,83 +0,0 @@
|
|
1
|
-
|
2
|
-
class EscherRequest
|
3
|
-
|
4
|
-
def initialize(request)
|
5
|
-
@request = request
|
6
|
-
request_uri = Addressable::URI.parse(uri)
|
7
|
-
raise "Invalid request URI: #{request_uri}" unless request_uri
|
8
|
-
@request_uri = request_uri
|
9
|
-
prepare_request_headers
|
10
|
-
end
|
11
|
-
|
12
|
-
def prepare_request_headers
|
13
|
-
@request_headers = []
|
14
|
-
case @request.class.to_s
|
15
|
-
when 'Hash'
|
16
|
-
@request_headers = @request[:headers]
|
17
|
-
when 'Sinatra::Request' # TODO: not working yet
|
18
|
-
@request.env.each { |key, value|
|
19
|
-
if key.downcase[0, 5] == "http_"
|
20
|
-
@request_headers += [[ key[5..-1].gsub("_", "-"), value ]]
|
21
|
-
end
|
22
|
-
}
|
23
|
-
when 'WEBrick::HTTPRequest'
|
24
|
-
@request.header.each { |key, values|
|
25
|
-
values.each { |value|
|
26
|
-
@request_headers += [[ key, value ]]
|
27
|
-
}
|
28
|
-
}
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def request
|
33
|
-
@request
|
34
|
-
end
|
35
|
-
|
36
|
-
def headers
|
37
|
-
@request_headers
|
38
|
-
end
|
39
|
-
|
40
|
-
def set_header(key, value)
|
41
|
-
@request[key] = value
|
42
|
-
end
|
43
|
-
|
44
|
-
def method
|
45
|
-
case @request.class.to_s
|
46
|
-
when 'Hash'
|
47
|
-
@request[:method]
|
48
|
-
else
|
49
|
-
@request.request_method
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def uri
|
54
|
-
case @request.class.to_s
|
55
|
-
when 'Hash'
|
56
|
-
@request[:uri]
|
57
|
-
else
|
58
|
-
@request.uri
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def body
|
63
|
-
case @request.class.to_s
|
64
|
-
when 'Hash'
|
65
|
-
@request[:body]
|
66
|
-
else
|
67
|
-
@request.body
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def host
|
72
|
-
@request_uri.host
|
73
|
-
end
|
74
|
-
|
75
|
-
def path
|
76
|
-
@request_uri.path
|
77
|
-
end
|
78
|
-
|
79
|
-
def query_values
|
80
|
-
@request_uri.query_values(Array) || []
|
81
|
-
end
|
82
|
-
|
83
|
-
end
|