C0 code coverage information

Generated on Sat May 03 13:27:45 -0400 2008 with rcov 0.8.1.2


Code reported as executed by Ruby looks like this...
and this: this line is also marked as covered.
Lines considered as run by rcov, but not reported by Ruby, look like this,
and this: these lines were inferred by rcov (using simple heuristics).
Finally, here's a line marked as not executed.
Name Total lines Lines of code Total coverage Code coverage
lib/addressable/uri.rb 1244 884
100.0%  
100.0%  
   1 #--
   2 # Addressable, Copyright (c) 2006-2007 Bob Aman
   3 #
   4 # Permission is hereby granted, free of charge, to any person obtaining
   5 # a copy of this software and associated documentation files (the
   6 # "Software"), to deal in the Software without restriction, including
   7 # without limitation the rights to use, copy, modify, merge, publish,
   8 # distribute, sublicense, and/or sell copies of the Software, and to
   9 # permit persons to whom the Software is furnished to do so, subject to
  10 # the following conditions:
  11 #
  12 # The above copyright notice and this permission notice shall be
  13 # included in all copies or substantial portions of the Software.
  14 #
  15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  16 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  17 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  18 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  19 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  20 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  21 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  22 #++
  23 
  24 $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), '/..')))
  25 $:.uniq!
  26 
  27 require 'addressable/version'
  28 
  29 module Addressable
  30   # This is an implementation of a URI parser based on RFC 3986, 3987.
  31   class URI
  32     # Raised if something other than a uri is supplied.
  33     class InvalidURIError < StandardError
  34     end
  35     
  36     # Raised if an invalid method option is supplied.
  37     class InvalidOptionError < StandardError
  38     end
  39     
  40     # Raised if an invalid method option is supplied.
  41     class InvalidTemplateValue < StandardError
  42     end
  43 
  44     module CharacterClasses
  45       ALPHA = "a-zA-Z"
  46       DIGIT = "0-9"
  47       GEN_DELIMS = "\\:\\/\\?\\#\\[\\]\\@"
  48       SUB_DELIMS = "\\!\\$\\&\\'\\(\\)\\*\\+\\,\\;\\="
  49       RESERVED = GEN_DELIMS + SUB_DELIMS
  50       UNRESERVED = ALPHA + DIGIT + "\\-\\.\\_\\~"
  51       PCHAR = UNRESERVED + SUB_DELIMS + "\\:\\@"
  52       SCHEME = ALPHA + DIGIT + "\\-\\+\\."
  53       AUTHORITY = PCHAR
  54       PATH = PCHAR + "\\/"
  55       QUERY = PCHAR + "\\/\\?"
  56       FRAGMENT = PCHAR + "\\/\\?"
  57     end    
  58     
  59     # Returns a URI object based on the parsed string.
  60     def self.parse(uri_string)
  61       return nil if uri_string.nil?
  62       
  63       # If a URI object is passed, just return itself.
  64       return uri_string if uri_string.kind_of?(self)
  65       
  66       # If a URI object of the Ruby standard library variety is passed,
  67       # convert it to a string, then parse the string.
  68       if uri_string.class.name =~ /^URI::/
  69         uri_string = uri_string.to_s
  70       end
  71       
  72       uri_regex =
  73         /^(([^:\/?#]+):)?(\/\/([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/
  74       scan = uri_string.scan(uri_regex)
  75       fragments = scan[0]
  76       return nil if fragments.nil?
  77       scheme = fragments[1]
  78       authority = fragments[3]
  79       path = fragments[4]
  80       query = fragments[6]
  81       fragment = fragments[8]
  82       userinfo = nil
  83       user = nil
  84       password = nil
  85       host = nil
  86       port = nil
  87       if authority != nil
  88         userinfo = authority.scan(/^([^\[\]]*)@/).flatten[0]
  89         if userinfo != nil
  90           user = userinfo.strip.scan(/^([^:]*):?/).flatten[0]
  91           password = userinfo.strip.scan(/:(.*)$/).flatten[0]
  92         end
  93         host = authority.gsub(/^([^\[\]]*)@/, "").gsub(/:([^:@\[\]]*?)$/, "")
  94         port = authority.scan(/:([^:@\[\]]*?)$/).flatten[0]
  95       end
  96       if port == ""
  97         port = nil
  98       end
  99       
 100       return Addressable::URI.new(
 101         scheme, user, password, host, port, path, query, fragment)
 102     end
 103     
 104     # Converts an input to a URI.  The input does not have to be a valid
 105     # URI -- the method will use heuristics to guess what URI was intended.
 106     # This is not standards compliant, merely user-friendly.
 107     def self.heuristic_parse(input, hints={})
 108       input = input.dup
 109       hints = {
 110         :scheme => "http"
 111       }.merge(hints)
 112       case input
 113       when /^http:\/+/
 114         input.gsub!(/^http:\/+/, "http://")
 115       when /^feed:\/+http:\/+/
 116         input.gsub!(/^feed:\/+http:\/+/, "feed:http://")
 117       when /^feed:\/+/
 118         input.gsub!(/^feed:\/+/, "feed://")
 119       when /^file:\/+/
 120         input.gsub!(/^file:\/+/, "file:///")
 121       end
 122       parsed = self.parse(input)
 123       if parsed.scheme =~ /^[^\/?#\.]+\.[^\/?#]+$/
 124         parsed = self.parse(hints[:scheme] + "://" + input)
 125       end
 126       if parsed.authority == nil
 127         if parsed.path =~ /^[^\/]+\./
 128           new_host = parsed.path.scan(/^([^\/]+\.[^\/]*)/).flatten[0]
 129           if new_host
 130             new_path = parsed.path.gsub(
 131               Regexp.new("^" + Regexp.escape(new_host)), "")
 132             parsed.host = new_host
 133             parsed.path = new_path
 134             parsed.scheme = hints[:scheme]
 135           end
 136         end
 137       end
 138       return parsed
 139     end
 140     
 141     # Converts a path to a file protocol URI.  If the path supplied is
 142     # relative, it will be returned as a relative URI.  If the path supplied
 143     # is actually a URI, it will return the parsed URI.
 144     def self.convert_path(path)
 145       return nil if path.nil?
 146       
 147       converted_uri = path.strip
 148       if converted_uri.length > 0 && converted_uri[0..0] == "/"
 149         converted_uri = "file://" + converted_uri
 150       end
 151       if converted_uri.length > 0 &&
 152           converted_uri.scan(/^[a-zA-Z]:[\\\/]/).size > 0
 153         converted_uri = "file:///" + converted_uri
 154       end
 155       converted_uri.gsub!(/^file:\/*/i, "file:///")
 156       if converted_uri =~ /^file:/i
 157         # Adjust windows-style uris
 158         converted_uri.gsub!(/^file:\/\/\/([a-zA-Z])\|/i, 'file:///\1:')
 159         converted_uri.gsub!(/\\/, '/')
 160         converted_uri = self.parse(converted_uri).normalize
 161         if File.exists?(converted_uri.path) &&
 162             File.stat(converted_uri.path).directory?
 163           converted_uri.path.gsub!(/\/$/, "")
 164           converted_uri.path = converted_uri.path + '/'
 165         end
 166       else
 167         converted_uri = self.parse(converted_uri)
 168       end
 169       
 170       return converted_uri
 171     end
 172     
 173     # Expands a URI template into a full URI.
 174     #
 175     # An optional processor object may be supplied.  The object should
 176     # respond to either the :validate or :transform messages or both.
 177     # Both the :validate and :transform methods should take two parameters:
 178     # :name and :value.  The :validate method should return true or false;
 179     # true if the value of the variable is valid, false otherwise.  The
 180     # :transform method should return the transformed variable value as a
 181     # string.
 182     #
 183     # An example:
 184     #    
 185     #  class ExampleProcessor
 186     #    def self.validate(name, value)
 187     #      return !!(value =~ /^[\w ]+$/) if name == "query"
 188     #      return true
 189     #    end
 190     #    
 191     #    def self.transform(name, value)
 192     #      return value.gsub(/ /, "+") if name == "query"
 193     #      return value
 194     #    end
 195     #  end
 196     # 
 197     #  Addressable::URI.expand_template(
 198     #    "http://example.com/search/{query}/",
 199     #    {"query" => "an example search query"},
 200     #    ExampleProcessor).to_s
 201     #  => "http://example.com/search/an+example+search+query/"
 202     def self.expand_template(pattern, mapping, processor=nil)
 203       result = pattern.dup
 204       for name, value in mapping
 205         transformed_value = value
 206         if processor != nil
 207           if processor.respond_to?(:validate)
 208             if !processor.validate(name, value)
 209               raise InvalidTemplateValue,
 210                 "(#{name}, #{value}) is an invalid template value."
 211             end
 212           end
 213           if processor.respond_to?(:transform)
 214             transformed_value = processor.transform(name, value)
 215           end
 216         end
 217 
 218         # Handle percent escaping
 219         transformed_value = self.encode_segment(transformed_value,
 220           Addressable::URI::CharacterClasses::RESERVED +
 221           Addressable::URI::CharacterClasses::UNRESERVED)
 222         
 223         result.gsub!(/\{#{Regexp.escape(name)}\}/, transformed_value)
 224       end
 225       result.gsub!(
 226         /\{[#{Addressable::URI::CharacterClasses::UNRESERVED}]+\}/, "")
 227       return Addressable::URI.parse(result)
 228     end
 229     
 230     # Extracts a mapping from the URI using a URI Template pattern.
 231     # Returns nil if the pattern doesn't match the URI.
 232     #
 233     # An optional processor object may be supplied.  The object should
 234     # respond to either the :restore or :match messages or both.
 235     # The :restore method should take two parameters: :name and :value.
 236     # The :restore method should reverse any transformations that have been
 237     # performed on the value to ensure a valid URI.  The :match method
 238     # should take a single parameter: :name.  The :match method should
 239     # return a String containing a regular expression capture group for
 240     # matching on that particular variable.  The default value is ".*".
 241     #
 242     # An example:
 243     #    
 244     #  class ExampleProcessor
 245     #    def self.restore(name, value)
 246     #      return value.gsub(/\+/, " ") if name == "query"
 247     #      return value
 248     #    end
 249     #
 250     #    def self.match(name)
 251     #      return ".*?" if name == "first"
 252     #      return ".*"
 253     #    end
 254     #  end
 255     #  
 256     #  uri = Addressable::URI.parse(
 257     #    "http://example.com/search/an+example+search+query/")
 258     #  uri.extract_mapping("http://example.com/search/{query}/",
 259     #    ExampleProcessor)
 260     #  => {"query" => "an example search query"}
 261     #  
 262     #  uri = Addressable::URI.parse(
 263     #    "http://example.com/a/b/c/")
 264     #  uri.extract_mapping("http://example.com/{first}/{second}/",
 265     #    ExampleProcessor)
 266     #  => {"first" => "a", "second" => "b/c"}
 267     def extract_mapping(pattern, processor=nil)
 268       mapping = {}
 269       variable_regexp =
 270         /\{([#{Addressable::URI::CharacterClasses::UNRESERVED}]+)\}/
 271       
 272       # Get all the variables in the pattern
 273       variables = pattern.scan(variable_regexp).flatten
 274       
 275       # Initialize all result values to the empty string
 276       variables.each { |v| mapping[v] = "" }
 277       
 278       # Escape the pattern
 279       escaped_pattern =
 280         Regexp.escape(pattern).gsub(/\\\{/, "{").gsub(/\\\}/, "}")
 281         
 282       # Create a regular expression that captures the values of the
 283       # variables in the URI.
 284       regexp_string = escaped_pattern.gsub(variable_regexp) do |v|
 285         capture_group = "(.*)"
 286         
 287         if processor != nil
 288           if processor.respond_to?(:match)
 289             name = v.scan(variable_regexp).flatten[0]
 290             capture_group = "(#{processor.match(name)})"
 291           end
 292         end
 293         
 294         capture_group
 295       end
 296       
 297       # Ensure that the regular expression matches the whole URI.
 298       regexp_string = "^#{regexp_string}$"
 299       
 300       regexp = Regexp.new(regexp_string)
 301       values = self.to_s.scan(regexp).flatten
 302       
 303       if variables.size == values.size && variables.size > 0
 304         # We have a match.
 305         for i in 0...variables.size
 306           name = variables[i]
 307           value = values[i]
 308           
 309           if processor != nil
 310             if processor.respond_to?(:restore)
 311               value = processor.restore(name, value)
 312             end
 313           end
 314           
 315           mapping[name] = value
 316         end
 317         return mapping
 318       elsif self.to_s == pattern
 319         # The pattern contained no variables but still matched.
 320         return mapping
 321       else
 322         # Pattern failed to match URI.
 323         return nil
 324       end
 325     end
 326     
 327     # Joins several uris together.
 328     def self.join(*uris)
 329       uri_objects = uris.collect do |uri|
 330         uri.kind_of?(self) ? uri : self.parse(uri.to_s)
 331       end
 332       result = uri_objects.shift.dup
 333       for uri in uri_objects
 334         result.merge!(uri)
 335       end
 336       return result
 337     end
 338     
 339     # Percent encodes a URI segment.  Returns a string.  Takes an optional
 340     # character class parameter, which should be specified as a string
 341     # containing a regular expression character class (not including the
 342     # surrounding square brackets).  The character class parameter defaults
 343     # to the reserved plus unreserved character classes specified in
 344     # RFC 3986.  Usage of the constants within the CharacterClasses module is
 345     # highly recommended when using this method.
 346     #
 347     # An example:
 348     #
 349     #  Addressable::URI.escape_segment("simple-example", "b-zB-Z0-9")
 350     #  => "simple%2Dex%61mple"
 351     def self.encode_segment(segment, character_class=
 352         Addressable::URI::CharacterClasses::RESERVED +
 353         Addressable::URI::CharacterClasses::UNRESERVED)
 354       return nil if segment.nil?
 355       return segment.gsub(
 356         /[^#{character_class}]/
 357       ) do |sequence|
 358         ("%" + sequence.unpack('C')[0].to_s(16).upcase)
 359       end      
 360     end
 361     
 362     # Unencodes any percent encoded characters within a URI segment.
 363     # Returns a string.
 364     def self.unencode_segment(segment)
 365       return nil if segment.nil?
 366       return segment.to_s.gsub(/%[0-9a-f]{2}/i) do |sequence|
 367         sequence[1..3].to_i(16).chr
 368       end
 369     end
 370     
 371     # Percent encodes any special characters in the URI.  This method does
 372     # not take IRIs or IDNs into account.
 373     def self.encode(uri)
 374       uri_object = uri.kind_of?(self) ? uri : self.parse(uri.to_s)
 375       return Addressable::URI.new(
 376         self.encode_segment(uri_object.scheme,
 377           Addressable::URI::CharacterClasses::SCHEME),
 378         self.encode_segment(uri_object.user,
 379           Addressable::URI::CharacterClasses::AUTHORITY),
 380         self.encode_segment(uri_object.password,
 381           Addressable::URI::CharacterClasses::AUTHORITY),
 382         self.encode_segment(uri_object.host,
 383           Addressable::URI::CharacterClasses::AUTHORITY),
 384         self.encode_segment(uri_object.specified_port,
 385           Addressable::URI::CharacterClasses::AUTHORITY),
 386         self.encode_segment(uri_object.path,
 387           Addressable::URI::CharacterClasses::PATH),
 388         self.encode_segment(uri_object.query,
 389           Addressable::URI::CharacterClasses::QUERY),
 390         self.encode_segment(uri_object.fragment,
 391           Addressable::URI::CharacterClasses::FRAGMENT)
 392       ).to_s
 393     end
 394     
 395     class << self
 396       alias_method :escape, :encode
 397     end
 398     
 399     # Normalizes the encoding of a URI.  Characters within a hostname are
 400     # not percent encoded to allow for internationalized domain names.
 401     def self.normalized_encode(uri)
 402       uri_object = uri.kind_of?(self) ? uri : self.parse(uri.to_s)
 403       segments = {
 404         :scheme => self.unencode_segment(uri_object.scheme),
 405         :user => self.unencode_segment(uri_object.user),
 406         :password => self.unencode_segment(uri_object.password),
 407         :host => self.unencode_segment(uri_object.host),
 408         :port => self.unencode_segment(uri_object.specified_port),
 409         :path => self.unencode_segment(uri_object.path),
 410         :query => self.unencode_segment(uri_object.query),
 411         :fragment => self.unencode_segment(uri_object.fragment)
 412       }
 413       if URI::IDNA.send(:use_libidn?)
 414         segments.each do |key, value|
 415           if value != nil
 416             segments[key] = IDN::Stringprep.nfkc_normalize(value.to_s)
 417           end
 418         end
 419       end
 420       return Addressable::URI.new(
 421         self.encode_segment(segments[:scheme],
 422           Addressable::URI::CharacterClasses::SCHEME),
 423         self.encode_segment(segments[:user],
 424           Addressable::URI::CharacterClasses::AUTHORITY),
 425         self.encode_segment(segments[:password],
 426           Addressable::URI::CharacterClasses::AUTHORITY),
 427         segments[:host],
 428         segments[:port],
 429         self.encode_segment(segments[:path],
 430           Addressable::URI::CharacterClasses::PATH),
 431         self.encode_segment(segments[:query],
 432           Addressable::URI::CharacterClasses::QUERY),
 433         self.encode_segment(segments[:fragment],
 434           Addressable::URI::CharacterClasses::FRAGMENT)
 435       ).to_s
 436     end
 437 
 438     # Extracts uris from an arbitrary body of text.
 439     def self.extract(text, options={})
 440       defaults = {:base => nil, :parse => false} 
 441       options = defaults.merge(options)
 442       raise InvalidOptionError unless (options.keys - defaults.keys).empty?
 443       # This regular expression needs to be less forgiving or else it would
 444       # match virtually all text.  Which isn't exactly what we're going for.
 445       extract_regex = /((([a-z\+]+):)[^ \n\<\>\"\\]+[\w\/])/
 446       extracted_uris =
 447         text.scan(extract_regex).collect { |match| match[0] }
 448       sgml_extract_regex = /<[^>]+href=\"([^\"]+?)\"[^>]*>/
 449       sgml_extracted_uris =
 450         text.scan(sgml_extract_regex).collect { |match| match[0] }
 451       extracted_uris.concat(sgml_extracted_uris - extracted_uris)
 452       textile_extract_regex = /\".+?\":([^ ]+\/[^ ]+)[ \,\.\;\:\?\!\<\>\"]/i
 453       textile_extracted_uris =
 454         text.scan(textile_extract_regex).collect { |match| match[0] }
 455       extracted_uris.concat(textile_extracted_uris - extracted_uris)
 456       parsed_uris = []
 457       base_uri = nil
 458       if options[:base] != nil
 459         base_uri = options[:base] if options[:base].kind_of?(self)
 460         base_uri = self.parse(options[:base].to_s) if base_uri == nil
 461       end
 462       for uri_string in extracted_uris
 463         begin
 464           if base_uri == nil
 465             parsed_uris << self.parse(uri_string)
 466           else
 467             parsed_uris << (base_uri + self.parse(uri_string))
 468           end
 469         rescue Exception
 470           nil
 471         end
 472       end
 473       parsed_uris.reject! do |uri|
 474         (uri.scheme =~ /T\d+/ ||
 475          uri.scheme == "xmlns" ||
 476          uri.scheme == "xml" ||
 477          uri.scheme == "thr" ||
 478          uri.scheme == "this" ||
 479          uri.scheme == "float" ||
 480          uri.scheme == "user" ||
 481          uri.scheme == "username" ||
 482          uri.scheme == "out")
 483       end
 484       if options[:parse]
 485         return parsed_uris
 486       else
 487         return parsed_uris.collect { |uri| uri.to_s }
 488       end
 489     end
 490     
 491     # Creates a new uri object from component parts.  Passing nil for
 492     # any of these parameters is acceptable.
 493     def initialize(scheme, user, password, host, port, path, query, fragment)
 494       @scheme = scheme
 495       @scheme = nil if @scheme.to_s.strip == ""
 496       @user = user
 497       @password = password
 498       @host = host
 499       @specified_port = port.to_s
 500       @port = port.kind_of?(Fixnum) ? port.to_s : port
 501       if @port != nil && !(@port =~ /^\d+$/)
 502         raise InvalidURIError,
 503           "Invalid port number: #{@port.inspect}"
 504       end
 505       @port = @port.to_i
 506       @port = nil if @port == 0
 507       @path = (path || "")
 508       @query = query
 509       @fragment = fragment
 510 
 511       validate()
 512     end
 513     
 514     # Returns the scheme (protocol) for this URI.
 515     def scheme
 516       return @scheme
 517     end
 518     
 519     # Sets the scheme (protocol for this URI.)
 520     def scheme=(new_scheme)
 521       @scheme = new_scheme
 522     end
 523     
 524     # Returns the user for this URI.
 525     def user
 526       return @user
 527     end
 528     
 529     # Sets the user for this URI.
 530     def user=(new_user)
 531       @user = new_user
 532 
 533       # You can't have a nil user with a non-nil password
 534       if @password != nil
 535         @user = "" if @user.nil?
 536       end
 537 
 538       # Reset dependant values
 539       @userinfo = nil
 540       @authority = nil
 541 
 542       # Ensure we haven't created an invalid URI
 543       validate()
 544     end
 545     
 546     # Returns the password for this URI.
 547     def password
 548       return @password
 549     end
 550 
 551     # Sets the password for this URI.
 552     def password=(new_password)
 553       @password = new_password
 554 
 555       # You can't have a nil user with a non-nil password
 556       if @password != nil
 557         @user = "" if @user.nil?
 558       end
 559 
 560       # Reset dependant values
 561       @userinfo = nil
 562       @authority = nil
 563 
 564       # Ensure we haven't created an invalid URI
 565       validate()
 566     end
 567     
 568     # Returns the username and password segment of this URI.
 569     def userinfo
 570       if !defined?(@userinfo) || @userinfo == nil
 571         current_user = self.user
 572         current_password = self.password
 573         if !current_user && !current_password
 574           @userinfo = nil
 575         elsif current_user && current_password
 576           @userinfo = "#{current_user}:#{current_password}"
 577         elsif current_user && !current_password
 578           @userinfo = "#{current_user}"
 579         end
 580       end
 581       return @userinfo
 582     end
 583     
 584     # Sets the username and password segment of this URI.
 585     def userinfo=(new_userinfo)
 586       new_user = new_userinfo.to_s.strip.scan(/^(.*):/).flatten[0]
 587       new_password = new_userinfo.to_s.strip.scan(/:(.*)$/).flatten[0]
 588       
 589       # Password assigned first to ensure validity in case of nil
 590       self.password = new_password
 591       self.user = new_user
 592 
 593       # Reset dependant values
 594       @authority = nil
 595 
 596       # Ensure we haven't created an invalid URI
 597       validate()
 598     end
 599     
 600     # Returns the host for this URI.
 601     def host
 602       return @host
 603     end
 604     
 605     # Sets the host for this URI.
 606     def host=(new_host)
 607       @host = new_host
 608 
 609       # Reset dependant values
 610       @authority = nil
 611 
 612       # Ensure we haven't created an invalid URI
 613       validate()
 614     end
 615     
 616     # Returns the authority segment of this URI.
 617     def authority
 618       if !defined?(@authority) || @authority.nil?
 619         return nil if self.host.nil?
 620         @authority = ""
 621         if self.userinfo != nil
 622           @authority << "#{self.userinfo}@"
 623         end
 624         @authority << self.host
 625         if self.specified_port != nil
 626           @authority << ":#{self.specified_port}"
 627         end
 628       end
 629       return @authority
 630     end
 631     
 632     # Sets the authority segment of this URI.
 633     def authority=(new_authority)
 634       if new_authority
 635         new_userinfo = new_authority.scan(/^([^\[\]]*)@/).flatten[0]
 636         if new_userinfo
 637           new_user = new_userinfo.strip.scan(/^([^:]*):?/).flatten[0]
 638           new_password = new_userinfo.strip.scan(/:(.*)$/).flatten[0]
 639         end
 640         new_host =
 641           new_authority.gsub(/^([^\[\]]*)@/, "").gsub(/:([^:@\[\]]*?)$/, "")
 642         new_port =
 643           new_authority.scan(/:([^:@\[\]]*?)$/).flatten[0]
 644       end
 645       
 646       # Password assigned first to ensure validity in case of nil
 647       self.password = new_password
 648       self.user = new_user
 649       self.host = new_host
 650       
 651       # Port reset to allow port normalization
 652       @port = nil
 653       @specified_port = new_port
 654       
 655       # Ensure we haven't created an invalid URI
 656       validate()
 657     end
 658 
 659     # Returns an array of known ip-based schemes.  These schemes typically
 660     # use a similar URI form:
 661     # //<user>:<password>@<host>:<port>/<url-path>
 662     def self.ip_based_schemes
 663       return self.scheme_mapping.keys
 664     end
 665 
 666     # Returns a hash of common IP-based schemes and their default port
 667     # numbers.  Adding new schemes to this hash, as necessary, will allow
 668     # for better URI normalization.
 669     def self.scheme_mapping
 670       if !defined?(@protocol_mapping) || @protocol_mapping.nil?
 671         @protocol_mapping = {
 672           "http" => 80,
 673           "https" => 443,
 674           "ftp" => 21,
 675           "tftp" => 69,
 676           "ssh" => 22,
 677           "svn+ssh" => 22,
 678           "telnet" => 23,
 679           "nntp" => 119,
 680           "gopher" => 70,
 681           "wais" => 210,
 682           "ldap" => 389,
 683           "prospero" => 1525
 684         }
 685       end
 686       return @protocol_mapping
 687     end
 688     
 689     # Returns the port number for this URI.  This method will normalize to the
 690     # default port for the URI's scheme if the port isn't explicitly specified
 691     # in the URI.
 692     def port
 693       if @port.to_i == 0
 694         if self.scheme
 695           @port = self.class.scheme_mapping[self.scheme.strip.downcase]
 696         else
 697           @port = nil
 698         end
 699         return @port
 700       else
 701         @port = @port.to_i
 702         return @port
 703       end
 704     end
 705     
 706     # Sets the port for this URI.
 707     def port=(new_port)
 708       @port = new_port.to_s.to_i
 709       @specified_port = @port
 710       @authority = nil
 711     end
 712     
 713     # Returns the port number that was actually specified in the URI string.
 714     def specified_port
 715       port = @specified_port.to_s.to_i
 716       if port == 0
 717         return nil
 718       else
 719         return port
 720       end
 721     end
 722     
 723     # Returns the path for this URI.
 724     def path
 725       return @path
 726     end
 727     
 728     # Sets the path for this URI.
 729     def path=(new_path)
 730       @path = (new_path || "")
 731     end
 732 
 733     # Returns the basename, if any, of the file at the path being referenced.
 734     # Returns nil if there is no path component.
 735     def basename
 736       # Path cannot be nil
 737       return File.basename(self.path).gsub(/;[^\/]*$/, "")
 738     end
 739         
 740     # Returns the extension, if any, of the file at the path being referenced.
 741     # Returns "" if there is no extension or nil if there is no path
 742     # component.
 743     def extname
 744       return nil unless self.path
 745       return File.extname(self.basename)
 746     end
 747     
 748     # Returns the query string for this URI.
 749     def query
 750       return @query
 751     end
 752     
 753     # Sets the query string for this URI.
 754     def query=(new_query)
 755       @query = new_query
 756     end
 757     
 758     # Returns the fragment for this URI.
 759     def fragment
 760       return @fragment
 761     end
 762     
 763     # Sets the fragment for this URI.
 764     def fragment=(new_fragment)
 765       @fragment = new_fragment
 766     end
 767     
 768     # Returns true if the URI uses an IP-based protocol.
 769     def ip_based?
 770       if self.scheme
 771         return self.class.ip_based_schemes.include?(
 772           self.scheme.strip.downcase)
 773       end
 774       return false
 775     end
 776     
 777     # Returns true if this URI is known to be relative.
 778     def relative?
 779       return self.scheme.nil?
 780     end
 781     
 782     # Returns true if this URI is known to be absolute.
 783     def absolute?
 784       return !relative?
 785     end
 786     
 787     # Joins two URIs together.
 788     def +(uri)
 789       if !uri.kind_of?(self.class)
 790         uri = URI.parse(uri.to_s)
 791       end
 792       if uri.to_s == ""
 793         return self.dup
 794       end
 795       
 796       joined_scheme = nil
 797       joined_user = nil
 798       joined_password = nil
 799       joined_host = nil
 800       joined_port = nil
 801       joined_path = nil
 802       joined_query = nil
 803       joined_fragment = nil
 804       
 805       # Section 5.2.2 of RFC 3986
 806       if uri.scheme != nil
 807         joined_scheme = uri.scheme
 808         joined_user = uri.user
 809         joined_password = uri.password
 810         joined_host = uri.host
 811         joined_port = uri.specified_port
 812         joined_path = self.class.normalize_path(uri.path)
 813         joined_query = uri.query
 814       else
 815         if uri.authority != nil
 816           joined_user = uri.user
 817           joined_password = uri.password
 818           joined_host = uri.host
 819           joined_port = uri.specified_port
 820           joined_path = self.class.normalize_path(uri.path)
 821           joined_query = uri.query
 822         else
 823           if uri.path == nil || uri.path == ""
 824             joined_path = self.path
 825             if uri.query != nil
 826               joined_query = uri.query
 827             else
 828               joined_query = self.query
 829             end
 830           else
 831             if uri.path[0..0] == "/"
 832               joined_path = self.class.normalize_path(uri.path)
 833             else
 834               base_path = self.path.dup
 835               base_path = "" if base_path == nil
 836               base_path = self.class.normalize_path(base_path)
 837 
 838               # Section 5.2.3 of RFC 3986
 839               #
 840               # Removes the right-most path segment from the base path.
 841               if base_path =~ /\//
 842                 base_path.gsub!(/\/[^\/]+$/, "/")
 843               else
 844                 base_path = ""
 845               end
 846               
 847               # If the base path is empty and an authority segment has been
 848               # defined, use a base path of "/"
 849               if base_path == "" && self.authority != nil
 850                 base_path = "/"
 851               end
 852               
 853               joined_path = self.class.normalize_path(base_path + uri.path)
 854             end
 855             joined_query = uri.query
 856           end
 857           joined_user = self.user
 858           joined_password = self.password
 859           joined_host = self.host
 860           joined_port = self.specified_port
 861         end
 862         joined_scheme = self.scheme
 863       end
 864       joined_fragment = uri.fragment
 865       
 866       return Addressable::URI.new(
 867         joined_scheme,
 868         joined_user,
 869         joined_password,
 870         joined_host,
 871         joined_port,
 872         joined_path,
 873         joined_query,
 874         joined_fragment
 875       )
 876     end
 877     
 878     # Merges two URIs together.
 879     def merge(uri)
 880       return self + uri
 881     end
 882     alias_method :join, :merge
 883     
 884     # Destructive form of merge.
 885     def merge!(uri)
 886       replace_self(self.merge(uri))
 887     end
 888     alias_method :join!, :merge!
 889     
 890     # Returns the shortest normalized relative form of this URI that uses the
 891     # supplied URI as a base for resolution.  Returns an absolute URI if
 892     # necessary.
 893     def route_from(uri)
 894       uri = self.class.parse(uri).normalize
 895       normalized_self = self.normalize
 896       if normalized_self.relative?
 897         raise ArgumentError, "Expected absolute URI, got: #{self.to_s}"
 898       end
 899       if uri.relative?
 900         raise ArgumentError, "Expected absolute URI, got: #{uri.to_s}"
 901       end
 902       if normalized_self == uri
 903         return Addressable::URI.parse("##{normalized_self.fragment}")
 904       end
 905       segments = normalized_self.to_hash
 906       if normalized_self.scheme == uri.scheme
 907         segments[:scheme] = nil
 908         if normalized_self.authority == uri.authority
 909           segments[:user] = nil
 910           segments[:password] = nil
 911           segments[:host] = nil
 912           segments[:port] = nil
 913           if normalized_self.path == uri.path
 914             segments[:path] = nil
 915             if normalized_self.query == uri.query
 916               segments[:query] = nil
 917             end
 918           else
 919             if uri.path != "/"
 920               segments[:path].gsub!(
 921                 Regexp.new("^" + Regexp.escape(uri.path)), "")
 922             end
 923           end
 924         end
 925       end
 926       # Avoid network-path references.
 927       if segments[:host] != nil
 928         segments[:scheme] = normalized_self.scheme
 929       end
 930       return Addressable::URI.new(
 931         segments[:scheme],
 932         segments[:user],
 933         segments[:password],
 934         segments[:host],
 935         segments[:port],
 936         segments[:path],
 937         segments[:query],
 938         segments[:fragment]
 939       )
 940     end
 941     
 942     # Returns the shortest normalized relative form of the supplied URI that
 943     # uses this URI as a base for resolution.  Returns an absolute URI if
 944     # necessary.
 945     def route_to(uri)
 946       return self.class.parse(uri).route_from(self)
 947     end
 948     
 949     # Returns a normalized URI object.
 950     #
 951     # NOTE: This method does not attempt to fully conform to specifications.
 952     # It exists largely to correct other people's failures to read the
 953     # specifications, and also to deal with caching issues since several
 954     # different URIs may represent the same resource and should not be
 955     # cached multiple times.
 956     def normalize
 957       normalized_scheme = nil
 958       normalized_scheme = self.scheme.strip.downcase if self.scheme != nil
 959       normalized_scheme = "svn+ssh" if normalized_scheme == "ssh+svn"
 960       if normalized_scheme == "feed"
 961         if self.to_s =~ /^feed:\/*http:\/*/
 962           return self.class.parse(
 963             self.to_s.scan(/^feed:\/*(http:\/*.*)/).flatten[0]).normalize
 964         end
 965       end
 966       normalized_user = nil
 967       normalized_user = self.user.strip if self.user != nil
 968       normalized_password = nil
 969       normalized_password = self.password.strip if self.password != nil
 970       
 971       # If we are using http or https and user/password are blank,
 972       # then we remove them
 973       if normalized_scheme =~ /https?/ && normalized_user == "" &&
 974           (!normalized_password || normalized_password == "")
 975         normalized_user = nil
 976         normalized_password = nil
 977       end
 978       
 979       normalized_host = nil
 980       normalized_host = self.host.strip.downcase if self.host != nil
 981       if normalized_host != nil
 982         begin
 983           normalized_host = URI::IDNA.to_ascii(normalized_host)
 984         rescue Exception
 985           nil
 986         end
 987         if normalized_host[-1..-1] == "."
 988           normalized_host = normalized_host[0...-1]
 989         end
 990       end
 991       
 992       normalized_port = self.port
 993       if self.class.scheme_mapping[normalized_scheme] == normalized_port
 994         normalized_port = nil
 995       end
 996       normalized_path = nil
 997       normalized_path = self.path.strip if self.path != nil
 998       if normalized_path != nil
 999         normalized_path = self.class.normalize_path(normalized_path)
1000       end
1001       if normalized_path == ""
1002         if ["http", "https", "ftp", "tftp"].include?(normalized_scheme)
1003           normalized_path = "/"
1004         end
1005       end
1006 
1007       normalized_query = nil
1008       normalized_query = self.query.strip if self.query != nil
1009 
1010       normalized_fragment = nil
1011       normalized_fragment = self.fragment.strip if self.fragment != nil
1012       return Addressable::URI.parse(
1013         Addressable::URI.normalized_encode(Addressable::URI.new(
1014           normalized_scheme,
1015           normalized_user,
1016           normalized_password,
1017           normalized_host,
1018           normalized_port,
1019           normalized_path,
1020           normalized_query,
1021           normalized_fragment
1022         )))
1023     end
1024 
1025     # Destructively normalizes this URI object.
1026     def normalize!
1027       replace_self(self.normalize)
1028     end
1029     
1030     # Creates a URI suitable for display to users.  If semantic attacks are
1031     # likely, the application should try to detect these and warn the user.
1032     # See RFC 3986 section 7.6 for more information.
1033     def display_uri
1034       display_uri = self.normalize
1035       begin
1036         display_uri.instance_variable_set("@host",
1037           URI::IDNA.to_unicode(display_uri.host))
1038       rescue Exception
1039         nil
1040       end
1041       return display_uri
1042     end
1043     
1044     # Returns true if the URI objects are equal.  This method normalizes
1045     # both URIs before doing the comparison, and allows comparison against
1046     # strings.
1047     def ===(uri)
1048       if uri.respond_to?(:normalize)
1049         uri_string = uri.normalize.to_s
1050       else
1051         begin
1052           uri_string = URI.parse(uri.to_s).normalize.to_s
1053         rescue InvalidURIError
1054           return false
1055         end
1056       end
1057       return self.normalize.to_s == uri_string
1058     end
1059     
1060     # Returns true if the URI objects are equal.  This method normalizes
1061     # both URIs before doing the comparison.
1062     def ==(uri)
1063       return false unless uri.kind_of?(self.class) 
1064       return self.normalize.to_s == uri.normalize.to_s
1065     end
1066 
1067     # Returns true if the URI objects are equal.  This method does NOT
1068     # normalize either URI before doing the comparison.
1069     def eql?(uri)
1070       return false unless uri.kind_of?(self.class) 
1071       return self.to_s == uri.to_s
1072     end
1073 
1074     # Returns a hash value that will make a URI equivalent to its normalized
1075     # form.
1076     def hash
1077       return (self.normalize.to_s.hash * -1)
1078     end
1079     
1080     # Clones the URI object.
1081     def dup
1082       duplicated_scheme = self.scheme ? self.scheme.dup : nil
1083       duplicated_user = self.user ? self.user.dup : nil
1084       duplicated_password = self.password ? self.password.dup : nil
1085       duplicated_host = self.host ? self.host.dup : nil
1086       duplicated_port = self.specified_port
1087       duplicated_path = self.path ? self.path.dup : nil
1088       duplicated_query = self.query ? self.query.dup : nil
1089       duplicated_fragment = self.fragment ? self.fragment.dup : nil
1090       duplicated_uri = Addressable::URI.new(
1091         duplicated_scheme,
1092         duplicated_user,
1093         duplicated_password,
1094         duplicated_host,
1095         duplicated_port,
1096         duplicated_path,
1097         duplicated_query,
1098         duplicated_fragment
1099       )
1100       return duplicated_uri
1101     end
1102     
1103     # Returns the assembled URI as a string.
1104     def to_s
1105       uri_string = ""
1106       uri_string << "#{self.scheme}:" if self.scheme != nil
1107       uri_string << "//#{self.authority}" if self.authority != nil
1108       uri_string << self.path.to_s
1109       uri_string << "?#{self.query}" if self.query != nil
1110       uri_string << "##{self.fragment}" if self.fragment != nil
1111       return uri_string
1112     end
1113     
1114     # Returns a Hash of the URI segments.
1115     def to_hash
1116       return {
1117         :scheme => self.scheme,
1118         :user => self.user,
1119         :password => self.password,
1120         :host => self.host,
1121         :port => self.specified_port,
1122         :path => self.path,
1123         :query => self.query,
1124         :fragment => self.fragment
1125       }
1126     end
1127     
1128     # Returns a string representation of the URI object's state.
1129     def inspect
1130       sprintf("#<%s:%#0x URI:%s>", self.class.to_s, self.object_id, self.to_s)
1131     end
1132     
1133     # This module handles internationalized domain names.  When Ruby has an
1134     # implementation of nameprep, stringprep, punycode, etc, this
1135     # module should contain an actual implementation of IDNA instead of
1136     # returning nil if libidn can't be used.
1137     module IDNA
1138       # Returns the ascii representation of the label.
1139       def self.to_ascii(label)
1140         return nil if label.nil?
1141         if self.use_libidn?
1142           return IDN::Idna.toASCII(label)
1143         else
1144           raise NotImplementedError,
1145             "There is no available pure-ruby implementation.  " +
1146             "Install libidn bindings."
1147         end
1148       end
1149       
1150       # Returns the unicode representation of the label.
1151       def self.to_unicode(label)
1152         return nil if label.nil?
1153         if self.use_libidn?
1154           return IDN::Idna.toUnicode(label)
1155         else
1156           raise NotImplementedError,
1157             "There is no available pure-ruby implementation.  " +
1158             "Install libidn bindings."
1159         end
1160       end
1161       
1162     private
1163       # Determines if the libidn bindings are available and able to be used.
1164       def self.use_libidn?
1165         if !defined?(@use_libidn) || @use_libidn.nil?
1166           begin
1167             require 'rubygems'
1168           rescue LoadError
1169             nil
1170           end
1171           begin
1172             require 'idn'
1173           rescue LoadError
1174             nil
1175           end
1176           @use_libidn = !!(defined?(IDN::Idna))
1177         end
1178         return @use_libidn
1179       end
1180     end
1181     
1182   private
1183     # Resolves paths to their simplest form.
1184     def self.normalize_path(path)
1185       # Section 5.2.4 of RFC 3986
1186       
1187       return nil if path.nil?
1188       normalized_path = path.dup
1189       previous_state = normalized_path.dup
1190       begin
1191         previous_state = normalized_path.dup
1192         normalized_path.gsub!(/\/\.\//, "/")
1193         normalized_path.gsub!(/\/\.$/, "/")
1194         parent = normalized_path.scan(/\/([^\/]+)\/\.\.\//).flatten[0]
1195         if parent != "." && parent != ".."
1196           normalized_path.gsub!(/\/#{parent}\/\.\.\//, "/")
1197         end
1198         parent = normalized_path.scan(/\/([^\/]+)\/\.\.$/).flatten[0]
1199         if parent != "." && parent != ".."
1200           normalized_path.gsub!(/\/#{parent}\/\.\.$/, "/")
1201         end
1202         normalized_path.gsub!(/^\.\.?\/?/, "")
1203         normalized_path.gsub!(/^\/\.\.?\//, "/")
1204       end until previous_state == normalized_path
1205       return normalized_path
1206     end
1207 
1208     # Ensures that the URI is valid.
1209     def validate
1210       if self.scheme != nil &&
1211           (self.host == nil || self.host == "") &&
1212           (self.path == nil || self.path == "")
1213         raise InvalidURIError,
1214           "Absolute URI missing hierarchical segment."
1215       end
1216       if self.host == nil
1217         if self.specified_port != nil ||
1218             self.user != nil ||
1219             self.password != nil
1220           raise InvalidURIError, "Hostname not supplied."
1221         end
1222       end
1223     end
1224     
1225     # Replaces the internal state of self with the specified URI's state.
1226     # Used in destructive operations to avoid massive code repetition.
1227     def replace_self(uri)
1228       # Reset dependant values
1229       @userinfo = nil
1230       @authority = nil
1231       
1232       @scheme = uri.scheme
1233       @user = uri.user
1234       @password = uri.password
1235       @host = uri.host
1236       @specified_port = uri.instance_variable_get("@specified_port")
1237       @port = @specified_port.to_s.to_i
1238       @path = uri.path
1239       @query = uri.query
1240       @fragment = uri.fragment
1241       return self
1242     end
1243   end
1244 end

Generated using the rcov code coverage analysis tool for Ruby version 0.8.1.2.

Valid XHTML 1.0! Valid CSS!