addressable 2.2.8 → 2.3.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of addressable might be problematic. Click here for more details.
- data/CHANGELOG.md +14 -0
- data/README.md +7 -3
- data/Rakefile +2 -2
- data/data/unicode.data +0 -0
- data/lib/addressable/idna/pure.rb +7 -4235
- data/lib/addressable/template.rb +334 -543
- data/lib/addressable/uri.rb +117 -160
- data/lib/addressable/version.rb +2 -2
- data/spec/addressable/idna_spec.rb +15 -0
- data/spec/addressable/template_spec.rb +920 -2063
- data/spec/addressable/uri_spec.rb +265 -129
- metadata +5 -27
- data/Gemfile.lock +0 -35
data/lib/addressable/uri.rb
CHANGED
@@ -120,7 +120,11 @@ module Addressable
|
|
120
120
|
user = userinfo.strip[/^([^:]*):?/, 1]
|
121
121
|
password = userinfo.strip[/:(.*)$/, 1]
|
122
122
|
end
|
123
|
-
host = authority.gsub(
|
123
|
+
host = authority.gsub(
|
124
|
+
/^([^\[\]]*)@/, EMPTY_STR
|
125
|
+
).gsub(
|
126
|
+
/:([^:@\[\]]*?)$/, EMPTY_STR
|
127
|
+
)
|
124
128
|
port = authority[/:([^:@\[\]]*?)$/, 1]
|
125
129
|
end
|
126
130
|
if port == EMPTY_STR
|
@@ -177,6 +181,8 @@ module Addressable
|
|
177
181
|
uri.gsub!(/^feed:\/+/, "feed://")
|
178
182
|
when /^file:\/+/
|
179
183
|
uri.gsub!(/^file:\/+/, "file:///")
|
184
|
+
when /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
|
185
|
+
uri.gsub!(/^/, hints[:scheme] + "://")
|
180
186
|
end
|
181
187
|
parsed = self.parse(uri)
|
182
188
|
if parsed.scheme =~ /^[^\/?#\.]+\.[^\/?#]+$/
|
@@ -365,7 +371,7 @@ module Addressable
|
|
365
371
|
# @param [String, Addressable::URI, #to_str] uri
|
366
372
|
# The URI or component to unencode.
|
367
373
|
#
|
368
|
-
# @param [Class]
|
374
|
+
# @param [Class] return_type
|
369
375
|
# The type of object to return.
|
370
376
|
# This value may only be set to <code>String</code> or
|
371
377
|
# <code>Addressable::URI</code>. All other values are invalid. Defaults
|
@@ -373,8 +379,9 @@ module Addressable
|
|
373
379
|
#
|
374
380
|
# @return [String, Addressable::URI]
|
375
381
|
# The unencoded component or URI.
|
376
|
-
# The return type is determined by the <code>
|
377
|
-
|
382
|
+
# The return type is determined by the <code>return_type</code>
|
383
|
+
# parameter.
|
384
|
+
def self.unencode(uri, return_type=String)
|
378
385
|
return nil if uri.nil?
|
379
386
|
|
380
387
|
begin
|
@@ -382,18 +389,18 @@ module Addressable
|
|
382
389
|
rescue NoMethodError, TypeError
|
383
390
|
raise TypeError, "Can't convert #{uri.class} into String."
|
384
391
|
end if !uri.is_a? String
|
385
|
-
if ![String, ::Addressable::URI].include?(
|
392
|
+
if ![String, ::Addressable::URI].include?(return_type)
|
386
393
|
raise TypeError,
|
387
394
|
"Expected Class (String or Addressable::URI), " +
|
388
|
-
"got #{
|
395
|
+
"got #{return_type.inspect}"
|
389
396
|
end
|
390
397
|
result = uri.gsub(/%[0-9a-f]{2}/i) do |sequence|
|
391
398
|
sequence[1..3].to_i(16).chr
|
392
399
|
end
|
393
400
|
result.force_encoding("utf-8") if result.respond_to?(:force_encoding)
|
394
|
-
if
|
401
|
+
if return_type == String
|
395
402
|
return result
|
396
|
-
elsif
|
403
|
+
elsif return_type == ::Addressable::URI
|
397
404
|
return ::Addressable::URI.parse(result)
|
398
405
|
end
|
399
406
|
end
|
@@ -478,7 +485,7 @@ module Addressable
|
|
478
485
|
# @param [String, Addressable::URI, #to_str] uri
|
479
486
|
# The URI to encode.
|
480
487
|
#
|
481
|
-
# @param [Class]
|
488
|
+
# @param [Class] return_type
|
482
489
|
# The type of object to return.
|
483
490
|
# This value may only be set to <code>String</code> or
|
484
491
|
# <code>Addressable::URI</code>. All other values are invalid. Defaults
|
@@ -486,8 +493,9 @@ module Addressable
|
|
486
493
|
#
|
487
494
|
# @return [String, Addressable::URI]
|
488
495
|
# The encoded URI.
|
489
|
-
# The return type is determined by the <code>
|
490
|
-
|
496
|
+
# The return type is determined by the <code>return_type</code>
|
497
|
+
# parameter.
|
498
|
+
def self.encode(uri, return_type=String)
|
491
499
|
return nil if uri.nil?
|
492
500
|
|
493
501
|
begin
|
@@ -496,10 +504,10 @@ module Addressable
|
|
496
504
|
raise TypeError, "Can't convert #{uri.class} into String."
|
497
505
|
end if !uri.is_a? String
|
498
506
|
|
499
|
-
if ![String, ::Addressable::URI].include?(
|
507
|
+
if ![String, ::Addressable::URI].include?(return_type)
|
500
508
|
raise TypeError,
|
501
509
|
"Expected Class (String or Addressable::URI), " +
|
502
|
-
"got #{
|
510
|
+
"got #{return_type.inspect}"
|
503
511
|
end
|
504
512
|
uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
|
505
513
|
encoded_uri = Addressable::URI.new(
|
@@ -514,9 +522,9 @@ module Addressable
|
|
514
522
|
:fragment => self.encode_component(uri_object.fragment,
|
515
523
|
Addressable::URI::CharacterClasses::FRAGMENT)
|
516
524
|
)
|
517
|
-
if
|
525
|
+
if return_type == String
|
518
526
|
return encoded_uri.to_s
|
519
|
-
elsif
|
527
|
+
elsif return_type == ::Addressable::URI
|
520
528
|
return encoded_uri
|
521
529
|
end
|
522
530
|
end
|
@@ -532,7 +540,7 @@ module Addressable
|
|
532
540
|
# @param [String, Addressable::URI, #to_str] uri
|
533
541
|
# The URI to encode.
|
534
542
|
#
|
535
|
-
# @param [Class]
|
543
|
+
# @param [Class] return_type
|
536
544
|
# The type of object to return.
|
537
545
|
# This value may only be set to <code>String</code> or
|
538
546
|
# <code>Addressable::URI</code>. All other values are invalid. Defaults
|
@@ -540,18 +548,19 @@ module Addressable
|
|
540
548
|
#
|
541
549
|
# @return [String, Addressable::URI]
|
542
550
|
# The encoded URI.
|
543
|
-
# The return type is determined by the <code>
|
544
|
-
|
551
|
+
# The return type is determined by the <code>return_type</code>
|
552
|
+
# parameter.
|
553
|
+
def self.normalized_encode(uri, return_type=String)
|
545
554
|
begin
|
546
555
|
uri = uri.to_str
|
547
556
|
rescue NoMethodError, TypeError
|
548
557
|
raise TypeError, "Can't convert #{uri.class} into String."
|
549
558
|
end if !uri.is_a? String
|
550
559
|
|
551
|
-
if ![String, ::Addressable::URI].include?(
|
560
|
+
if ![String, ::Addressable::URI].include?(return_type)
|
552
561
|
raise TypeError,
|
553
562
|
"Expected Class (String or Addressable::URI), " +
|
554
|
-
"got #{
|
563
|
+
"got #{return_type.inspect}"
|
555
564
|
end
|
556
565
|
uri_object = uri.kind_of?(self) ? uri : self.parse(uri)
|
557
566
|
components = {
|
@@ -591,9 +600,9 @@ module Addressable
|
|
591
600
|
:fragment => self.encode_component(components[:fragment],
|
592
601
|
Addressable::URI::CharacterClasses::FRAGMENT)
|
593
602
|
)
|
594
|
-
if
|
603
|
+
if return_type == String
|
595
604
|
return encoded_uri.to_s
|
596
|
-
elsif
|
605
|
+
elsif return_type == ::Addressable::URI
|
597
606
|
return encoded_uri
|
598
607
|
end
|
599
608
|
end
|
@@ -1019,6 +1028,14 @@ module Addressable
|
|
1019
1028
|
end
|
1020
1029
|
@host = new_host ? new_host.to_str : nil
|
1021
1030
|
|
1031
|
+
unreserved = CharacterClasses::UNRESERVED
|
1032
|
+
sub_delims = CharacterClasses::SUB_DELIMS
|
1033
|
+
if @host != nil && (@host =~ /[<>{}\/\?\#\@]/ ||
|
1034
|
+
(@host[/^\[(.*)\]$/, 1] != nil && @host[/^\[(.*)\]$/, 1] !~
|
1035
|
+
Regexp.new("^[#{unreserved}#{sub_delims}:]*$")))
|
1036
|
+
raise InvalidURIError, "Invalid character in host: '#{@host.to_s}'"
|
1037
|
+
end
|
1038
|
+
|
1022
1039
|
# Reset dependant values
|
1023
1040
|
@authority = nil
|
1024
1041
|
@normalized_host = nil
|
@@ -1089,8 +1106,11 @@ module Addressable
|
|
1089
1106
|
new_user = new_userinfo.strip[/^([^:]*):?/, 1]
|
1090
1107
|
new_password = new_userinfo.strip[/:(.*)$/, 1]
|
1091
1108
|
end
|
1092
|
-
new_host =
|
1093
|
-
|
1109
|
+
new_host = new_authority.gsub(
|
1110
|
+
/^([^\[\]]*)@/, EMPTY_STR
|
1111
|
+
).gsub(
|
1112
|
+
/:([^:@\[\]]*?)$/, EMPTY_STR
|
1113
|
+
)
|
1094
1114
|
new_port =
|
1095
1115
|
new_authority[/:([^:@\[\]]*?)$/, 1]
|
1096
1116
|
end
|
@@ -1201,16 +1221,22 @@ module Addressable
|
|
1201
1221
|
# @return [Integer] The inferred port component.
|
1202
1222
|
def inferred_port
|
1203
1223
|
if self.port.to_i == 0
|
1204
|
-
|
1205
|
-
URI.port_mapping[self.scheme.strip.downcase]
|
1206
|
-
else
|
1207
|
-
nil
|
1208
|
-
end
|
1224
|
+
self.default_port
|
1209
1225
|
else
|
1210
1226
|
self.port.to_i
|
1211
1227
|
end
|
1212
1228
|
end
|
1213
1229
|
|
1230
|
+
##
|
1231
|
+
# The default port for this URI's scheme.
|
1232
|
+
# This method will always returns the default port for the URI's scheme
|
1233
|
+
# regardless of the presence of an explicit port in the URI.
|
1234
|
+
#
|
1235
|
+
# @return [Integer] The default port.
|
1236
|
+
def default_port
|
1237
|
+
URI.port_mapping[self.scheme.strip.downcase] if self.scheme
|
1238
|
+
end
|
1239
|
+
|
1214
1240
|
##
|
1215
1241
|
# The combination of components that represent a site.
|
1216
1242
|
# Combines the scheme, user, password, host, and port components.
|
@@ -1391,114 +1417,72 @@ module Addressable
|
|
1391
1417
|
##
|
1392
1418
|
# Converts the query component to a Hash value.
|
1393
1419
|
#
|
1394
|
-
# @
|
1395
|
-
#
|
1396
|
-
# <code>:subscript</code>. The <code>:dot</code> notation is not
|
1397
|
-
# supported for assignment. Default value is <code>:subscript</code>.
|
1420
|
+
# @param [Class] return_type The return type desired. Value must be either
|
1421
|
+
# `Hash` or `Array`.
|
1398
1422
|
#
|
1399
1423
|
# @return [Hash, Array] The query string parsed as a Hash or Array object.
|
1400
1424
|
#
|
1401
1425
|
# @example
|
1402
1426
|
# Addressable::URI.parse("?one=1&two=2&three=3").query_values
|
1403
1427
|
# #=> {"one" => "1", "two" => "2", "three" => "3"}
|
1404
|
-
# Addressable::URI.parse("?one
|
1405
|
-
# #=>
|
1406
|
-
# Addressable::URI.parse("?one
|
1407
|
-
#
|
1408
|
-
|
1409
|
-
|
1410
|
-
|
1411
|
-
|
1412
|
-
# )
|
1413
|
-
# #=> {"one[two][three]" => "four"}
|
1414
|
-
# Addressable::URI.parse("?one.two.three=four").query_values(
|
1415
|
-
# :notation => :flat
|
1416
|
-
# )
|
1417
|
-
# #=> {"one.two.three" => "four"}
|
1418
|
-
# Addressable::URI.parse(
|
1419
|
-
# "?one[two][three][]=four&one[two][three][]=five"
|
1420
|
-
# ).query_values
|
1421
|
-
# #=> {"one" => {"two" => {"three" => ["four", "five"]}}}
|
1422
|
-
# Addressable::URI.parse(
|
1423
|
-
# "?one=two&one=three").query_values(:notation => :flat_array)
|
1424
|
-
# #=> [['one', 'two'], ['one', 'three']]
|
1425
|
-
def query_values(options={})
|
1426
|
-
defaults = {:notation => :subscript}
|
1427
|
-
options = defaults.merge(options)
|
1428
|
-
if ![:flat, :dot, :subscript, :flat_array].include?(options[:notation])
|
1429
|
-
raise ArgumentError,
|
1430
|
-
"Invalid notation. Must be one of: " +
|
1431
|
-
"[:flat, :dot, :subscript, :flat_array]."
|
1432
|
-
end
|
1433
|
-
dehash = lambda do |hash|
|
1434
|
-
hash.each do |(key, value)|
|
1435
|
-
if value.kind_of?(Hash)
|
1436
|
-
hash[key] = dehash.call(value)
|
1437
|
-
end
|
1438
|
-
end
|
1439
|
-
if hash != {} && hash.keys.all? { |key| key =~ /^\d+$/ }
|
1440
|
-
hash.sort.inject([]) do |accu, (_, value)|
|
1441
|
-
accu << value; accu
|
1442
|
-
end
|
1443
|
-
else
|
1444
|
-
hash
|
1445
|
-
end
|
1428
|
+
# Addressable::URI.parse("?one=two&one=three").query_values(Array)
|
1429
|
+
# #=> [["one", "two"], ["one", "three"]]
|
1430
|
+
# Addressable::URI.parse("?one=two&one=three").query_values(Hash)
|
1431
|
+
# #=> {"one" => "three"}
|
1432
|
+
def query_values(return_type=Hash)
|
1433
|
+
empty_accumulator = Array == return_type ? [] : {}
|
1434
|
+
if return_type != Hash && return_type != Array
|
1435
|
+
raise ArgumentError, "Invalid return type. Must be Hash or Array."
|
1446
1436
|
end
|
1447
1437
|
return nil if self.query == nil
|
1448
|
-
|
1449
|
-
return ((self.query.split("&").map do |pair|
|
1438
|
+
split_query = (self.query.split("&").map do |pair|
|
1450
1439
|
pair.split("=", 2) if pair && !pair.empty?
|
1451
|
-
end).compact
|
1452
|
-
|
1453
|
-
key
|
1454
|
-
|
1455
|
-
|
1440
|
+
end).compact
|
1441
|
+
return split_query.inject(empty_accumulator.dup) do |accu, pair|
|
1442
|
+
# I'd rather use key/value identifiers instead of array lookups,
|
1443
|
+
# but in this case I really want to maintain the exact pair structure,
|
1444
|
+
# so it's best to make all changes in-place.
|
1445
|
+
pair[0] = URI.unencode_component(pair[0])
|
1446
|
+
# This looks weird, but it's correct. Handles query values like:
|
1447
|
+
# ?data=1&flag&color=blue
|
1448
|
+
# In this case, `flag` would evaluate to `true`, which is what you
|
1449
|
+
# want. Its absence implies that `flag` resolves to `false`.
|
1450
|
+
# value = true if value.nil?
|
1451
|
+
if pair[1].respond_to?(:to_str)
|
1452
|
+
# I loathe the fact that I have to do this. Stupid HTML 4.01.
|
1453
|
+
# Treating '+' as a space was just an unbelievably bad idea.
|
1454
|
+
# There was nothing wrong with '%20'!
|
1455
|
+
# If it ain't broke, don't fix it!
|
1456
|
+
pair[1] = URI.unencode_component(pair[1].to_str.gsub(/\+/, " "))
|
1456
1457
|
end
|
1457
|
-
if
|
1458
|
-
|
1459
|
-
raise ArgumentError, "Key was repeated: #{key.inspect}"
|
1460
|
-
end
|
1461
|
-
accumulator[key] = value
|
1462
|
-
elsif options[:notation] == :flat_array
|
1463
|
-
accumulator << [key, value]
|
1458
|
+
if return_type == Hash
|
1459
|
+
accu[pair[0]] = pair[1]
|
1464
1460
|
else
|
1465
|
-
|
1466
|
-
array_value = false
|
1467
|
-
subkeys = key.split(".")
|
1468
|
-
elsif options[:notation] == :subscript
|
1469
|
-
array_value = !!(key =~ /\[\]$/)
|
1470
|
-
subkeys = key.split(/[\[\]]+/)
|
1471
|
-
end
|
1472
|
-
current_hash = accumulator
|
1473
|
-
for i in 0...(subkeys.size - 1)
|
1474
|
-
subkey = subkeys[i]
|
1475
|
-
current_hash[subkey] = {} unless current_hash[subkey]
|
1476
|
-
current_hash = current_hash[subkey]
|
1477
|
-
end
|
1478
|
-
if array_value
|
1479
|
-
current_hash[subkeys.last] = [] unless current_hash[subkeys.last]
|
1480
|
-
current_hash[subkeys.last] << value
|
1481
|
-
else
|
1482
|
-
current_hash[subkeys.last] = value
|
1483
|
-
end
|
1461
|
+
accu << pair
|
1484
1462
|
end
|
1485
|
-
|
1486
|
-
end).inject(empty_accumulator.dup) do |accumulator, (key, value)|
|
1487
|
-
if options[:notation] == :flat_array
|
1488
|
-
accumulator << [key, value]
|
1489
|
-
else
|
1490
|
-
accumulator[key] = value.kind_of?(Hash) ? dehash.call(value) : value
|
1491
|
-
end
|
1492
|
-
accumulator
|
1463
|
+
accu
|
1493
1464
|
end
|
1494
1465
|
end
|
1495
1466
|
|
1496
1467
|
##
|
1497
1468
|
# Sets the query component for this URI from a Hash object.
|
1498
|
-
#
|
1499
|
-
# An empty Hash will result in a nil query.
|
1469
|
+
# An empty Hash or Array will result in an empty query string.
|
1500
1470
|
#
|
1501
1471
|
# @param [Hash, #to_hash, Array] new_query_values The new query values.
|
1472
|
+
#
|
1473
|
+
# @example
|
1474
|
+
# uri.query_values = {:a => "a", :b => ["c", "d", "e"]}
|
1475
|
+
# uri.query
|
1476
|
+
# # => "a=a&b=c&b=d&b=e"
|
1477
|
+
# uri.query_values = [['a', 'a'], ['b', 'c'], ['b', 'd'], ['b', 'e']]
|
1478
|
+
# uri.query
|
1479
|
+
# # => "a=a&b=c&b=d&b=e"
|
1480
|
+
# uri.query_values = [['a', 'a'], ['b', ['c', 'd', 'e']]]
|
1481
|
+
# uri.query
|
1482
|
+
# # => "a=a&b=c&b=d&b=e"
|
1483
|
+
# uri.query_values = [['flag'], ['key', 'value']]
|
1484
|
+
# uri.query
|
1485
|
+
# # => "flag&key=value"
|
1502
1486
|
def query_values=(new_query_values)
|
1503
1487
|
if new_query_values == nil
|
1504
1488
|
self.query = nil
|
@@ -1520,55 +1504,28 @@ module Addressable
|
|
1520
1504
|
new_query_values.sort!
|
1521
1505
|
end
|
1522
1506
|
|
1523
|
-
|
1524
|
-
|
1525
|
-
|
1526
|
-
|
1527
|
-
|
1528
|
-
|
1529
|
-
|
1530
|
-
|
1531
|
-
|
1532
|
-
|
1533
|
-
|
1534
|
-
|
1535
|
-
|
1536
|
-
|
1537
|
-
val
|
1538
|
-
]
|
1539
|
-
end
|
1540
|
-
value.sort!
|
1541
|
-
buffer = ""
|
1542
|
-
value.each do |key, val|
|
1543
|
-
new_parent = "#{parent}[#{key}]"
|
1544
|
-
buffer << "#{to_query.call(new_parent, val)}&"
|
1545
|
-
end
|
1546
|
-
return buffer.chop
|
1547
|
-
elsif value.is_a?(Array)
|
1548
|
-
buffer = ""
|
1549
|
-
value.each_with_index do |val, i|
|
1550
|
-
new_parent = "#{parent}[#{i}]"
|
1551
|
-
buffer << "#{to_query.call(new_parent, val)}&"
|
1507
|
+
# new_query_values have form [['key1', 'value1'], ['key2', 'value2']]
|
1508
|
+
buffer = ""
|
1509
|
+
new_query_values.each do |key, value|
|
1510
|
+
encoded_key = URI.encode_component(
|
1511
|
+
key, CharacterClasses::UNRESERVED
|
1512
|
+
)
|
1513
|
+
if value == nil || value == true
|
1514
|
+
buffer << "#{encoded_key}&"
|
1515
|
+
elsif value.kind_of?(Array)
|
1516
|
+
value.each do |sub_value|
|
1517
|
+
encoded_value = URI.encode_component(
|
1518
|
+
sub_value, CharacterClasses::UNRESERVED
|
1519
|
+
)
|
1520
|
+
buffer << "#{encoded_key}=#{encoded_value}&"
|
1552
1521
|
end
|
1553
|
-
return buffer.chop
|
1554
|
-
elsif value == true
|
1555
|
-
return parent
|
1556
1522
|
else
|
1557
1523
|
encoded_value = URI.encode_component(
|
1558
1524
|
value, CharacterClasses::UNRESERVED
|
1559
1525
|
)
|
1560
|
-
|
1526
|
+
buffer << "#{encoded_key}=#{encoded_value}&"
|
1561
1527
|
end
|
1562
1528
|
end
|
1563
|
-
|
1564
|
-
# new_query_values have form [['key1', 'value1'], ['key2', 'value2']]
|
1565
|
-
buffer = ""
|
1566
|
-
new_query_values.each do |parent, value|
|
1567
|
-
encoded_parent = URI.encode_component(
|
1568
|
-
parent, CharacterClasses::UNRESERVED
|
1569
|
-
)
|
1570
|
-
buffer << "#{to_query.call(encoded_parent, value)}&"
|
1571
|
-
end
|
1572
1529
|
self.query = buffer.chop
|
1573
1530
|
end
|
1574
1531
|
|
data/lib/addressable/version.rb
CHANGED
@@ -196,6 +196,21 @@ describe Addressable::IDNA, "when using the pure-Ruby implementation" do
|
|
196
196
|
|
197
197
|
it_should_behave_like "converting from unicode to ASCII"
|
198
198
|
it_should_behave_like "converting from ASCII to unicode"
|
199
|
+
|
200
|
+
begin
|
201
|
+
require "fiber"
|
202
|
+
|
203
|
+
it "should not blow up inside fibers" do
|
204
|
+
f = Fiber.new do
|
205
|
+
Addressable.send(:remove_const, :IDNA)
|
206
|
+
load "addressable/idna/pure.rb"
|
207
|
+
end
|
208
|
+
f.resume
|
209
|
+
end
|
210
|
+
rescue LoadError
|
211
|
+
# Fibers aren't supported in this version of Ruby, skip this test.
|
212
|
+
warn('Fibers unsupported.')
|
213
|
+
end
|
199
214
|
end
|
200
215
|
|
201
216
|
begin
|