furi 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +105 -7
- data/lib/furi.rb +101 -374
- data/lib/furi/query_token.rb +57 -0
- data/lib/furi/uri.rb +533 -0
- data/lib/furi/utils.rb +16 -0
- data/lib/furi/version.rb +1 -1
- data/spec/furi_spec.rb +339 -46
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6b454ab79a6ac9b95fc85b8b8b2381c8e93715af
|
4
|
+
data.tar.gz: d84272345cd55a7aad224ce2b124423806f38301
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc4227555a121e3c25d7c4ba32765659fa78678ff7aa2b500f0f6cbe384f9507e11bac3828bfd9eae097879e786395ac9b8113049447b5d1a1aff6a834a91108
|
7
|
+
data.tar.gz: 4b8d902ab961bff2d68a369da05c96f1cc142e6d71980a3cd24691de930807057b65016aa9ec16e8d7e52e2cdd5173068d108ac2d0128ced4ac9d497e671adbb
|
data/README.md
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# Furi
|
2
2
|
|
3
|
-
|
3
|
+
Furi is a Friendly URI parsing library.
|
4
|
+
Furi's philosophy is to make any operation possible in ONE LINE OF CODE.
|
5
|
+
|
6
|
+
If there is an operation that takes more than one line of code to do with Furi, this is considered a terrible bug and you should create an issue.
|
4
7
|
|
5
8
|
## Installation
|
6
9
|
|
@@ -20,12 +23,107 @@ Or install it yourself as:
|
|
20
23
|
|
21
24
|
## Usage
|
22
25
|
|
23
|
-
|
26
|
+
I'll say it again: any operation should take exacly one line of code!
|
27
|
+
Here are basic:
|
28
|
+
|
29
|
+
### Utility Methods
|
30
|
+
|
31
|
+
|
32
|
+
``` ruby
|
33
|
+
Furi.host("http://gusiev.com") # => "gusiev.com"
|
34
|
+
Furi.port("http://gusiev.com") # => nil
|
35
|
+
Furi.port!("http://gusiev.com") # => 80
|
36
|
+
|
37
|
+
Furi.update("http://gusiev.com", protocol: '') # => "//gusiev.com"
|
38
|
+
Furi.update("http://gusiev.com?source=google", query: {email: "a@b.com"})
|
39
|
+
# => "http://gusiev.com?email=a@b.com"
|
40
|
+
Furi.merge("http://gusiev.com?source=google", query: {email: "a@b.com"})
|
41
|
+
# => "http://gusiev.com?source=google&email=a@b.com"
|
42
|
+
|
43
|
+
Furi.build(protocol: '//', host: 'gusiev.com', path: '/assets/application.js')
|
44
|
+
# => "//gusiev.com/assets/application.js"
|
45
|
+
|
46
|
+
Furi.defaults("http://gusiev.com", subdomain: 'www') # => "http://www.gusiev.com"
|
47
|
+
```
|
48
|
+
|
49
|
+
### Working with Object
|
50
|
+
|
51
|
+
``` ruby
|
52
|
+
uri = Furi.parse("gusiev.com")
|
53
|
+
# => #<Furi::Uri "gusiev.com">
|
54
|
+
|
55
|
+
uri.port # => nil
|
56
|
+
uri.port! # => 80
|
57
|
+
uri.path # => nil
|
58
|
+
uri.path! # => '/'
|
59
|
+
uri.subdomain ||= 'www'
|
60
|
+
uri.protocol = "//" # protocol abstract URL
|
61
|
+
```
|
62
|
+
|
63
|
+
### Processing Query String
|
64
|
+
|
65
|
+
``` ruby
|
66
|
+
uri = Furi.parse("/?person[first_name]=Bogdan&person[last_name]=Gusiev")
|
67
|
+
|
68
|
+
uri.query_string # => "person[first_name]=Bogdan&person[last_name]=Gusiev"
|
69
|
+
uri.query_tokens # => [person[first_name]=Bogdan, person[last_name]=Gusiev]
|
70
|
+
uri.query # => {person: {first_name: Bogdan, last_name: 'Gusiev'}}
|
71
|
+
|
72
|
+
uri.merge_query(person: {email: 'a@b.com'})
|
73
|
+
# => {person: {email: 'a@b.com', first_name: Bogdan, last_name: 'Gusiev'}}
|
74
|
+
|
75
|
+
uri.merge_query(person: {email: 'a@b.com'})
|
76
|
+
# => {person: {email: 'a@b.com', first_name: Bogdan, last_name: 'Gusiev'}}
|
77
|
+
```
|
78
|
+
|
79
|
+
## Reference
|
80
|
+
|
81
|
+
```
|
82
|
+
location resource
|
83
|
+
| ___|___
|
84
|
+
_______|_______ / \
|
85
|
+
/ \ / \
|
86
|
+
/ authority request \
|
87
|
+
/ __________|_________ | \
|
88
|
+
/ / \ ______|______ \
|
89
|
+
/ userinfo hostinfo / \ \
|
90
|
+
/ __|___ ___|___ / \ \
|
91
|
+
/ / \ / \ / \ \
|
92
|
+
/ username password host port path query anchor
|
93
|
+
/ __|___ __|__ ______|______ | _________|__________ ____|____ |
|
94
|
+
/ / \ / \ / \ / \/ \ / \ / \
|
95
|
+
http://username:zhongguo@www.example.com:80/hello/world/article.html?name=bogdan#info
|
96
|
+
\_/ \_/ \___/ \_/ \__________/ \ \_/
|
97
|
+
| | | | | \ |
|
98
|
+
protocol subdomain | domainzone directory \ extension
|
99
|
+
| | \_____/
|
100
|
+
domainname / |
|
101
|
+
\___/ filename
|
102
|
+
|
|
103
|
+
domain
|
104
|
+
```
|
105
|
+
|
106
|
+
|
107
|
+
Originated from [URI.js](http://medialize.github.io/URI.js/about-uris.html) parsing library.
|
108
|
+
Giving credit...
|
109
|
+
|
110
|
+
|
111
|
+
## TODO
|
112
|
+
|
113
|
+
* rfc3986 validation
|
114
|
+
* mailto protocol
|
115
|
+
* escaping in path
|
116
|
+
* case insensetivity:
|
117
|
+
* domain
|
118
|
+
* protocol
|
119
|
+
* case sensitivity:
|
120
|
+
* path
|
121
|
+
* query
|
122
|
+
* anchor
|
123
|
+
* basic auth data ?
|
24
124
|
|
25
125
|
## Contributing
|
26
126
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
31
|
-
5. Create a new Pull Request
|
127
|
+
Contribute in the way you want. Branch names and other bla-bla-bla do not matter.
|
128
|
+
|
129
|
+
|
data/lib/furi.rb
CHANGED
@@ -3,18 +3,30 @@ require "uri"
|
|
3
3
|
|
4
4
|
module Furi
|
5
5
|
|
6
|
-
|
6
|
+
autoload :QueryToken, 'furi/query_token'
|
7
|
+
autoload :Uri, 'furi/uri'
|
8
|
+
autoload :Utils, 'furi/utils'
|
9
|
+
|
10
|
+
ESSENTIAL_PARTS = [
|
7
11
|
:anchor, :protocol, :query_tokens,
|
8
12
|
:path, :host, :port, :username, :password
|
9
13
|
]
|
14
|
+
COMBINED_PARTS = [
|
15
|
+
:hostinfo, :userinfo, :authority, :ssl, :domain, :domainname,
|
16
|
+
:domainzone, :request, :location, :query,
|
17
|
+
:extension, :filename
|
18
|
+
]
|
19
|
+
PARTS = ESSENTIAL_PARTS + COMBINED_PARTS
|
20
|
+
|
10
21
|
ALIASES = {
|
11
22
|
protocol: [:schema, :scheme],
|
12
23
|
anchor: [:fragment],
|
13
24
|
host: [:hostname],
|
14
25
|
username: [:user],
|
26
|
+
request: [:request_uri]
|
15
27
|
}
|
16
28
|
|
17
|
-
DELEGATES = [:port!, :host
|
29
|
+
DELEGATES = [:port!, :host!, :path!, :home_page?]
|
18
30
|
|
19
31
|
PROTOCOLS = {
|
20
32
|
"http" => {port: 80, ssl: false},
|
@@ -32,6 +44,7 @@ module Furi
|
|
32
44
|
"prospero" => {port: 1525},
|
33
45
|
}
|
34
46
|
|
47
|
+
|
35
48
|
SSL_MAPPING = {
|
36
49
|
'http' => 'https',
|
37
50
|
'ftp' => 'sftp',
|
@@ -42,22 +55,15 @@ module Furi
|
|
42
55
|
|
43
56
|
ROOT = '/'
|
44
57
|
|
45
|
-
|
46
|
-
attr_accessor :protocol
|
47
|
-
|
48
|
-
def initialize
|
49
|
-
@protocol = /^[a-z][a-z0-9.+-]*$/i
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
def self.expressions
|
54
|
-
Expressions.new
|
55
|
-
end
|
56
|
-
|
58
|
+
# Parses a given string and return an URL object
|
57
59
|
def self.parse(argument)
|
58
60
|
Uri.new(argument)
|
59
61
|
end
|
60
62
|
|
63
|
+
# Builds an URL from given parts
|
64
|
+
#
|
65
|
+
# Furi.build(path: "/dashboard", host: 'example.com', protocol: "https")
|
66
|
+
# # => "https://example.com/dashboard"
|
61
67
|
def self.build(argument)
|
62
68
|
Uri.new(argument).to_s
|
63
69
|
end
|
@@ -65,19 +71,99 @@ module Furi
|
|
65
71
|
class << self
|
66
72
|
(PARTS + ALIASES.values.flatten + DELEGATES).each do |part|
|
67
73
|
define_method(part) do |string|
|
68
|
-
Uri.new(string)
|
74
|
+
Uri.new(string)[part]
|
69
75
|
end
|
70
76
|
end
|
71
77
|
end
|
72
78
|
|
79
|
+
# Replaces a given URL string with given parts
|
80
|
+
#
|
81
|
+
# Furi.update("http://gusiev.com", protocol: 'https', subdomain: 'www')
|
82
|
+
# # => "https://www.gusiev.com"
|
73
83
|
def self.update(string, parts)
|
74
84
|
parse(string).update(parts).to_s
|
75
85
|
end
|
76
86
|
|
87
|
+
# Puts the default values for given URL that are not defined
|
88
|
+
#
|
89
|
+
# Furi.defaults("gusiev.com/hello.html", protocol: 'http', path: '/index.html')
|
90
|
+
# # => "http://gusiev.com/hello.html"
|
91
|
+
def self.defaults(string, parts)
|
92
|
+
parse(string).defaults(parts).to_s
|
93
|
+
end
|
94
|
+
|
95
|
+
# Replaces a given URL string with given parts.
|
96
|
+
# Same as update but works different for URL query parameter:
|
97
|
+
# merges newly specified parameters instead of replacing existing ones
|
98
|
+
#
|
99
|
+
# Furi.merge("/hello.html?a=1", host: 'gusiev.com', query: {b: 2})
|
100
|
+
# # => "gusiev.com/hello.html?a=1&b=2"
|
101
|
+
#
|
77
102
|
def self.merge(string, parts)
|
78
103
|
parse(string).merge(parts).to_s
|
79
104
|
end
|
80
105
|
|
106
|
+
|
107
|
+
# Parses a query into nested paramters hash using a rack convension with square brackets.
|
108
|
+
#
|
109
|
+
# Furi.parse_query("a[]=1&a[]=2") # => {a: [1,2]}
|
110
|
+
# Furi.parse_query("p[email]=a&a[two]=2") # => {a: {one: 1, two: 2}}
|
111
|
+
# Furi.parse_query("p[one]=1&a[two]=2") # => {a: {one: 1, two: 2}}
|
112
|
+
# Furi.serialize({p: {name: 'Bogdan Gusiev', email: 'bogdan@example.com', data: {one: 1, two: 2}}})
|
113
|
+
# # => "p%5Bname%5D=Bogdan&p%5Bemail%5D=bogdan%40example.com&p%5Bdata%5D%5Bone%5D=1&p%5Bdata%5D%5Btwo%5D=2"
|
114
|
+
def self.parse_query(query)
|
115
|
+
return Furi::Utils.stringify_keys(query) if query.is_a?(Hash)
|
116
|
+
|
117
|
+
params = {}
|
118
|
+
query_tokens(query).each do |token|
|
119
|
+
parse_query_token(params, token.name, token.value)
|
120
|
+
end
|
121
|
+
|
122
|
+
return params
|
123
|
+
end
|
124
|
+
|
125
|
+
# Parses query key/value pairs from query string and returns them raw
|
126
|
+
# without organising them into hashes and without normalising them.
|
127
|
+
#
|
128
|
+
# Furi.query_tokens("a=1&b=2").map {|k,v| "#{k} -> #{v}"} # => ['a -> 1', 'b -> 2']
|
129
|
+
# Furi.query_tokens("a=1&a=1&a=2").map {|k,v| "#{k} -> #{v}"} # => ['a -> 1', 'a -> 1', 'a -> 2']
|
130
|
+
# Furi.query_tokens("name=Bogdan&email=bogdan%40example.com") # => [name=Bogdan, email=bogdan@example.com]
|
131
|
+
# Furi.query_tokens("a[one]=1&a[two]=2") # => [a[one]=1, a[two]=2]
|
132
|
+
def self.query_tokens(query)
|
133
|
+
case query
|
134
|
+
when Enumerable, Enumerator
|
135
|
+
query.map do |token|
|
136
|
+
QueryToken.parse(token)
|
137
|
+
end
|
138
|
+
when nil, ''
|
139
|
+
[]
|
140
|
+
when String
|
141
|
+
query.gsub(/\A\?/, '').split(/[&;] */n, -1).map do |p|
|
142
|
+
QueryToken.parse(p)
|
143
|
+
end
|
144
|
+
else
|
145
|
+
raise ArgumentError, "Can not parse #{query.inspect} query tokens"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Serializes query parameters into query string.
|
150
|
+
# Optionaly accepts a basic name space.
|
151
|
+
#
|
152
|
+
# Furi.serialize({a: 1, b: 2}) # => "a=1&b=2"
|
153
|
+
# Furi.serialize({a: [1,2]}) # => "a[]=1&a[]=2"
|
154
|
+
# Furi.serialize({a: {b: 1, c:2}}) # => "a[b]=1&a[c]=2"
|
155
|
+
# Furi.serialize({name: 'Bogdan', email: 'bogdan@example.com'}, "person")
|
156
|
+
# # => "person[name]=Bogdan&person[email]=bogdan%40example.com"
|
157
|
+
#
|
158
|
+
def self.serialize(query, namespace = nil)
|
159
|
+
serialize_tokens(query, namespace).join("&")
|
160
|
+
end
|
161
|
+
|
162
|
+
class FormattingError < StandardError
|
163
|
+
end
|
164
|
+
|
165
|
+
protected
|
166
|
+
|
81
167
|
def self.serialize_tokens(query, namespace = nil)
|
82
168
|
case query
|
83
169
|
when Hash
|
@@ -112,42 +198,6 @@ module Furi
|
|
112
198
|
end
|
113
199
|
end
|
114
200
|
|
115
|
-
def self.parse_nested_query(qs)
|
116
|
-
|
117
|
-
params = {}
|
118
|
-
query_tokens(qs).each do |token|
|
119
|
-
parse_query_token(params, token.name, token.value)
|
120
|
-
end
|
121
|
-
|
122
|
-
return params
|
123
|
-
end
|
124
|
-
|
125
|
-
def self.query_tokens(query)
|
126
|
-
case query
|
127
|
-
when Enumerable, Enumerator
|
128
|
-
query.map do |token|
|
129
|
-
case token
|
130
|
-
when QueryToken
|
131
|
-
token
|
132
|
-
when String
|
133
|
-
QueryToken.parse(token)
|
134
|
-
when Array
|
135
|
-
QueryToken.new(*token)
|
136
|
-
else
|
137
|
-
raise ArgumentError, "Can not parse query token #{token.inspect}"
|
138
|
-
end
|
139
|
-
end
|
140
|
-
when nil, ''
|
141
|
-
[]
|
142
|
-
when String
|
143
|
-
query.gsub(/\A\?/, '').split(/[&;] */n).map do |p|
|
144
|
-
QueryToken.parse(p)
|
145
|
-
end
|
146
|
-
else
|
147
|
-
raise ArgumentError, "Can not parse #{query.inspect} query tokens"
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
151
201
|
def self.parse_query_token(params, name, value)
|
152
202
|
name =~ %r(\A[\[\]]*([^\[\]]+)\]*)
|
153
203
|
namespace = $1 || ''
|
@@ -187,327 +237,4 @@ module Furi
|
|
187
237
|
return params
|
188
238
|
end
|
189
239
|
|
190
|
-
def self.serialize(query, namespace = nil)
|
191
|
-
serialize_tokens(query, namespace).join("&")
|
192
|
-
end
|
193
|
-
|
194
|
-
class QueryToken
|
195
|
-
attr_reader :name, :value
|
196
|
-
|
197
|
-
def self.parse(token)
|
198
|
-
k,v = token.split('=', 2).map { |s| ::URI.decode_www_form_component(s) }
|
199
|
-
new(k,v)
|
200
|
-
end
|
201
|
-
|
202
|
-
def initialize(name, value)
|
203
|
-
@name = name
|
204
|
-
@value = value
|
205
|
-
end
|
206
|
-
|
207
|
-
def to_a
|
208
|
-
[name, value]
|
209
|
-
end
|
210
|
-
|
211
|
-
def ==(other)
|
212
|
-
to_s == other.to_s
|
213
|
-
end
|
214
|
-
|
215
|
-
def to_s
|
216
|
-
"#{::URI.encode_www_form_component(name.to_s)}=#{::URI.encode_www_form_component(value.to_s)}"
|
217
|
-
end
|
218
|
-
|
219
|
-
def inspect
|
220
|
-
[name, value].join('=')
|
221
|
-
end
|
222
|
-
end
|
223
|
-
|
224
|
-
class Uri
|
225
|
-
|
226
|
-
attr_reader(*PARTS)
|
227
|
-
|
228
|
-
ALIASES.each do |origin, aliases|
|
229
|
-
aliases.each do |aliaz|
|
230
|
-
define_method(aliaz) do
|
231
|
-
send(origin)
|
232
|
-
end
|
233
|
-
|
234
|
-
define_method(:"#{aliaz}=") do |*args|
|
235
|
-
send(:"#{origin}=", *args)
|
236
|
-
end
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
def initialize(argument)
|
241
|
-
@query_tokens = []
|
242
|
-
case argument
|
243
|
-
when String
|
244
|
-
parse_uri_string(argument)
|
245
|
-
when Hash
|
246
|
-
update(argument)
|
247
|
-
end
|
248
|
-
end
|
249
|
-
|
250
|
-
def update(parts)
|
251
|
-
parts.each do |part, value|
|
252
|
-
send(:"#{part}=", value)
|
253
|
-
end
|
254
|
-
self
|
255
|
-
end
|
256
|
-
|
257
|
-
def merge(parts)
|
258
|
-
parts.each do |part, value|
|
259
|
-
case part.to_sym
|
260
|
-
when :query
|
261
|
-
merge_query(value)
|
262
|
-
else
|
263
|
-
send(:"#{part}=", value)
|
264
|
-
end
|
265
|
-
end
|
266
|
-
self
|
267
|
-
end
|
268
|
-
|
269
|
-
def merge_query(query)
|
270
|
-
case query
|
271
|
-
when Hash
|
272
|
-
self.query = self.query.merge(Furi::Utils.stringify_keys(query))
|
273
|
-
when String, Array
|
274
|
-
self.query_tokens += Furi.query_tokens(query)
|
275
|
-
when nil
|
276
|
-
else
|
277
|
-
raise ArgumentError, "#{query.inspect} can not be merged"
|
278
|
-
end
|
279
|
-
end
|
280
|
-
|
281
|
-
def userinfo
|
282
|
-
if username
|
283
|
-
[username, password].compact.join(":")
|
284
|
-
elsif password
|
285
|
-
raise FormattingError, "can not build URI with password but without username"
|
286
|
-
else
|
287
|
-
nil
|
288
|
-
end
|
289
|
-
end
|
290
|
-
|
291
|
-
def host=(host)
|
292
|
-
@host = host
|
293
|
-
end
|
294
|
-
|
295
|
-
def to_s
|
296
|
-
result = []
|
297
|
-
if protocol
|
298
|
-
result.push(protocol.empty? ? "//" : "#{protocol}://")
|
299
|
-
end
|
300
|
-
if userinfo
|
301
|
-
result << userinfo
|
302
|
-
end
|
303
|
-
result << host if host
|
304
|
-
result << ":" << port if explicit_port
|
305
|
-
result << (host ? path : path!)
|
306
|
-
if query_tokens.any?
|
307
|
-
result << "?" << query_string
|
308
|
-
end
|
309
|
-
if anchor
|
310
|
-
result << "#" << anchor
|
311
|
-
end
|
312
|
-
result.join
|
313
|
-
end
|
314
|
-
|
315
|
-
|
316
|
-
def request
|
317
|
-
result = []
|
318
|
-
result << path!
|
319
|
-
result << "?" << query_string if query_tokens.any?
|
320
|
-
result.join
|
321
|
-
end
|
322
|
-
|
323
|
-
def request_uri
|
324
|
-
request
|
325
|
-
end
|
326
|
-
|
327
|
-
def query
|
328
|
-
return @query if query_level?
|
329
|
-
@query = Furi.parse_nested_query(query_tokens)
|
330
|
-
end
|
331
|
-
|
332
|
-
|
333
|
-
def query=(value)
|
334
|
-
@query = nil
|
335
|
-
@query_tokens = []
|
336
|
-
case value
|
337
|
-
when String, Array
|
338
|
-
@query_tokens = Furi.query_tokens(value)
|
339
|
-
when Hash
|
340
|
-
@query = value
|
341
|
-
@query_tokens = Furi.serialize_tokens(value)
|
342
|
-
when nil
|
343
|
-
else
|
344
|
-
raise ArgumentError, 'Query can only be Hash or String'
|
345
|
-
end
|
346
|
-
end
|
347
|
-
|
348
|
-
def port=(port)
|
349
|
-
if port != nil
|
350
|
-
@port = port.to_i
|
351
|
-
if @port == 0
|
352
|
-
raise ArgumentError, "port should be an Integer > 0"
|
353
|
-
end
|
354
|
-
else
|
355
|
-
@port = nil
|
356
|
-
end
|
357
|
-
@port
|
358
|
-
end
|
359
|
-
|
360
|
-
def query_tokens=(tokens)
|
361
|
-
@query = nil
|
362
|
-
@query_tokens = tokens
|
363
|
-
end
|
364
|
-
|
365
|
-
def username=(username)
|
366
|
-
@username = username.nil? ? nil : username.to_s
|
367
|
-
end
|
368
|
-
|
369
|
-
def password=(password)
|
370
|
-
@password = password.nil? ? nil : password.to_s
|
371
|
-
end
|
372
|
-
|
373
|
-
def path=(path)
|
374
|
-
@path = path.to_s
|
375
|
-
end
|
376
|
-
|
377
|
-
def protocol=(protocol)
|
378
|
-
@protocol = protocol ? protocol.gsub(%r{:/?/?\Z}, "") : nil
|
379
|
-
end
|
380
|
-
|
381
|
-
def query_string
|
382
|
-
if query_level?
|
383
|
-
Furi.serialize(@query)
|
384
|
-
else
|
385
|
-
query_tokens.join("&")
|
386
|
-
end
|
387
|
-
end
|
388
|
-
|
389
|
-
def expressions
|
390
|
-
Furi.expressions
|
391
|
-
end
|
392
|
-
|
393
|
-
def port!
|
394
|
-
port || default_port
|
395
|
-
end
|
396
|
-
|
397
|
-
def default_port
|
398
|
-
protocol && PROTOCOLS[protocol] ? PROTOCOLS[protocol][:port] : nil
|
399
|
-
end
|
400
|
-
|
401
|
-
def ssl?
|
402
|
-
!!(protocol && PROTOCOLS[protocol][:ssl])
|
403
|
-
end
|
404
|
-
|
405
|
-
def ssl
|
406
|
-
ssl?
|
407
|
-
end
|
408
|
-
|
409
|
-
def ssl=(ssl)
|
410
|
-
self.protocol = find_protocol_for_ssl(ssl)
|
411
|
-
end
|
412
|
-
|
413
|
-
def filename
|
414
|
-
path.split("/").last
|
415
|
-
end
|
416
|
-
|
417
|
-
def default_web_port?
|
418
|
-
WEB_PROTOCOL.any? do |web_protocol|
|
419
|
-
PROTOCOLS[web_protocol][:port] == port!
|
420
|
-
end
|
421
|
-
end
|
422
|
-
|
423
|
-
def web_protocol?
|
424
|
-
WEB_PROTOCOL.include?(protocol)
|
425
|
-
end
|
426
|
-
|
427
|
-
def resource
|
428
|
-
[request, anchor].compact.join("#")
|
429
|
-
end
|
430
|
-
|
431
|
-
def path!
|
432
|
-
path || ROOT
|
433
|
-
end
|
434
|
-
|
435
|
-
def host!
|
436
|
-
host || ""
|
437
|
-
end
|
438
|
-
|
439
|
-
def ==(other)
|
440
|
-
to_s == other.to_s
|
441
|
-
end
|
442
|
-
|
443
|
-
protected
|
444
|
-
|
445
|
-
def query_level?
|
446
|
-
!!@query
|
447
|
-
end
|
448
|
-
|
449
|
-
def explicit_port
|
450
|
-
port == default_port ? nil : port
|
451
|
-
end
|
452
|
-
|
453
|
-
def parse_uri_string(string)
|
454
|
-
string, *@anchor = string.split("#")
|
455
|
-
@anchor = @anchor.empty? ? nil : @anchor.join("#")
|
456
|
-
if string.include?("?")
|
457
|
-
string, query_string = string.split("?", 2)
|
458
|
-
self.query_tokens = Furi.query_tokens(query_string)
|
459
|
-
end
|
460
|
-
|
461
|
-
if string.include?("://")
|
462
|
-
@protocol, string = string.split(":", 2)
|
463
|
-
@protocol = '' if @protocol.empty?
|
464
|
-
end
|
465
|
-
if string.start_with?("//")
|
466
|
-
@protocol ||= ''
|
467
|
-
string = string[2..-1]
|
468
|
-
end
|
469
|
-
parse_authority(string)
|
470
|
-
end
|
471
|
-
|
472
|
-
def parse_authority(string)
|
473
|
-
if string.include?("/")
|
474
|
-
string, @path = string.split("/", 2)
|
475
|
-
@path = "/" + @path
|
476
|
-
end
|
477
|
-
|
478
|
-
if string.include?("@")
|
479
|
-
userinfo, string = string.split("@", 2)
|
480
|
-
@username, @password = userinfo.split(":", 2)
|
481
|
-
end
|
482
|
-
host, port = string.split(":", 2)
|
483
|
-
self.host = host if host
|
484
|
-
self.port = port if port
|
485
|
-
end
|
486
|
-
|
487
|
-
def find_protocol_for_ssl(ssl)
|
488
|
-
if SSL_MAPPING.key?(protocol)
|
489
|
-
ssl ? SSL_MAPPING[protocol] : protocol
|
490
|
-
elsif SSL_MAPPING.values.include?(protocol)
|
491
|
-
ssl ? protocol : SSL_MAPPING.invert[protocol]
|
492
|
-
else
|
493
|
-
raise ArgumentError, "Can not specify ssl for #{protocol.inspect} protocol"
|
494
|
-
end
|
495
|
-
end
|
496
|
-
|
497
|
-
end
|
498
|
-
|
499
|
-
class FormattingError < StandardError
|
500
|
-
end
|
501
|
-
|
502
|
-
class Utils
|
503
|
-
class << self
|
504
|
-
def stringify_keys(hash)
|
505
|
-
result = {}
|
506
|
-
hash.each_key do |key|
|
507
|
-
result[key.to_s] = hash[key]
|
508
|
-
end
|
509
|
-
result
|
510
|
-
end
|
511
|
-
end
|
512
|
-
end
|
513
240
|
end
|