4 # Author:: Akira Yamada <akira@ruby-lang.org>
5 # License:: You can redistribute it and/or modify it under the same term as Ruby.
6 # Revision:: $Id: generic.rb 12860 2007-08-01 03:40:08Z nobu $
14 # Base class for all URI classes.
15 # Implements generic URI syntax as per RFC 2396.
24 # Returns default port
31 self.class.default_port
36 :userinfo, :host, :port, :registry,
43 # Components of the URI in the order.
65 # At first, tries to create a new URI::Generic instance using
66 # URI::Generic::build. But, if exception URI::InvalidComponentError is raised,
67 # then it URI::Escape.escape all URI components and tries again.
72 return self.build(args)
73 rescue InvalidComponentError
74 if args.kind_of?(Array)
75 return self.build(args.collect{|x|
82 elsif args.kind_of?(Hash)
84 args.each do |key, value|
91 return self.build(tmp)
103 # Creates a new URI::Generic instance from components of URI::Generic
104 # with check. Components are: scheme, userinfo, host, port, registry, path,
105 # opaque, query and fragment. You can provide arguments either by an Array or a Hash.
106 # See #new for hash keys to use or for order of array items.
109 if args.kind_of?(Array) &&
110 args.size == ::URI::Generic::COMPONENT.size
112 elsif args.kind_of?(Hash)
113 tmp = ::URI::Generic::COMPONENT.collect do |c|
122 "expected Array of or Hash of components of #{self.class} (#{self.class.component.join(', ')})"
126 return self.new(*tmp)
132 # Protocol scheme, i.e. 'http','ftp','mailto' and so on.
134 # User name and password, i.e. 'sdmitry:bla'
148 # A part of URI after '#' sign
150 # Check arguments [false by default]
154 # Creates a new URI::Generic instance from ``generic'' components without check.
156 def initialize(scheme,
157 userinfo, host, port, registry,
175 self.userinfo = userinfo
181 self.registry = registry
182 self.fragment = fragment
184 self.set_scheme(scheme)
185 self.set_userinfo(userinfo)
189 self.set_query(query)
190 self.set_opaque(opaque)
191 self.set_registry(registry)
192 self.set_fragment(fragment)
194 if @registry && !self.class.use_registry
195 raise InvalidURIError,
196 "the scheme #{@scheme} does not accept registry part: #{@registry} (or bad hostname?)"
199 @scheme.freeze if @scheme
200 self.set_path('') if !@path && !@opaque # (see RFC2396 Section 5.2)
201 self.set_port(self.default_port) if self.default_port && !@port
206 attr_reader :registry
210 attr_reader :fragment
212 # replace self by other URI object
214 if self.class != oth.class
215 raise ArgumentError, "expected #{self.class} object"
218 component.each do |c|
219 self.__send__("#{c}=", oth.__send__(c))
230 raise InvalidComponentError,
231 "bad component(expected scheme component): #{v}"
236 private :check_scheme
241 protected :set_scheme
249 def check_userinfo(user, password = nil)
251 user, password = split_userinfo(user)
254 check_password(password, user)
258 private :check_userinfo
261 if @registry || @opaque
262 raise InvalidURIError,
263 "can not set user with registry or opaque"
269 raise InvalidComponentError,
270 "bad component(expected userinfo component or user component): #{v}"
277 def check_password(v, user = @user)
278 if @registry || @opaque
279 raise InvalidURIError,
280 "can not set password with registry or opaque"
285 raise InvalidURIError,
286 "password component depends user component"
290 raise InvalidComponentError,
291 "bad component(expected user component): #{v}"
296 private :check_password
299 # Sets userinfo, argument is string like 'name:pass'
301 def userinfo=(userinfo)
305 check_userinfo(*userinfo)
306 set_userinfo(*userinfo)
316 def password=(password)
317 check_password(password)
318 set_password(password)
322 def set_userinfo(user, password = nil)
324 user, password = split_userinfo(user)
327 @password = password if password
331 protected :set_userinfo
334 set_userinfo(v, @password)
343 protected :set_password
345 def split_userinfo(ui)
346 return nil, nil unless ui
347 user, password = ui.split(/:/, 2)
349 return user, password
351 private :split_userinfo
353 def escape_userpass(v)
354 v = URI.escape(v, /[@:\/]/o) # RFC 1738 section 3.1 #/
356 private :escape_userpass
364 @user + ':' + @password
379 if @registry || @opaque
380 raise InvalidURIError,
381 "can not set host with registry or opaque"
383 raise InvalidComponentError,
384 "bad component(expected host component): #{v}"
405 if @registry || @opaque
406 raise InvalidURIError,
407 "can not set port with registry or opaque"
408 elsif !v.kind_of?(Fixnum) && PORT !~ v
409 raise InvalidComponentError,
410 "bad component(expected port component): #{v}"
418 unless !v || v.kind_of?(Fixnum)
435 def check_registry(v)
438 # raise if both server and registry are not nil, because:
439 # authority = server | reg_name
440 # server = [ [ userinfo "@" ] hostport ]
441 if @host || @port || @user # userinfo = @user + ':' + @password
442 raise InvalidURIError,
443 "can not set registry with host, port, or userinfo"
444 elsif v && REGISTRY !~ v
445 raise InvalidComponentError,
446 "bad component(expected registry component): #{v}"
451 private :check_registry
456 protected :set_registry
465 # raise if both hier and opaque are not nil, because:
466 # absoluteURI = scheme ":" ( hier_part | opaque_part )
467 # hier_part = ( net_path | abs_path ) [ "?" query ]
469 raise InvalidURIError,
470 "path conflicts with opaque"
474 if v && v != '' && ABS_PATH !~ v
475 raise InvalidComponentError,
476 "bad component(expected absolute path component): #{v}"
479 if v && v != '' && ABS_PATH !~ v && REL_PATH !~ v
480 raise InvalidComponentError,
481 "bad component(expected relative path component): #{v}"
503 # raise if both hier and opaque are not nil, because:
504 # absoluteURI = scheme ":" ( hier_part | opaque_part )
505 # hier_part = ( net_path | abs_path ) [ "?" query ]
507 raise InvalidURIError,
508 "query conflicts with opaque"
511 if v && v != '' && QUERY !~ v
512 raise InvalidComponentError,
513 "bad component(expected query component): #{v}"
534 # raise if both hier and opaque are not nil, because:
535 # absoluteURI = scheme ":" ( hier_part | opaque_part )
536 # hier_part = ( net_path | abs_path ) [ "?" query ]
537 if @host || @port || @user || @path # userinfo = @user + ':' + @password
538 raise InvalidURIError,
539 "can not set opaque with host, port, userinfo or path"
540 elsif v && OPAQUE !~ v
541 raise InvalidComponentError,
542 "bad component(expected opaque component): #{v}"
547 private :check_opaque
552 protected :set_opaque
560 def check_fragment(v)
563 if v && v != '' && FRAGMENT !~ v
564 raise InvalidComponentError,
565 "bad component(expected fragment component): #{v}"
570 private :check_fragment
575 protected :set_fragment
584 # Checks if URI has a path
595 # Checks if URI is an absolute one
604 alias absolute absolute?
607 # Checks if URI is relative
614 path.split(%r{/+}, -1)
618 def merge_path(base, rel)
620 # RFC2396, Section 5.2, 5)
621 # RFC2396, Section 5.2, 6)
622 base_path = split_path(base)
623 rel_path = split_path(rel)
625 # RFC2396, Section 5.2, 6), a)
626 base_path << '' if base_path.last == '..'
627 while i = base_path.index('..')
628 base_path.slice!(i - 1, 2)
631 if (first = rel_path.first) and first.empty?
636 # RFC2396, Section 5.2, 6), c)
637 # RFC2396, Section 5.2, 6), d)
638 rel_path.push('') if rel_path.last == '.' || rel_path.last == '..'
641 # RFC2396, Section 5.2, 6), e)
645 !(tmp.empty? || tmp.last == '..')
652 add_trailer_slash = !tmp.empty?
654 base_path = [''] # keep '/' for root directory
655 elsif add_trailer_slash
661 # a .. or . in an absolute path has no special meaning
662 base_path.pop if base_path.size > 1
665 # valid absolute (but abnormal) path "/../..."
667 # valid absolute path
670 tmp.each {|t| base_path << t}
671 add_trailer_slash = false
675 base_path.push('') if add_trailer_slash
677 return base_path.join('/')
689 # Destructive form of #merge
695 # uri = URI.parse("http://my.example.com")
696 # uri.merge!("/main.rbx?page=1")
698 # # => #<URI::HTTP:0x2021f3b0 URL:http://my.example.com/main.rbx?page=1>
724 # uri = URI.parse("http://my.example.com")
725 # p uri.merge("/main.rbx?page=1")
726 # # => #<URI::HTTP:0x2021f3b0 URL:http://my.example.com/main.rbx?page=1>
730 base, rel = merge0(oth)
732 raise $!.class, $!.message
739 authority = rel.userinfo || rel.host || rel.port
741 # RFC2396, Section 5.2, 2)
742 if (rel.path.nil? || rel.path.empty?) && !authority && !rel.query
743 base.set_fragment(rel.fragment) if rel.fragment
748 base.set_fragment(nil)
750 # RFC2396, Section 5.2, 4)
752 base.set_path(merge_path(base.path, rel.path)) if base.path && rel.path
754 # RFC2396, Section 5.2, 4)
755 base.set_path(rel.path) if rel.path
758 # RFC2396, Section 5.2, 7)
759 base.set_userinfo(rel.userinfo) if rel.userinfo
760 base.set_host(rel.host) if rel.host
761 base.set_port(rel.port) if rel.port
762 base.set_query(rel.query) if rel.query
763 base.set_fragment(rel.fragment) if rel.fragment
769 # return base and rel.
770 # you can modify `base', but can not `rel'.
778 "bad argument(expected URI object or URI string)"
781 if self.relative? && oth.relative?
783 "both URI are relative"
786 if self.absolute? && oth.absolute?
788 # "both URI are absolute"
789 # hmm... should return oth for usability?
801 def route_from_path(src, dst)
802 # RFC2396, Section 4.2
803 return '' if src == dst
805 src_path = split_path(src)
806 dst_path = split_path(dst)
808 # hmm... dst has abnormal absolute path,
809 # like "/./", "/../", "/x/../", ...
810 if dst_path.include?('..') ||
811 dst_path.include?('.')
818 while dst_path.first == src_path.first
819 break if dst_path.empty?
825 tmp = dst_path.join('/')
831 elsif dst_path.first.include?(':') # (see RFC2396 Section 5)
838 return '../' * src_path.size + tmp
840 private :route_from_path
849 "bad argument(expected URI object or URI string)"
854 "relative URI: #{self}"
858 "relative URI: #{oth}"
861 if self.scheme != oth.scheme
862 return self, self.dup
864 rel = URI::Generic.new(nil, # it is relative URI
865 self.userinfo, self.host, self.port,
866 self.registry, self.path, self.opaque,
867 self.query, self.fragment)
869 if rel.userinfo != oth.userinfo ||
870 rel.host.to_s.downcase != oth.host.to_s.downcase ||
872 if self.userinfo.nil? && self.host.nil?
873 return self, self.dup
875 rel.set_port(nil) if rel.port == oth.default_port
878 rel.set_userinfo(nil)
882 if rel.path && rel.path == oth.path
884 rel.set_query(nil) if rel.query == oth.query
886 elsif rel.opaque && rel.opaque == oth.opaque
888 rel.set_query(nil) if rel.query == oth.query
892 # you can modify `rel', but can not `oth'.
904 # Calculates relative path from oth to self
910 # uri = URI.parse('http://my.example.com/main.rbx?page=1')
911 # p uri.route_from('http://my.example.com')
912 # #=> #<URI::Generic:0x20218858 URL:/main.rbx?page=1>
915 # you can modify `rel', but can not `oth'.
917 oth, rel = route_from0(oth)
919 raise $!.class, $!.message
925 rel.set_path(route_from_path(oth.path, self.path))
926 if rel.path == './' && self.query
944 # Calculates relative path to oth from self
950 # uri = URI.parse('http://my.example.com')
951 # p uri.route_to('http://my.example.com/main.rbx?page=1')
952 # #=> #<URI::Generic:0x2020c2f6 URL:/main.rbx?page=1>
961 "bad argument(expected URI object or URI string)"
968 # Returns normalized URI
977 # Destructive version of #normalize
980 if path && path == ''
983 if host && host != host.downcase
984 set_host(self.host.downcase)
998 # Constructs String from URI
1018 str << self.userinfo
1024 if @port && @port != self.default_port
1045 if self.class == oth.class
1046 self.normalize.component_ary == oth.normalize.component_ary
1053 self.component_ary.hash
1057 self.component_ary.eql?(oth.component_ary)
1062 --- URI::Generic#===(oth)
1066 # raise NotImplementedError
1072 component.collect do |x|
1076 protected :component_ary
1081 # Multiple Symbol arguments defined in URI::HTTP
1085 # Selects specified components from URI
1091 # uri = URI.parse('http://myuser:mypass@my.example.com/test.rbx')
1092 # p uri.select(:userinfo, :host, :path)
1093 # # => ["myuser:mypass", "my.example.com", "/test.rbx"]
1095 def select(*components)
1096 components.collect do |c|
1097 if component.include?(c)
1100 raise ArgumentError,
1101 "expected of components of #{self.class} (#{self.class.component.join(', ')})"
1107 sprintf("#<%s:%#0x URL:%s>", self.class.to_s, self.object_id, self.to_s)
1113 oth = URI.parse(oth)