furi 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,57 @@
1
+ module Furi
2
+ class QueryToken
3
+ attr_reader :name, :value
4
+
5
+ def self.parse(token)
6
+ case token
7
+ when QueryToken
8
+ token
9
+ when String
10
+ key, value = token.split('=', 2).map do |s|
11
+ ::URI.decode_www_form_component(s)
12
+ end
13
+ key ||= ""
14
+ new(key, value)
15
+ when Array
16
+ QueryToken.new(*token)
17
+ else
18
+ raise_parse_error(token)
19
+ end
20
+ end
21
+
22
+ def self.raise_parse_error(token)
23
+ raise ArgumentError, "Can not parse query token #{token.inspect}"
24
+ end
25
+
26
+ def initialize(name, value)
27
+ @name = name
28
+ @value = value
29
+ end
30
+
31
+ def to_a
32
+ [name, value]
33
+ end
34
+
35
+ def ==(other)
36
+ other = self.class.parse(other)
37
+ return false unless other
38
+ to_s == other.to_s
39
+ end
40
+
41
+ def to_s
42
+ encoded_key = ::URI.encode_www_form_component(name.to_s)
43
+
44
+ !value.nil? ?
45
+ "#{encoded_key}=#{::URI.encode_www_form_component(value.to_s)}" :
46
+ encoded_key
47
+ end
48
+
49
+ def as_json(options = nil)
50
+ to_a
51
+ end
52
+
53
+ def inspect
54
+ [name, value].join('=')
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,533 @@
1
+ module Furi
2
+ class Uri
3
+
4
+ attr_reader(*Furi::ESSENTIAL_PARTS)
5
+
6
+ Furi::ALIASES.each do |origin, aliases|
7
+ aliases.each do |aliaz|
8
+ define_method(aliaz) do
9
+ self[origin]
10
+ end
11
+
12
+ define_method(:"#{aliaz}=") do |arg|
13
+ self[origin] = arg
14
+ end
15
+ end
16
+ end
17
+
18
+ def initialize(argument)
19
+ @query_tokens = []
20
+ case argument
21
+ when String
22
+ parse_uri_string(argument)
23
+ when Hash
24
+ update(argument)
25
+ end
26
+ end
27
+
28
+ def update(parts)
29
+ parts.each do |part, value|
30
+ self[part] = value
31
+ end
32
+ self
33
+ end
34
+
35
+ def merge(parts)
36
+ parts.each do |part, value|
37
+ case part.to_sym
38
+ when :query, :query_tokens
39
+ merge_query(value)
40
+ else
41
+ self[part] = value
42
+ end
43
+ end
44
+ self
45
+ end
46
+
47
+ def defaults(parts)
48
+ parts.each do |part, value|
49
+ case part.to_sym
50
+ when :query, :query_tokens
51
+ Furi.parse_query(value).each do |key, default_value|
52
+ unless query.key?(key)
53
+ query[key] = default_value
54
+ end
55
+ end
56
+ else
57
+ unless self[part]
58
+ self[part] = value
59
+ end
60
+ end
61
+ end
62
+ self
63
+ end
64
+
65
+ def merge_query(query)
66
+ case query
67
+ when Hash
68
+ self.query = self.query.merge(Furi::Utils.stringify_keys(query))
69
+ when String, Array
70
+ self.query_tokens += Furi.query_tokens(query)
71
+ when nil
72
+ else
73
+ raise ArgumentError, "#{query.inspect} can not be merged"
74
+ end
75
+ end
76
+
77
+ def userinfo
78
+ if username
79
+ [username, password].compact.join(":")
80
+ elsif password
81
+ raise Furi::FormattingError, "can not build URI with password but without username"
82
+ else
83
+ nil
84
+ end
85
+ end
86
+
87
+ def host=(host)
88
+ @host = case host
89
+ when Array
90
+ join_domain(host)
91
+ when "", nil
92
+ nil
93
+ else
94
+ host.to_s
95
+ end
96
+ end
97
+
98
+ def domainzone
99
+ parsed_host.last
100
+ end
101
+
102
+ def domainzone=(new_zone)
103
+ self.host = [subdomain, domainname, new_zone]
104
+ end
105
+
106
+ def domainname
107
+ parsed_host[1]
108
+ end
109
+
110
+ def domainname=(new_domainname)
111
+ self.domain = join_domain([subdomain, new_domainname, domainzone])
112
+ end
113
+
114
+ def domain
115
+ join_domain(parsed_host[1..2].flatten)
116
+ end
117
+
118
+ def domain=(new_domain)
119
+ self.host= [subdomain, new_domain]
120
+ end
121
+
122
+ def subdomain
123
+ parsed_host.first
124
+ end
125
+
126
+ def subdomain=(new_subdomain)
127
+ self.host = [new_subdomain, domain]
128
+ end
129
+
130
+ def hostinfo
131
+ return host unless explicit_port?
132
+ if port && !host
133
+ raise Furi::FormattingError, "can not build URI with port but without host"
134
+ end
135
+ [host, port].join(":")
136
+ end
137
+
138
+ def hostinfo=(string)
139
+ if string.match(%r{\A\[.+\]\z}) #ipv6 host
140
+ self.host = string
141
+ else
142
+ if match = string.match(/\A(.+):(.*)\z/)
143
+ self.host, self.port = match.captures
144
+ else
145
+ self.host = string
146
+ self.port = nil
147
+ end
148
+ end
149
+ end
150
+
151
+ def authority
152
+ return hostinfo unless userinfo
153
+ [userinfo, hostinfo].join("@")
154
+ end
155
+
156
+ def authority=(string)
157
+ if string.include?("@")
158
+ userinfo, string = string.split("@", 2)
159
+ self.userinfo = userinfo
160
+ else
161
+ self.userinfo = nil
162
+ end
163
+ self.hostinfo = string
164
+ end
165
+
166
+ def to_s
167
+ result = []
168
+ result << location
169
+ result << (host ? path : path!)
170
+ if query_tokens.any?
171
+ result << "?" << query_string
172
+ end
173
+ if anchor
174
+ result << "#" << anchor
175
+ end
176
+ result.join
177
+ end
178
+
179
+ def location
180
+ if protocol
181
+ unless host
182
+ raise Furi::FormattingError, "can not build URI with protocol but without host"
183
+ end
184
+ [protocol.empty? ? "" : "#{protocol}:", authority].join("//")
185
+ else
186
+ authority
187
+ end
188
+ end
189
+
190
+ def location=(string)
191
+ string ||= ""
192
+ string = string.gsub(%r(/\Z), '')
193
+ self.protocol = nil
194
+ string = parse_protocol(string)
195
+ self.authority = string
196
+ end
197
+
198
+ def request
199
+ result = []
200
+ result << path!
201
+ result << "?" << query_string if query_tokens.any?
202
+ result.join
203
+ end
204
+
205
+ def request=(string)
206
+ string = parse_anchor_and_query(string)
207
+ self.path = string
208
+ end
209
+
210
+ def home_page?
211
+ path! == Furi::ROOT || path! == "/index.html"
212
+ end
213
+
214
+ def query
215
+ return @query if query_level?
216
+ @query = Furi.parse_query(query_tokens)
217
+ end
218
+
219
+
220
+ def query=(value)
221
+ @query = nil
222
+ @query_tokens = []
223
+ case value
224
+ when String, Array
225
+ self.query_tokens = value
226
+ when Hash
227
+ self.query_tokens = value
228
+ @query = value
229
+ when nil
230
+ else
231
+ raise ArgumentError, 'Query can only be Hash or String'
232
+ end
233
+ end
234
+
235
+ def port=(port)
236
+ @port = case port
237
+ when String
238
+ if port.empty?
239
+ nil
240
+ else
241
+ unless port =~ /\A\s*\d+\s*\z/
242
+ raise ArgumentError, "port should be an Integer >= 0"
243
+ end
244
+ port.to_i
245
+ end
246
+ when Integer
247
+ if port < 0
248
+ raise ArgumentError, "port should be an Integer >= 0"
249
+ end
250
+ port
251
+ when nil
252
+ nil
253
+ else
254
+ raise ArgumentError, "can not parse port: #{port.inspect}"
255
+ end
256
+ @port
257
+ end
258
+
259
+ def query_tokens=(tokens)
260
+ @query = nil
261
+ @query_tokens = Furi.query_tokens(tokens)
262
+ end
263
+
264
+ def username=(username)
265
+ @username = username.nil? ? nil : username.to_s
266
+ end
267
+
268
+ def password=(password)
269
+ @password = password.nil? ? nil : password.to_s
270
+ end
271
+
272
+ def userinfo=(userinfo)
273
+ username, password = (userinfo || "").split(":", 2)
274
+ self.username = username
275
+ self.password = password
276
+ end
277
+
278
+ def path=(path)
279
+ @path = path.to_s
280
+ if !@path.empty? && !@path.start_with?("/")
281
+ @path = "/" + @path
282
+ end
283
+ end
284
+
285
+ def protocol=(protocol)
286
+ @protocol = protocol ? protocol.gsub(%r{:?/?/?\Z}, "") : nil
287
+ end
288
+
289
+
290
+ def directory
291
+ path_tokens[0..-2].join("/")
292
+ end
293
+
294
+ def directory=(string)
295
+ string ||= "/"
296
+ if filename && string !~ %r{/\z}
297
+ string += '/'
298
+ end
299
+ self.path = string + filename.to_s
300
+ end
301
+
302
+ def extension
303
+ return nil unless filename
304
+ file_tokens.size > 1 ? file_tokens.last : nil
305
+ end
306
+
307
+ def extension=(string)
308
+ tokens = file_tokens
309
+ case tokens.size
310
+ when 0
311
+ raise Furi::FormattingError, "can not assign extension when there is no filename"
312
+ when 1
313
+ tokens.push(string)
314
+ else
315
+ if string
316
+ tokens[-1] = string
317
+ else
318
+ tokens.pop
319
+ end
320
+ end
321
+ self.filename = tokens.join(".")
322
+ end
323
+
324
+ def filename=(name)
325
+ unless name
326
+ return unless path
327
+ else
328
+ name = name.gsub(%r{\A/}, "")
329
+ end
330
+
331
+ self.path = path_tokens.tap do |p|
332
+ filename_index = [p.size-1, 0].max
333
+ p[filename_index] = name
334
+ end.join("/")
335
+ end
336
+
337
+ def path_tokens
338
+ return [] unless path
339
+ path.split("/", -1)
340
+ end
341
+
342
+
343
+ def query_string
344
+ if query_level?
345
+ Furi.serialize(query)
346
+ else
347
+ query_tokens.any? ? query_tokens.join("&") : nil
348
+ end
349
+ end
350
+
351
+ def query_string!
352
+ query_string || ""
353
+ end
354
+
355
+ def query_string=(string)
356
+ self.query_tokens = string.to_s
357
+ end
358
+
359
+ def port!
360
+ port || default_port
361
+ end
362
+
363
+ def default_port
364
+ protocol && Furi::PROTOCOLS[protocol] ? Furi::PROTOCOLS[protocol][:port] : nil
365
+ end
366
+
367
+ def ssl?
368
+ !!(protocol && Furi::PROTOCOLS[protocol][:ssl])
369
+ end
370
+
371
+ def ssl
372
+ ssl?
373
+ end
374
+
375
+ def ssl=(ssl)
376
+ self.protocol = find_protocol_for_ssl(ssl)
377
+ end
378
+
379
+ def filename
380
+ result = path_tokens.last
381
+ result == "" ? nil : result
382
+ end
383
+
384
+ def filename!
385
+ filename || ''
386
+ end
387
+
388
+ def default_web_port?
389
+ Furi::WEB_PROTOCOL.any? do |web_protocol|
390
+ Furi::PROTOCOLS[web_protocol][:port] == port!
391
+ end
392
+ end
393
+
394
+ def web_protocol?
395
+ Furi::WEB_PROTOCOL.include?(protocol)
396
+ end
397
+
398
+ def resource
399
+ [request, anchor].compact.join("#")
400
+ end
401
+
402
+ def resource=(value)
403
+ self.anchor = nil
404
+ self.query_tokens = []
405
+ self.path = nil
406
+ value = parse_anchor_and_query(value)
407
+ self.path = value
408
+ end
409
+
410
+ def path!
411
+ path || Furi::ROOT
412
+ end
413
+
414
+ def resource!
415
+ [request]
416
+ end
417
+
418
+ def host!
419
+ host || ""
420
+ end
421
+
422
+ def ==(other)
423
+ to_s == other.to_s
424
+ end
425
+
426
+ def inspect
427
+ "#<#{self.class} #{to_s.inspect}>"
428
+ end
429
+
430
+ def anchor=(string)
431
+ string = string.to_s
432
+ @anchor = string.empty? ? nil : string
433
+ end
434
+
435
+ def [](part)
436
+ send(part)
437
+ end
438
+
439
+ def []=(part, value)
440
+ send(:"#{part}=", value)
441
+ end
442
+
443
+ def rfc3986?
444
+ uri = to_s
445
+ !!(uri.match(URI::RFC3986_Parser::RFC3986_Parser) ||
446
+ uri.match(URI::RFC3986_Parser::RFC3986_relative_ref))
447
+ end
448
+
449
+ protected
450
+
451
+ def file_tokens
452
+ filename ? filename.split('.') : []
453
+ end
454
+
455
+ def query_level?
456
+ !!@query
457
+ end
458
+
459
+ def explicit_port?
460
+ port && port != default_port
461
+ end
462
+
463
+ def parse_uri_string(string)
464
+ string = parse_anchor_and_query(string)
465
+
466
+ string = parse_protocol(string)
467
+
468
+ if string.include?("/")
469
+ string, path = string.split("/", 2)
470
+ self.path = "/" + path
471
+ end
472
+
473
+ self.authority = string
474
+ end
475
+
476
+ def find_protocol_for_ssl(ssl)
477
+ if Furi::SSL_MAPPING.key?(protocol)
478
+ ssl ? Furi::SSL_MAPPING[protocol] : protocol
479
+ elsif Furi::SSL_MAPPING.values.include?(protocol)
480
+ ssl ? protocol : Furi::SSL_MAPPING.invert[protocol]
481
+ else
482
+ raise ArgumentError, "Can not specify SSL for #{protocol.inspect} protocol"
483
+ end
484
+ end
485
+
486
+ def join_domain(tokens)
487
+ tokens = tokens.compact
488
+ tokens.any? ? tokens.join(".") : nil
489
+ end
490
+
491
+ def parse_anchor_and_query(string)
492
+ string ||= ''
493
+ string, *anchor = string.split("#")
494
+ self.anchor = anchor.join("#")
495
+ if string && string.include?("?")
496
+ string, query_string = string.split("?", 2)
497
+ self.query_tokens = query_string
498
+ end
499
+ string
500
+ end
501
+
502
+ def parse_protocol(string)
503
+ if string.include?("://")
504
+ protocol, string = string.split(":", 2)
505
+ self.protocol = protocol
506
+ end
507
+ if string.start_with?("//")
508
+ self.protocol ||= ''
509
+ string = string[2..-1]
510
+ end
511
+ string
512
+ end
513
+
514
+ def parsed_host
515
+ return @parsed_host if @parsed_host
516
+ tokens = host_tokens
517
+ zone = []
518
+ subdomain = []
519
+ while tokens.any? && tokens.last.size <= 3 && tokens.size >= 2
520
+ zone.unshift tokens.pop
521
+ end
522
+ while tokens.size > 1
523
+ subdomain << tokens.shift
524
+ end
525
+ domainname = tokens.first
526
+ @parsed_host = [join_domain(subdomain), domainname, join_domain(zone)]
527
+ end
528
+
529
+ def host_tokens
530
+ host.split(".")
531
+ end
532
+ end
533
+ end