furi 0.0.2 → 0.2.3

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