ruby-openid2 3.0.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
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +136 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/CONTRIBUTING.md +54 -0
- data/LICENSE.txt +210 -0
- data/README.md +81 -0
- data/SECURITY.md +15 -0
- data/lib/hmac/hmac.rb +110 -0
- data/lib/hmac/sha1.rb +11 -0
- data/lib/hmac/sha2.rb +25 -0
- data/lib/openid/association.rb +246 -0
- data/lib/openid/consumer/associationmanager.rb +354 -0
- data/lib/openid/consumer/checkid_request.rb +179 -0
- data/lib/openid/consumer/discovery.rb +516 -0
- data/lib/openid/consumer/discovery_manager.rb +144 -0
- data/lib/openid/consumer/html_parse.rb +142 -0
- data/lib/openid/consumer/idres.rb +513 -0
- data/lib/openid/consumer/responses.rb +147 -0
- data/lib/openid/consumer/session.rb +36 -0
- data/lib/openid/consumer.rb +406 -0
- data/lib/openid/cryptutil.rb +112 -0
- data/lib/openid/dh.rb +84 -0
- data/lib/openid/extension.rb +38 -0
- data/lib/openid/extensions/ax.rb +552 -0
- data/lib/openid/extensions/oauth.rb +88 -0
- data/lib/openid/extensions/pape.rb +170 -0
- data/lib/openid/extensions/sreg.rb +268 -0
- data/lib/openid/extensions/ui.rb +49 -0
- data/lib/openid/fetchers.rb +277 -0
- data/lib/openid/kvform.rb +113 -0
- data/lib/openid/kvpost.rb +62 -0
- data/lib/openid/message.rb +555 -0
- data/lib/openid/protocolerror.rb +7 -0
- data/lib/openid/server.rb +1571 -0
- data/lib/openid/store/filesystem.rb +260 -0
- data/lib/openid/store/interface.rb +73 -0
- data/lib/openid/store/memcache.rb +109 -0
- data/lib/openid/store/memory.rb +79 -0
- data/lib/openid/store/nonce.rb +72 -0
- data/lib/openid/trustroot.rb +597 -0
- data/lib/openid/urinorm.rb +72 -0
- data/lib/openid/util.rb +119 -0
- data/lib/openid/version.rb +5 -0
- data/lib/openid/yadis/accept.rb +141 -0
- data/lib/openid/yadis/constants.rb +16 -0
- data/lib/openid/yadis/discovery.rb +151 -0
- data/lib/openid/yadis/filters.rb +192 -0
- data/lib/openid/yadis/htmltokenizer.rb +290 -0
- data/lib/openid/yadis/parsehtml.rb +50 -0
- data/lib/openid/yadis/services.rb +44 -0
- data/lib/openid/yadis/xrds.rb +160 -0
- data/lib/openid/yadis/xri.rb +86 -0
- data/lib/openid/yadis/xrires.rb +87 -0
- data/lib/openid.rb +27 -0
- data/lib/ruby-openid.rb +1 -0
- data.tar.gz.sig +0 -0
- metadata +331 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,597 @@
|
|
1
|
+
# stdlib
|
2
|
+
require "uri"
|
3
|
+
|
4
|
+
# This library
|
5
|
+
require_relative "urinorm"
|
6
|
+
|
7
|
+
module OpenID
|
8
|
+
class RealmVerificationRedirected < Exception
|
9
|
+
# Attempting to verify this realm resulted in a redirect.
|
10
|
+
def initialize(relying_party_url, rp_url_after_redirects)
|
11
|
+
@relying_party_url = relying_party_url
|
12
|
+
@rp_url_after_redirects = rp_url_after_redirects
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
"Attempting to verify #{@relying_party_url} resulted in " +
|
17
|
+
"redirect to #{@rp_url_after_redirects}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module TrustRoot
|
22
|
+
TOP_LEVEL_DOMAINS = %w[
|
23
|
+
ac
|
24
|
+
ad
|
25
|
+
ae
|
26
|
+
aero
|
27
|
+
af
|
28
|
+
ag
|
29
|
+
ai
|
30
|
+
al
|
31
|
+
am
|
32
|
+
an
|
33
|
+
ao
|
34
|
+
aq
|
35
|
+
ar
|
36
|
+
arpa
|
37
|
+
as
|
38
|
+
asia
|
39
|
+
at
|
40
|
+
au
|
41
|
+
aw
|
42
|
+
ax
|
43
|
+
az
|
44
|
+
ba
|
45
|
+
bb
|
46
|
+
bd
|
47
|
+
be
|
48
|
+
bf
|
49
|
+
bg
|
50
|
+
bh
|
51
|
+
bi
|
52
|
+
biz
|
53
|
+
bj
|
54
|
+
bm
|
55
|
+
bn
|
56
|
+
bo
|
57
|
+
br
|
58
|
+
bs
|
59
|
+
bt
|
60
|
+
bv
|
61
|
+
bw
|
62
|
+
by
|
63
|
+
bz
|
64
|
+
ca
|
65
|
+
cat
|
66
|
+
cc
|
67
|
+
cd
|
68
|
+
cf
|
69
|
+
cg
|
70
|
+
ch
|
71
|
+
ci
|
72
|
+
ck
|
73
|
+
cl
|
74
|
+
cm
|
75
|
+
cn
|
76
|
+
co
|
77
|
+
com
|
78
|
+
coop
|
79
|
+
cr
|
80
|
+
cu
|
81
|
+
cv
|
82
|
+
cx
|
83
|
+
cy
|
84
|
+
cz
|
85
|
+
de
|
86
|
+
dj
|
87
|
+
dk
|
88
|
+
dm
|
89
|
+
do
|
90
|
+
dz
|
91
|
+
ec
|
92
|
+
edu
|
93
|
+
ee
|
94
|
+
eg
|
95
|
+
er
|
96
|
+
es
|
97
|
+
et
|
98
|
+
eu
|
99
|
+
fi
|
100
|
+
fj
|
101
|
+
fk
|
102
|
+
fm
|
103
|
+
fo
|
104
|
+
fr
|
105
|
+
ga
|
106
|
+
gb
|
107
|
+
gd
|
108
|
+
ge
|
109
|
+
gf
|
110
|
+
gg
|
111
|
+
gh
|
112
|
+
gi
|
113
|
+
gl
|
114
|
+
gm
|
115
|
+
gn
|
116
|
+
gov
|
117
|
+
gp
|
118
|
+
gq
|
119
|
+
gr
|
120
|
+
gs
|
121
|
+
gt
|
122
|
+
gu
|
123
|
+
gw
|
124
|
+
gy
|
125
|
+
hk
|
126
|
+
hm
|
127
|
+
hn
|
128
|
+
hr
|
129
|
+
ht
|
130
|
+
hu
|
131
|
+
id
|
132
|
+
ie
|
133
|
+
il
|
134
|
+
im
|
135
|
+
in
|
136
|
+
info
|
137
|
+
int
|
138
|
+
io
|
139
|
+
iq
|
140
|
+
ir
|
141
|
+
is
|
142
|
+
it
|
143
|
+
je
|
144
|
+
jm
|
145
|
+
jo
|
146
|
+
jobs
|
147
|
+
jp
|
148
|
+
ke
|
149
|
+
kg
|
150
|
+
kh
|
151
|
+
ki
|
152
|
+
km
|
153
|
+
kn
|
154
|
+
kp
|
155
|
+
kr
|
156
|
+
kw
|
157
|
+
ky
|
158
|
+
kz
|
159
|
+
la
|
160
|
+
lb
|
161
|
+
lc
|
162
|
+
li
|
163
|
+
lk
|
164
|
+
lr
|
165
|
+
ls
|
166
|
+
lt
|
167
|
+
lu
|
168
|
+
lv
|
169
|
+
ly
|
170
|
+
ma
|
171
|
+
mc
|
172
|
+
md
|
173
|
+
me
|
174
|
+
mg
|
175
|
+
mh
|
176
|
+
mil
|
177
|
+
mk
|
178
|
+
ml
|
179
|
+
mm
|
180
|
+
mn
|
181
|
+
mo
|
182
|
+
mobi
|
183
|
+
mp
|
184
|
+
mq
|
185
|
+
mr
|
186
|
+
ms
|
187
|
+
mt
|
188
|
+
mu
|
189
|
+
museum
|
190
|
+
mv
|
191
|
+
mw
|
192
|
+
mx
|
193
|
+
my
|
194
|
+
mz
|
195
|
+
na
|
196
|
+
name
|
197
|
+
nc
|
198
|
+
ne
|
199
|
+
net
|
200
|
+
nf
|
201
|
+
ng
|
202
|
+
ni
|
203
|
+
nl
|
204
|
+
no
|
205
|
+
np
|
206
|
+
nr
|
207
|
+
nu
|
208
|
+
nz
|
209
|
+
om
|
210
|
+
org
|
211
|
+
pa
|
212
|
+
pe
|
213
|
+
pf
|
214
|
+
pg
|
215
|
+
ph
|
216
|
+
pk
|
217
|
+
pl
|
218
|
+
pm
|
219
|
+
pn
|
220
|
+
pr
|
221
|
+
pro
|
222
|
+
ps
|
223
|
+
pt
|
224
|
+
pw
|
225
|
+
py
|
226
|
+
qa
|
227
|
+
re
|
228
|
+
ro
|
229
|
+
rs
|
230
|
+
ru
|
231
|
+
rw
|
232
|
+
sa
|
233
|
+
sb
|
234
|
+
sc
|
235
|
+
sd
|
236
|
+
se
|
237
|
+
sg
|
238
|
+
sh
|
239
|
+
si
|
240
|
+
sj
|
241
|
+
sk
|
242
|
+
sl
|
243
|
+
sm
|
244
|
+
sn
|
245
|
+
so
|
246
|
+
sr
|
247
|
+
st
|
248
|
+
su
|
249
|
+
sv
|
250
|
+
sy
|
251
|
+
sz
|
252
|
+
tc
|
253
|
+
td
|
254
|
+
tel
|
255
|
+
tf
|
256
|
+
tg
|
257
|
+
th
|
258
|
+
tj
|
259
|
+
tk
|
260
|
+
tl
|
261
|
+
tm
|
262
|
+
tn
|
263
|
+
to
|
264
|
+
tp
|
265
|
+
tr
|
266
|
+
travel
|
267
|
+
tt
|
268
|
+
tv
|
269
|
+
tw
|
270
|
+
tz
|
271
|
+
ua
|
272
|
+
ug
|
273
|
+
uk
|
274
|
+
us
|
275
|
+
uy
|
276
|
+
uz
|
277
|
+
va
|
278
|
+
vc
|
279
|
+
ve
|
280
|
+
vg
|
281
|
+
vi
|
282
|
+
vn
|
283
|
+
vu
|
284
|
+
wf
|
285
|
+
ws
|
286
|
+
xn--0zwm56d
|
287
|
+
xn--11b5bs3a9aj6g
|
288
|
+
xn--80akhbyknj4f
|
289
|
+
xn--9t4b11yi5a
|
290
|
+
xn--deba0ad
|
291
|
+
xn--g6w251d
|
292
|
+
xn--hgbk6aj7f53bba
|
293
|
+
xn--hlcj6aya9esc7a
|
294
|
+
xn--jxalpdlp
|
295
|
+
xn--kgbechtv
|
296
|
+
xn--zckzah
|
297
|
+
ye
|
298
|
+
yt
|
299
|
+
yu
|
300
|
+
za
|
301
|
+
zm
|
302
|
+
zw
|
303
|
+
]
|
304
|
+
|
305
|
+
ALLOWED_PROTOCOLS = %w[http https]
|
306
|
+
|
307
|
+
# The URI for relying party discovery, used in realm verification.
|
308
|
+
#
|
309
|
+
# XXX: This should probably live somewhere else (like in
|
310
|
+
# OpenID or OpenID::Yadis somewhere)
|
311
|
+
RP_RETURN_TO_URL_TYPE = "http://specs.openid.net/auth/2.0/return_to"
|
312
|
+
|
313
|
+
# If the endpoint is a relying party OpenID return_to endpoint,
|
314
|
+
# return the endpoint URL. Otherwise, return None.
|
315
|
+
#
|
316
|
+
# This function is intended to be used as a filter for the Yadis
|
317
|
+
# filtering interface.
|
318
|
+
#
|
319
|
+
# endpoint: An XRDS BasicServiceEndpoint, as returned by
|
320
|
+
# performing Yadis dicovery.
|
321
|
+
#
|
322
|
+
# returns the endpoint URL or None if the endpoint is not a
|
323
|
+
# relying party endpoint.
|
324
|
+
def self._extract_return_url(endpoint)
|
325
|
+
return endpoint.uri if endpoint.matchTypes([RP_RETURN_TO_URL_TYPE])
|
326
|
+
|
327
|
+
nil
|
328
|
+
end
|
329
|
+
|
330
|
+
# Is the return_to URL under one of the supplied allowed
|
331
|
+
# return_to URLs?
|
332
|
+
def self.return_to_matches(allowed_return_to_urls, return_to)
|
333
|
+
allowed_return_to_urls.each do |allowed_return_to|
|
334
|
+
# A return_to pattern works the same as a realm, except that
|
335
|
+
# it's not allowed to use a wildcard. We'll model this by
|
336
|
+
# parsing it as a realm, and not trying to match it if it has
|
337
|
+
# a wildcard.
|
338
|
+
|
339
|
+
return_realm = TrustRoot.parse(allowed_return_to)
|
340
|
+
if !return_realm.nil? and
|
341
|
+
|
342
|
+
# Does not have a wildcard
|
343
|
+
!return_realm.wildcard and
|
344
|
+
|
345
|
+
# Matches the return_to that we passed in with it
|
346
|
+
return_realm.validate_url(return_to) # Parses as a trust root
|
347
|
+
|
348
|
+
return true
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
# No URL in the list matched
|
353
|
+
false
|
354
|
+
end
|
355
|
+
|
356
|
+
# Given a relying party discovery URL return a list of return_to
|
357
|
+
# URLs.
|
358
|
+
def self.get_allowed_return_urls(relying_party_url)
|
359
|
+
rp_url_after_redirects, return_to_urls = services.get_service_endpoints(
|
360
|
+
relying_party_url, _extract_return_url
|
361
|
+
)
|
362
|
+
|
363
|
+
if rp_url_after_redirects != relying_party_url
|
364
|
+
# Verification caused a redirect
|
365
|
+
raise RealmVerificationRedirected.new(
|
366
|
+
relying_party_url, rp_url_after_redirects
|
367
|
+
)
|
368
|
+
end
|
369
|
+
|
370
|
+
return_to_urls
|
371
|
+
end
|
372
|
+
|
373
|
+
# Verify that a return_to URL is valid for the given realm.
|
374
|
+
#
|
375
|
+
# This function builds a discovery URL, performs Yadis discovery
|
376
|
+
# on it, makes sure that the URL does not redirect, parses out
|
377
|
+
# the return_to URLs, and finally checks to see if the current
|
378
|
+
# return_to URL matches the return_to.
|
379
|
+
#
|
380
|
+
# raises DiscoveryFailure when Yadis discovery fails returns
|
381
|
+
# true if the return_to URL is valid for the realm
|
382
|
+
def self.verify_return_to(realm_str, return_to, _vrfy = nil)
|
383
|
+
# _vrfy parameter is there to make testing easier
|
384
|
+
_vrfy = method(:get_allowed_return_urls) if _vrfy.nil?
|
385
|
+
|
386
|
+
raise ArgumentError, "_vrfy must be a Proc or Method" unless _vrfy.is_a?(Proc) or _vrfy.is_a?(Method)
|
387
|
+
|
388
|
+
realm = TrustRoot.parse(realm_str)
|
389
|
+
if realm.nil?
|
390
|
+
# The realm does not parse as a URL pattern
|
391
|
+
return false
|
392
|
+
end
|
393
|
+
|
394
|
+
begin
|
395
|
+
allowable_urls = _vrfy.call(realm.build_discovery_url)
|
396
|
+
rescue RealmVerificationRedirected => e
|
397
|
+
Util.log(e.to_s)
|
398
|
+
return false
|
399
|
+
end
|
400
|
+
|
401
|
+
return true if return_to_matches(allowable_urls, return_to)
|
402
|
+
|
403
|
+
Util.log("Failed to validate return_to #{return_to} for " +
|
404
|
+
"realm #{realm_str}, was not in #{allowable_urls}")
|
405
|
+
false
|
406
|
+
end
|
407
|
+
|
408
|
+
class TrustRoot
|
409
|
+
attr_reader :unparsed, :proto, :wildcard, :host, :port, :path
|
410
|
+
|
411
|
+
@@empty_re = Regexp.new('^http[s]*:\/\/\*\/$')
|
412
|
+
|
413
|
+
def self._build_path(path, query = nil, frag = nil)
|
414
|
+
s = path.dup
|
415
|
+
|
416
|
+
frag = nil if frag == ""
|
417
|
+
query = nil if query == ""
|
418
|
+
|
419
|
+
s << "?" << query if query
|
420
|
+
|
421
|
+
s << "#" << frag if frag
|
422
|
+
|
423
|
+
s
|
424
|
+
end
|
425
|
+
|
426
|
+
def self._parse_url(url)
|
427
|
+
begin
|
428
|
+
url = URINorm.urinorm(url)
|
429
|
+
rescue URI::InvalidURIError
|
430
|
+
nil
|
431
|
+
end
|
432
|
+
|
433
|
+
begin
|
434
|
+
parsed = URI::DEFAULT_PARSER.parse(url)
|
435
|
+
rescue URI::InvalidURIError
|
436
|
+
return
|
437
|
+
end
|
438
|
+
|
439
|
+
path = TrustRoot._build_path(
|
440
|
+
parsed.path,
|
441
|
+
parsed.query,
|
442
|
+
parsed.fragment,
|
443
|
+
)
|
444
|
+
|
445
|
+
[
|
446
|
+
parsed.scheme || "",
|
447
|
+
parsed.host || "",
|
448
|
+
parsed.port || "",
|
449
|
+
path || "",
|
450
|
+
]
|
451
|
+
end
|
452
|
+
|
453
|
+
def self.parse(trust_root)
|
454
|
+
trust_root = trust_root.dup
|
455
|
+
unparsed = trust_root.dup
|
456
|
+
|
457
|
+
# look for wildcard
|
458
|
+
wildcard = !trust_root.index("://*.").nil?
|
459
|
+
trust_root.sub!("*.", "") if wildcard
|
460
|
+
|
461
|
+
# handle http://*/ case
|
462
|
+
if !wildcard and @@empty_re.match(trust_root)
|
463
|
+
proto = trust_root.split(":")[0]
|
464
|
+
port = (proto == "http") ? 80 : 443
|
465
|
+
return new(unparsed, proto, true, "", port, "/")
|
466
|
+
end
|
467
|
+
|
468
|
+
parts = TrustRoot._parse_url(trust_root)
|
469
|
+
return if parts.nil?
|
470
|
+
|
471
|
+
proto, host, port, path = parts
|
472
|
+
return if host[0] == "."
|
473
|
+
|
474
|
+
# check for URI fragment
|
475
|
+
return if path and !path.index("#").nil?
|
476
|
+
|
477
|
+
return unless %w[http https].member?(proto)
|
478
|
+
|
479
|
+
new(unparsed, proto, wildcard, host, port, path)
|
480
|
+
end
|
481
|
+
|
482
|
+
def self.check_sanity(trust_root_string)
|
483
|
+
trust_root = TrustRoot.parse(trust_root_string)
|
484
|
+
return false if trust_root.nil?
|
485
|
+
|
486
|
+
trust_root.sane?
|
487
|
+
end
|
488
|
+
|
489
|
+
# quick func for validating a url against a trust root. See the
|
490
|
+
# TrustRoot class if you need more control.
|
491
|
+
def self.check_url(trust_root, url)
|
492
|
+
tr = parse(trust_root)
|
493
|
+
(!tr.nil? and tr.validate_url(url))
|
494
|
+
end
|
495
|
+
|
496
|
+
# Return a discovery URL for this realm.
|
497
|
+
#
|
498
|
+
# This function does not check to make sure that the realm is
|
499
|
+
# valid. Its behaviour on invalid inputs is undefined.
|
500
|
+
#
|
501
|
+
# return_to:: The relying party return URL of the OpenID
|
502
|
+
# authentication request
|
503
|
+
#
|
504
|
+
# Returns the URL upon which relying party discovery should be
|
505
|
+
# run in order to verify the return_to URL
|
506
|
+
def build_discovery_url
|
507
|
+
return @unparsed unless wildcard
|
508
|
+
|
509
|
+
# Use "www." in place of the star
|
510
|
+
www_domain = "www." + @host
|
511
|
+
port = (!@port.nil? and ![80, 443].member?(@port)) ? (":" + @port.to_s) : ""
|
512
|
+
"#{@proto}://#{www_domain}#{port}#{@path}"
|
513
|
+
end
|
514
|
+
|
515
|
+
def initialize(unparsed, proto, wildcard, host, port, path)
|
516
|
+
@unparsed = unparsed
|
517
|
+
@proto = proto
|
518
|
+
@wildcard = wildcard
|
519
|
+
@host = host
|
520
|
+
@port = port
|
521
|
+
@path = path
|
522
|
+
end
|
523
|
+
|
524
|
+
def sane?
|
525
|
+
return true if @host == "localhost"
|
526
|
+
|
527
|
+
host_parts = @host.split(".")
|
528
|
+
|
529
|
+
# a note: ruby string split does not put an empty string at
|
530
|
+
# the end of the list if the split element is last. for
|
531
|
+
# example, 'foo.com.'.split('.') => ['foo','com']. Mentioned
|
532
|
+
# because the python code differs here.
|
533
|
+
|
534
|
+
return false if host_parts.length == 0
|
535
|
+
|
536
|
+
# no adjacent dots
|
537
|
+
return false if host_parts.member?("")
|
538
|
+
|
539
|
+
# last part must be a tld
|
540
|
+
tld = host_parts[-1]
|
541
|
+
return false unless TOP_LEVEL_DOMAINS.member?(tld)
|
542
|
+
|
543
|
+
return false if host_parts.length == 1
|
544
|
+
|
545
|
+
if @wildcard && (tld.length == 2 and host_parts[-2].length <= 3)
|
546
|
+
# It's a 2-letter tld with a short second to last segment
|
547
|
+
# so there needs to be more than two segments specified
|
548
|
+
# (e.g. *.co.uk is insane)
|
549
|
+
return host_parts.length > 2
|
550
|
+
end
|
551
|
+
|
552
|
+
true
|
553
|
+
end
|
554
|
+
|
555
|
+
def validate_url(url)
|
556
|
+
parts = TrustRoot._parse_url(url)
|
557
|
+
return false if parts.nil?
|
558
|
+
|
559
|
+
proto, host, port, path = parts
|
560
|
+
|
561
|
+
return false unless proto == @proto
|
562
|
+
return false unless port == @port
|
563
|
+
return false unless host.index("*").nil?
|
564
|
+
|
565
|
+
if !@wildcard
|
566
|
+
return false if host != @host
|
567
|
+
elsif (@host != "") and
|
568
|
+
!host.end_with?("." + @host) and
|
569
|
+
(host != @host)
|
570
|
+
return false
|
571
|
+
end
|
572
|
+
|
573
|
+
if path != @path
|
574
|
+
path_len = @path.length
|
575
|
+
trust_prefix = @path[0...path_len]
|
576
|
+
url_prefix = path[0...path_len]
|
577
|
+
|
578
|
+
# must be equal up to the length of the path, at least
|
579
|
+
return false if trust_prefix != url_prefix
|
580
|
+
|
581
|
+
# These characters must be on the boundary between the end
|
582
|
+
# of the trust root's path and the start of the URL's path.
|
583
|
+
allowed = if !@path.index("?").nil?
|
584
|
+
"&"
|
585
|
+
else
|
586
|
+
"?/"
|
587
|
+
end
|
588
|
+
|
589
|
+
return (!allowed.index(@path[-1]).nil? or
|
590
|
+
!allowed.index(path[path_len]).nil?)
|
591
|
+
end
|
592
|
+
|
593
|
+
true
|
594
|
+
end
|
595
|
+
end
|
596
|
+
end
|
597
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# stdlib
|
2
|
+
require "uri"
|
3
|
+
|
4
|
+
module OpenID
|
5
|
+
module URINorm
|
6
|
+
VALID_URI_SCHEMES = %w[http https].freeze
|
7
|
+
|
8
|
+
def self.urinorm(uri)
|
9
|
+
uri = URI.parse(uri)
|
10
|
+
|
11
|
+
raise URI::InvalidURIError.new("no scheme") unless uri.scheme
|
12
|
+
|
13
|
+
uri.scheme = uri.scheme.downcase
|
14
|
+
raise URI::InvalidURIError.new("Not an HTTP or HTTPS URI") unless VALID_URI_SCHEMES.member?(uri.scheme)
|
15
|
+
|
16
|
+
raise URI::InvalidURIError.new("no host") if uri.host.nil? # For Ruby 2.7
|
17
|
+
|
18
|
+
raise URI::InvalidURIError.new("no host") if uri.host.empty? # For Ruby 3+
|
19
|
+
|
20
|
+
uri.host = uri.host.downcase
|
21
|
+
|
22
|
+
uri.path = remove_dot_segments(uri.path)
|
23
|
+
uri.path = "/" if uri.path.empty?
|
24
|
+
|
25
|
+
uri = uri.normalize.to_s
|
26
|
+
uri.gsub(PERCENT_ESCAPE_RE) do
|
27
|
+
sub = ::Regexp.last_match(0)[1..2].to_i(16).chr
|
28
|
+
reserved(sub) ? ::Regexp.last_match(0).upcase : sub
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
RESERVED_RE = /[A-Za-z0-9._~-]/
|
33
|
+
PERCENT_ESCAPE_RE = /%[0-9a-zA-Z]{2}/
|
34
|
+
|
35
|
+
def self.reserved(chr)
|
36
|
+
!(RESERVED_RE =~ chr)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.remove_dot_segments(path)
|
40
|
+
result_segments = []
|
41
|
+
|
42
|
+
while path.length > 0
|
43
|
+
if path.start_with?("../")
|
44
|
+
path = path[3..-1]
|
45
|
+
elsif path.start_with?("./")
|
46
|
+
path = path[2..-1]
|
47
|
+
elsif path.start_with?("/./")
|
48
|
+
path = path[2..-1]
|
49
|
+
elsif path == "/."
|
50
|
+
path = "/"
|
51
|
+
elsif path.start_with?("/../")
|
52
|
+
path = path[3..-1]
|
53
|
+
result_segments.pop if result_segments.length > 0
|
54
|
+
elsif path == "/.."
|
55
|
+
path = "/"
|
56
|
+
result_segments.pop if result_segments.length > 0
|
57
|
+
elsif ["..", "."].include?(path)
|
58
|
+
path = ""
|
59
|
+
else
|
60
|
+
i = 0
|
61
|
+
i = 1 if path[0].chr == "/"
|
62
|
+
i = path.index("/", i)
|
63
|
+
i = path.length if i.nil?
|
64
|
+
result_segments << path[0...i]
|
65
|
+
path = path[i..-1]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
result_segments.join("")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|