URI::merge bug?

Hi all,

I'm not sure whether this is a bug:

irb(main):013:0> a = URI.parse("http://www.example.com/foo/bar?a=b")
=> #<URI::HTTP:0xfdbb6d160 URL:http://www.example.com/foo/bar?a=b>
irb(main):014:0> b = URI.parse("?a=c")
=> #<URI::Generic:0xfdbb6b770 URL:?a=c>
irb(main):015:0> puts a.merge(b).to_s
http://www.example.com/foo/?a=c

I'd expect the last line to be "http://www.example.com/foo/bar?a=c" - it seems to lose the last element of the path. Firefox seems to think do what I'm expecting, anyway... Can someone more au fait with URI RFC's comment?

···

--
Alex

Hi,

have alook at what "?a=c" parses to, i.e. if it is interpreted as
"/?a=c" that would explain the behaviour.

J.

···

On 5/18/07, Alex Young <alex@blackkettle.org> wrote:

Hi all,

I'm not sure whether this is a bug:

irb(main):013:0> a = URI.parse("http://www.example.com/foo/bar?a=b&quot;\)
=> #<URI::HTTP:0xfdbb6d160 URL:http://www.example.com/foo/bar?a=b&gt;
irb(main):014:0> b = URI.parse("?a=c")
=> #<URI::Generic:0xfdbb6b770 URL:?a=c>
irb(main):015:0> puts a.merge(b).to_s
http://www.example.com/foo/?a=c

I'd expect the last line to be "http://www.example.com/foo/bar?a=c&quot; - it
seems to lose the last element of the path. Firefox seems to think do
what I'm expecting, anyway... Can someone more au fait with URI RFC's
comment?

Alex Young <alex@blackkettle.org> writes:

I'm not sure whether this is a bug:

irb(main):013:0> a = URI.parse("http://www.example.com/foo/bar?a=b&quot;\)
=> #<URI::HTTP:0xfdbb6d160 URL:http://www.example.com/foo/bar?a=b&gt;
irb(main):014:0> b = URI.parse("?a=c")
=> #<URI::Generic:0xfdbb6b770 URL:?a=c>
irb(main):015:0> puts a.merge(b).to_s
http://www.example.com/foo/?a=c

I'll note that although firefox agrees with your expectations, lynx
agrees with the behavior of the uri module.

To understand what the uri module is doing, look at this:

irb(main):001:0> require 'uri'
=> true
irb(main):002:0> b = URI.parse("?a=c")
=> #<URI::Generic:0xfdbccb1d0 URL:?a=c>
irb(main):003:0> b.scheme
=> nil
irb(main):004:0> b.userinfo || b.host || b.port
=> nil
irb(main):005:0> b.path
=> ""
irb(main):006:0> b.query
=> "a=c"
irb(main):007:0> b.fragment
=> nil

That is, the scheme and authority portions of the uri are *nil*, but
the path is present, as the empty string. When merging an empty path
with the path "/foo/bar" , the uri module comes up with "/foo/". Not
a totally unreasonable choice.

In fact, this is a bug, but not the one you think. "?a=b" is a
malformed relative URI. You should get a parse error trying to create
that.

According to RFC2396, a relative URI consists of (section 5, near the
bottom of pg. 17):

      relativeURI = ( net_path | abs_path | rel_path ) [ "?" query ]

      rel_path = rel_segment [ abs_path ]

      rel_segment = 1*( unreserved | escaped |
                          ";" | "@" | "&" | "=" | "+" | "$" | "," )

See the 1* part? That means that a relative uri path segment must
consist of at least one character. An empty path segment is illegal.
(Note that uri references that begin with '#' are covered in section 4
of the RFC, and match the rule "URI-reference" rather than the rule
"relativeURI")

Now, given that the URI module does indeed accept relative URIs like
this, perhaps we should redefine URI merging for these pathological
cases so that the URI module behaves as some particular well-known
browser does:

module URI
  class Generic
    def merge_like(browser, other)
      if !other.absolute? and other.path and other.path.empty? and
         not (other.userinfo || other.host || other.port) then
        case browser
        when :firefox, :netscape
          other = other.dup
          other.path = self.path
        when :ie, :microsoft, :links
          other = other.dup
          if other.query || other.fragment
            other.path = self.path
          else
            other.path = '.'
          end
        when :lynx
          # we're good already, so we don't *need* to do
          # this, but let's pass the real merge function
          # valid relative uris anyway, okay?
          other = other.dup
          if other.query
            other.path = '.'
          else
            other.path = self.path
          end
        else
          # Could someone test how opera handles the three links on
          # http://snowplow.org/martin/relative_uri_test.html ?
          raise "Unhandled browser type #{browser}"
        end
      end
      return merge(other)
    end
  end
end

···

--
s=%q( Daniel Martin -- martin@snowplow.org
       puts "s=%q(#{s})",s.to_a.last )
       puts "s=%q(#{s})",s.to_a.last

Daniel Martin <martin@snowplow.org> writes:

In fact, this is a bug, but not the one you think. "?a=b" is a
malformed relative URI. You should get a parse error trying to create
that.

According to RFC2396, a relative URI consists of (section 5, near the
bottom of pg. 17):

You know what? I'm using an old RFC. Everything I said applies to
RFC 2396, but that's not the current URI RFC. The current one is
RFC 3986, and by that one firefox is doing exactly the right thing.
(So, by the way, is Opera)

So it is a bug in URI, and the bug is "written to an old RFC".

I don't know how to explain Internet Explorer's odd behavior with
regard to a relative uri of "", but that's Microsoft for you.

···

--
s=%q( Daniel Martin -- martin@snowplow.org
       puts "s=%q(#{s})",s.to_a.last )
       puts "s=%q(#{s})",s.to_a.last

Daniel Martin wrote:

Daniel Martin <martin@snowplow.org> writes:

In fact, this is a bug, but not the one you think. "?a=b" is a
malformed relative URI. You should get a parse error trying to create
that.

According to RFC2396, a relative URI consists of (section 5, near the
bottom of pg. 17):

You know what? I'm using an old RFC. Everything I said applies to
RFC 2396, but that's not the current URI RFC. The current one is
RFC 3986, and by that one firefox is doing exactly the right thing.
(So, by the way, is Opera)

So it is a bug in URI, and the bug is "written to an old RFC".

I don't know how to explain Internet Explorer's odd behavior with
regard to a relative uri of "", but that's Microsoft for you.

Thanks - that pretty much confirms what I thought. Would a patch to bring URI up to RFC 3986 compliance be a huge undertaking?

···

--
Alex

Hi,

At Sat, 19 May 2007 00:52:37 +0900,
Daniel Martin wrote in [ruby-talk:252116]:

You know what? I'm using an old RFC. Everything I said applies to
RFC 2396, but that's not the current URI RFC. The current one is
RFC 3986, and by that one firefox is doing exactly the right thing.
(So, by the way, is Opera)

So it is a bug in URI, and the bug is "written to an old RFC".

Then, the test is wrong too?

Index: lib/uri/generic.rb

···

===================================================================
--- lib/uri/generic.rb (revision 12297)
+++ lib/uri/generic.rb (working copy)
@@ -632,9 +632,4 @@ module URI
     base_path.slice!(i - 1, 2)
         end
- if base_path.empty?
- base_path = [''] # keep '/' for root directory
- else
- base_path.pop
- end

         # RFC2396, Section 5.2, 6), c)
@@ -654,5 +649,10 @@ module URI
         end

- add_trailer_slash = true
+ add_trailer_slash = !tmp.empty?
+ if base_path.empty?
+ base_path = [''] # keep '/' for root directory
+ elsif add_trailer_slash
+ base_path.pop
+ end
         while x = tmp.shift
           if x == '..' && base_path.size > 1
Index: test/uri/test_generic.rb

--- test/uri/test_generic.rb (revision 12297)
+++ test/uri/test_generic.rb (working copy)
@@ -297,9 +297,9 @@ class TestGeneric < Test::Unit::TestCase

# http://a/b/c/d;p?q
-# ?y = http://a/b/c/?y
+# ?y = http://a/b/c/d;p?y
     url = @base_url.merge('?y')
     assert_kind_of(URI::HTTP, url)
- assert_equal('http://a/b/c/?y&#39;, url.to_s)
- url = @base_url.route_to('http://a/b/c/?y&#39;\)
+ assert_equal('http://a/b/c/d;p?y&#39;, url.to_s)
+ url = @base_url.route_to('http://a/b/c/d;p?y&#39;\)
     assert_kind_of(URI::Generic, url)
     assert_equal('?y', url.to_s)

--
Nobu Nakada

Nobuyoshi Nakada <nobu@ruby-lang.org> writes:

Hi,

At Sat, 19 May 2007 00:52:37 +0900,
Daniel Martin wrote in [ruby-talk:252116]:

You know what? I'm using an old RFC. Everything I said applies to
RFC 2396, but that's not the current URI RFC. The current one is
RFC 3986, and by that one firefox is doing exactly the right thing.
(So, by the way, is Opera)

So it is a bug in URI, and the bug is "written to an old RFC".

Then, the test is wrong too?

Yes, it is. That test case is straight out of RFC 2396, and it is
clearly incorrect given the pseudocode in section 5.2.2 (pages 31-32)
of RFC 3986.

RFC 3986 updates the test cases, including them in section 5.4 (Not
in an appendix as RFC 2396 did) That test case is mentioned, and the
behavior is indeed what it looks like from the pseudocode.

We should probably check that ruby's uri module passes all the tests
now in RFC 3986. If I have a chance I'll go do that.

···

--
s=%q( Daniel Martin -- martin@snowplow.org
       puts "s=%q(#{s})",s.to_a.last )
       puts "s=%q(#{s})",s.to_a.last

Daniel Martin <martin@snowplow.org> writes:

RFC 3986 updates the test cases, including them in section 5.4 (Not
in an appendix as RFC 2396 did) That test case is mentioned, and the
behavior is indeed what it looks like from the pseudocode.

We should probably check that ruby's uri module passes all the tests
now in RFC 3986. If I have a chance I'll go do that.

I've converted all the test examples in that section of RFC 3986 into
test code; see below.

And even with Nakada-san's patch, there's still a problem or two with
our resolver relative to the RFC 3986 test cases. However, with that
patch it passes everything labeled in the RFC as "Normal Cases"
(section 5.4.1). It still has issues with the abnormal cases of
section 5.4.2

#! /usr/bin/env ruby
# This is test only of the merge method based on the cases in the text
# of RFC 3986, section 5.4. The test cases were cut-and-pasted
# directly to reduce the chance of transcription error.

require 'test/unit'
require 'uri'
require 'enumerator'

module URI
    class TestGenericMerge < Test::Unit::TestCase
        def setup
            @url = 'http://a/b/c/d;p?q&#39;
            @base_url = URI.parse(@url)
        end
        def test_merge
            testcases = %w[
      "g:h" = "g:h"
      "g" = "http://a/b/c/g&quot;
      "./g" = "http://a/b/c/g&quot;
      "g/" = "http://a/b/c/g/&quot;
      "/g" = "http://a/g&quot;
      "//g" = "http://g"
      "?y" = "http://a/b/c/d;p?y&quot;
      "g?y" = "http://a/b/c/g?y&quot;
      "#s" = "http://a/b/c/d;p?q#s&quot;
      "g#s" = "http://a/b/c/g#s&quot;
      "g?y#s" = "http://a/b/c/g?y#s&quot;
      ";x" = "http://a/b/c/;x&quot;
      "g;x" = "http://a/b/c/g;x&quot;
      "g;x?y#s" = "http://a/b/c/g;x?y#s&quot;
      "" = "http://a/b/c/d;p?q&quot;
      "." = "http://a/b/c/&quot;
      "./" = "http://a/b/c/&quot;
      ".." = "http://a/b/&quot;
      "../" = "http://a/b/&quot;
      "../g" = "http://a/b/g&quot;
      "../.." = "http://a/&quot;
      "../../" = "http://a/&quot;
      "../../g" = "http://a/g&quot;
      
      "../../../g" = "http://a/g&quot;
      "../../../../g" = "http://a/g&quot;
      
      "/./g" = "http://a/g&quot;
      "/../g" = "http://a/g&quot;
      "g." = "http://a/b/c/g\.&quot;
      ".g" = "http://a/b/c/.g&quot;
      "g.." = "http://a/b/c/g\.\.&quot;
      "..g" = "http://a/b/c/..g&quot;
      
      "./../g" = "http://a/b/g&quot;
      "./g/." = "http://a/b/c/g/&quot;
      "g/./h" = "http://a/b/c/g/h&quot;
      "g/../h" = "http://a/b/c/h&quot;
      "g;x=1/./y" = "http://a/b/c/g;x=1/y&quot;
      "g;x=1/../y" = "http://a/b/c/y&quot;
      
      "g?y/./x" = "http://a/b/c/g?y/./x&quot;
      "g?y/../x" = "http://a/b/c/g?y/../x&quot;
      "g#s/./x" = "http://a/b/c/g#s/./x&quot;
      "g#s/../x" = "http://a/b/c/g#s/../x&quot;
      
      "http:g" = "http:g"
            ]
            testcases.each_slice(3) { |rel, eq, expected|
                rel.gsub!(/"/,'')
                expected.gsub!(/"/,'')
                assert_equal(expected, @base_url.merge(rel).to_s, rel)
            }
        end
    end
end

__END__

···

--
s=%q( Daniel Martin -- martin@snowplow.org
       puts "s=%q(#{s})",s.to_a.last )
       puts "s=%q(#{s})",s.to_a.last

Hi,

At Sat, 19 May 2007 04:03:13 +0900,
Daniel Martin wrote in [ruby-talk:252151]:

I've converted all the test examples in that section of RFC 3986 into
test code; see below.

Thank you and sorry to be late.

And even with Nakada-san's patch, there's still a problem or two with
our resolver relative to the RFC 3986 test cases. However, with that
patch it passes everything labeled in the RFC as "Normal Cases"
(section 5.4.1). It still has issues with the abnormal cases of
section 5.4.2

URI.parse("ftp://example.com/pub").path returns "/pub" in 1.8
while "pub" in 1.9. Is 1.9 correct?

Index: lib/uri/generic.rb

···

===================================================================
--- lib/uri/generic.rb (revision 12858)
+++ lib/uri/generic.rb (working copy)
@@ -617,10 +617,6 @@ module URI

     def merge_path(base, rel)
- # RFC2396, Section 5.2, 5)
- if rel[0] == ?/ #/
- # RFC2396, Section 5.2, 5)
- return rel

- else
+ # RFC2396, Section 5.2, 5)
         # RFC2396, Section 5.2, 6)
         base_path = split_path(base)
@@ -632,8 +628,8 @@ module URI
     base_path.slice!(i - 1, 2)
         end
- if base_path.empty?
- base_path = [''] # keep '/' for root directory
- else
- base_path.pop
+
+ if (first = rel_path.first) and first.empty?
+ base_path.clear
+ rel_path.shift
         end

@@ -654,10 +650,15 @@ module URI
         end

- add_trailer_slash = true
+ add_trailer_slash = !tmp.empty?
+ if base_path.empty?
+ base_path = [''] # keep '/' for root directory
+ elsif add_trailer_slash
+ base_path.pop
+ end
         while x = tmp.shift
- if x == '..' && base_path.size > 1
+ if x == '..'
             # RFC2396, Section 4
             # a .. or . in an absolute path has no special meaning
- base_path.pop
+ base_path.pop if base_path.size > 1
           else
             # if x == '..'
@@ -676,5 +677,4 @@ module URI
         return base_path.join('/')
       end
- end
     private :merge_path

Index: test/uri/test_generic.rb

--- test/uri/test_generic.rb (revision 12858)
+++ test/uri/test_generic.rb (working copy)
@@ -1,9 +1,7 @@
require 'test/unit'
require 'uri'
+require 'enumerator'

-module URI
-
-
-class TestGeneric < Test::Unit::TestCase
+class URI::TestGeneric < Test::Unit::TestCase
   def setup
     @url = 'http://a/b/c/d;p?q&#39;
@@ -297,9 +295,9 @@ class TestGeneric < Test::Unit::TestCase

# http://a/b/c/d;p?q
-# ?y = http://a/b/c/?y
+# ?y = http://a/b/c/d;p?y
     url = @base_url.merge('?y')
     assert_kind_of(URI::HTTP, url)
- assert_equal('http://a/b/c/?y&#39;, url.to_s)
- url = @base_url.route_to('http://a/b/c/?y&#39;\)
+ assert_equal('http://a/b/c/d;p?y&#39;, url.to_s)
+ url = @base_url.route_to('http://a/b/c/d;p?y&#39;\)
     assert_kind_of(URI::Generic, url)
     assert_equal('?y', url.to_s)
@@ -453,8 +451,8 @@ class TestGeneric < Test::Unit::TestCase

# http://a/b/c/d;p?q
-# /./g = http://a/./g
+# /./g = http://a/g
     url = @base_url.merge('/./g')
     assert_kind_of(URI::HTTP, url)
- assert_equal('http://a/./g&#39;, url.to_s)
+ assert_equal('http://a/g&#39;, url.to_s)
     url = @base_url.route_to('http://a/./g&#39;\)
     assert_kind_of(URI::Generic, url)
@@ -465,5 +463,5 @@ class TestGeneric < Test::Unit::TestCase
     url = @base_url.merge('/../g')
     assert_kind_of(URI::HTTP, url)
- assert_equal('http://a/../g&#39;, url.to_s)
+ assert_equal('http://a/g&#39;, url.to_s)
     url = @base_url.route_to('http://a/../g&#39;\)
     assert_kind_of(URI::Generic, url)
@@ -507,8 +505,8 @@ class TestGeneric < Test::Unit::TestCase

# http://a/b/c/d;p?q
-# ../../../g = http://a/../g
+# ../../../g = http://a/g
     url = @base_url.merge('../../../g')
     assert_kind_of(URI::HTTP, url)
- assert_equal('http://a/../g&#39;, url.to_s)
+ assert_equal('http://a/g&#39;, url.to_s)
     url = @base_url.route_to('http://a/../g&#39;\)
     assert_kind_of(URI::Generic, url)
@@ -520,5 +518,5 @@ class TestGeneric < Test::Unit::TestCase
     url = @base_url.merge('../../../../g')
     assert_kind_of(URI::HTTP, url)
- assert_equal('http://a/../../g&#39;, url.to_s)
+ assert_equal('http://a/g&#39;, url.to_s)
     url = @base_url.route_to('http://a/../../g&#39;\)
     assert_kind_of(URI::Generic, url)
@@ -693,6 +691,56 @@ class TestGeneric < Test::Unit::TestCase
     assert_raises(URI::InvalidURIError) { uri.query = 'bar' }
   end
+
+ def m(s)
+ @base_url.merge(s).to_s
end

+ def test_rfc3986_examples
+ assert_equal("g:h", m("g:h"))
+ assert_equal("http://a/b/c/g&quot;, m("g"))
+ assert_equal("http://a/b/c/g&quot;, m("./g"))
+ assert_equal("http://a/b/c/g/&quot;, m("g/"))
+ assert_equal("http://a/g&quot;, m("/g"))
+ assert_equal("http://g", m("//g"))
+ assert_equal("http://a/b/c/d;p?y&quot;, m("?y"))
+ assert_equal("http://a/b/c/g?y&quot;, m("g?y"))
+ assert_equal("http://a/b/c/d;p?q#s&quot;, m("#s"))
+ assert_equal("http://a/b/c/g#s&quot;, m("g#s"))
+ assert_equal("http://a/b/c/g?y#s&quot;, m("g?y#s"))
+ assert_equal("http://a/b/c/;x&quot;, m(";x"))
+ assert_equal("http://a/b/c/g;x&quot;, m("g;x"))
+ assert_equal("http://a/b/c/g;x?y#s&quot;, m("g;x?y#s"))
+ assert_equal("http://a/b/c/d;p?q&quot;, m(""))
+ assert_equal("http://a/b/c/&quot;, m("."))
+ assert_equal("http://a/b/c/&quot;, m("./"))
+ assert_equal("http://a/b/&quot;, m(".."))
+ assert_equal("http://a/b/&quot;, m("../"))
+ assert_equal("http://a/b/g&quot;, m("../g"))
+ assert_equal("http://a/&quot;, m("../.."))
+ assert_equal("http://a/&quot;, m("../../"))
+ assert_equal("http://a/g&quot;, m("../../g"))
+ assert_equal("http://a/g&quot;, m("../../../g"))
+ assert_equal("http://a/g&quot;, m("../../../../g"))
+
+ assert_equal("http://a/g&quot;, m("/./g"))
+ assert_equal("http://a/g&quot;, m("/../g"))
+ assert_equal("http://a/b/c/g\.&quot;, m("g."))
+ assert_equal("http://a/b/c/.g&quot;, m(".g"))
+ assert_equal("http://a/b/c/g\.\.&quot;, m("g.."))
+ assert_equal("http://a/b/c/..g&quot;, m("..g"))
+
+ assert_equal("http://a/b/g&quot;, m("./../g"))
+ assert_equal("http://a/b/c/g/&quot;, m("./g/."))
+ assert_equal("http://a/b/c/g/h&quot;, m("g/./h"))
+ assert_equal("http://a/b/c/h&quot;, m("g/../h"))
+ assert_equal("http://a/b/c/g;x=1/y&quot;, m("g;x=1/./y"))
+ assert_equal("http://a/b/c/y&quot;, m("g;x=1/../y"))
+
+ assert_equal("http://a/b/c/g?y/./x&quot;, m("g?y/./x"))
+ assert_equal("http://a/b/c/g?y/../x&quot;, m("g?y/../x"))
+ assert_equal("http://a/b/c/g#s/./x&quot;, m("g#s/./x"))
+ assert_equal("http://a/b/c/g#s/../x&quot;, m("g#s/../x"))

+ assert_equal("http:g", m("http:g"))
+ end
end

--
Nobu Nakada

Hi Nakada,

I am a beginner of Ruby, I also meet this bug which you fixed.
Now my question is how I can get the file you patched?
I have visited the http://svn.ruby-lang.org/repos/ruby/trunk/lib/uri/
But I found the file is not patched.

So could you help me?

Many Thanks.

Nan

···

2007/7/31, Nobuyoshi Nakada <nobu@ruby-lang.org>:

Hi,

At Sat, 19 May 2007 04:03:13 +0900,
Daniel Martin wrote in [ruby-talk:252151]:
> I've converted all the test examples in that section of RFC 3986 into
> test code; see below.

Thank you and sorry to be late.

> And even with Nakada-san's patch, there's still a problem or two with
> our resolver relative to the RFC 3986 test cases. However, with that
> patch it passes everything labeled in the RFC as "Normal Cases"
> (section 5.4.1). It still has issues with the abnormal cases of
> section 5.4.2

URI.parse("ftp://example.com/pub").path returns "/pub" in 1.8
while "pub" in 1.9. Is 1.9 correct?

Index: lib/uri/generic.rb

--- lib/uri/generic.rb (revision 12858)
+++ lib/uri/generic.rb (working copy)
@@ -617,10 +617,6 @@ module URI

    def merge_path(base, rel)
- # RFC2396, Section 5.2, 5)
- if rel[0] == ?/ #/
- # RFC2396, Section 5.2, 5)
- return rel

- else
+ # RFC2396, Section 5.2, 5)
        # RFC2396, Section 5.2, 6)
        base_path = split_path(base)
@@ -632,8 +628,8 @@ module URI
         base_path.slice!(i - 1, 2)
        end
- if base_path.empty?
- base_path = [''] # keep '/' for root directory
- else
- base_path.pop
+
+ if (first = rel_path.first) and first.empty?
+ base_path.clear
+ rel_path.shift
        end

@@ -654,10 +650,15 @@ module URI
        end

- add_trailer_slash = true
+ add_trailer_slash = !tmp.empty?
+ if base_path.empty?
+ base_path = [''] # keep '/' for root directory
+ elsif add_trailer_slash
+ base_path.pop
+ end
        while x = tmp.shift
- if x == '..' && base_path.size > 1
+ if x == '..'
            # RFC2396, Section 4
            # a .. or . in an absolute path has no special meaning
- base_path.pop
+ base_path.pop if base_path.size > 1
          else
            # if x == '..'
@@ -676,5 +677,4 @@ module URI
        return base_path.join('/')
      end
- end
    private :merge_path

Index: test/uri/test_generic.rb

--- test/uri/test_generic.rb (revision 12858)
+++ test/uri/test_generic.rb (working copy)
@@ -1,9 +1,7 @@
require 'test/unit'
require 'uri'
+require 'enumerator'

-module URI
-
-
-class TestGeneric < Test::Unit::TestCase
+class URI::TestGeneric < Test::Unit::TestCase
  def setup
    @url = 'http://a/b/c/d;p?q&#39;
@@ -297,9 +295,9 @@ class TestGeneric < Test::Unit::TestCase

# http://a/b/c/d;p?q
-# ?y = http://a/b/c/?y
+# ?y = http://a/b/c/d;p?y
    url = @base_url.merge('?y')
    assert_kind_of(URI::HTTP, url)
- assert_equal('http://a/b/c/?y&#39;, url.to_s)
- url = @base_url.route_to('http://a/b/c/?y&#39;\)
+ assert_equal('http://a/b/c/d;p?y&#39;, url.to_s)
+ url = @base_url.route_to('http://a/b/c/d;p?y&#39;\)
    assert_kind_of(URI::Generic, url)
    assert_equal('?y', url.to_s)
@@ -453,8 +451,8 @@ class TestGeneric < Test::Unit::TestCase

# http://a/b/c/d;p?q
-# /./g = http://a/./g
+# /./g = http://a/g
    url = @base_url.merge('/./g')
    assert_kind_of(URI::HTTP, url)
- assert_equal('http://a/./g&#39;, url.to_s)
+ assert_equal('http://a/g&#39;, url.to_s)
    url = @base_url.route_to('http://a/./g&#39;\)
    assert_kind_of(URI::Generic, url)
@@ -465,5 +463,5 @@ class TestGeneric < Test::Unit::TestCase
    url = @base_url.merge('/../g')
    assert_kind_of(URI::HTTP, url)
- assert_equal('http://a/../g&#39;, url.to_s)
+ assert_equal('http://a/g&#39;, url.to_s)
    url = @base_url.route_to('http://a/../g&#39;\)
    assert_kind_of(URI::Generic, url)
@@ -507,8 +505,8 @@ class TestGeneric < Test::Unit::TestCase

# http://a/b/c/d;p?q
-# ../../../g = http://a/../g
+# ../../../g = http://a/g
    url = @base_url.merge('../../../g')
    assert_kind_of(URI::HTTP, url)
- assert_equal('http://a/../g&#39;, url.to_s)
+ assert_equal('http://a/g&#39;, url.to_s)
    url = @base_url.route_to('http://a/../g&#39;\)
    assert_kind_of(URI::Generic, url)
@@ -520,5 +518,5 @@ class TestGeneric < Test::Unit::TestCase
    url = @base_url.merge('../../../../g')
    assert_kind_of(URI::HTTP, url)
- assert_equal('http://a/../../g&#39;, url.to_s)
+ assert_equal('http://a/g&#39;, url.to_s)
    url = @base_url.route_to('http://a/../../g&#39;\)
    assert_kind_of(URI::Generic, url)
@@ -693,6 +691,56 @@ class TestGeneric < Test::Unit::TestCase
    assert_raises(URI::InvalidURIError) { uri.query = 'bar' }
  end
+
+ def m(s)
+ @base_url.merge(s).to_s
end

+ def test_rfc3986_examples
+ assert_equal("g:h", m("g:h"))
+ assert_equal("http://a/b/c/g&quot;, m("g"))
+ assert_equal("http://a/b/c/g&quot;, m("./g"))
+ assert_equal("http://a/b/c/g/&quot;, m("g/"))
+ assert_equal("http://a/g&quot;, m("/g"))
+ assert_equal("http://g", m("//g"))
+ assert_equal("http://a/b/c/d;p?y&quot;, m("?y"))
+ assert_equal("http://a/b/c/g?y&quot;, m("g?y"))
+ assert_equal("http://a/b/c/d;p?q#s&quot;, m("#s"))
+ assert_equal("http://a/b/c/g#s&quot;, m("g#s"))
+ assert_equal("http://a/b/c/g?y#s&quot;, m("g?y#s"))
+ assert_equal("http://a/b/c/;x&quot;, m(";x"))
+ assert_equal("http://a/b/c/g;x&quot;, m("g;x"))
+ assert_equal("http://a/b/c/g;x?y#s&quot;, m("g;x?y#s"))
+ assert_equal("http://a/b/c/d;p?q&quot;, m(""))
+ assert_equal("http://a/b/c/&quot;, m("."))
+ assert_equal("http://a/b/c/&quot;, m("./"))
+ assert_equal("http://a/b/&quot;, m(".."))
+ assert_equal("http://a/b/&quot;, m("../"))
+ assert_equal("http://a/b/g&quot;, m("../g"))
+ assert_equal("http://a/&quot;, m("../.."))
+ assert_equal("http://a/&quot;, m("../../"))
+ assert_equal("http://a/g&quot;, m("../../g"))
+ assert_equal("http://a/g&quot;, m("../../../g"))
+ assert_equal("http://a/g&quot;, m("../../../../g"))
+
+ assert_equal("http://a/g&quot;, m("/./g"))
+ assert_equal("http://a/g&quot;, m("/../g"))
+ assert_equal("http://a/b/c/g\.&quot;, m("g."))
+ assert_equal("http://a/b/c/.g&quot;, m(".g"))
+ assert_equal("http://a/b/c/g\.\.&quot;, m("g.."))
+ assert_equal("http://a/b/c/..g&quot;, m("..g"))
+
+ assert_equal("http://a/b/g&quot;, m("./../g"))
+ assert_equal("http://a/b/c/g/&quot;, m("./g/."))
+ assert_equal("http://a/b/c/g/h&quot;, m("g/./h"))
+ assert_equal("http://a/b/c/h&quot;, m("g/../h"))
+ assert_equal("http://a/b/c/g;x=1/y&quot;, m("g;x=1/./y"))
+ assert_equal("http://a/b/c/y&quot;, m("g;x=1/../y"))
+
+ assert_equal("http://a/b/c/g?y/./x&quot;, m("g?y/./x"))
+ assert_equal("http://a/b/c/g?y/../x&quot;, m("g?y/../x"))
+ assert_equal("http://a/b/c/g#s/./x&quot;, m("g#s/./x"))
+ assert_equal("http://a/b/c/g#s/../x&quot;, m("g#s/../x"))

+ assert_equal("http:g", m("http:g"))
+ end
end

--
Nobu Nakada

Hi,

···

In message "Re: URI::merge bug?" on Tue, 31 Jul 2007 04:45:12 +0900, Nobuyoshi Nakada <nobu@ruby-lang.org> writes:

Index: lib/uri/generic.rb

--- lib/uri/generic.rb (revision 12858)
+++ lib/uri/generic.rb (working copy)

Could you commit?

              matz.

Hi,

At Tue, 31 Jul 2007 12:21:33 +0900,
nan wu wrote in [ruby-talk:262574]:

Now my question is how I can get the file you patched?
I have visited the http://svn.ruby-lang.org/repos/ruby/trunk/lib/uri/
But I found the file is not patched.

You can patch with "patch" command, or:
<http://www.rubyist.net/~nobu/ruby/uri/generic.rb&gt;

···

--
Nobu Nakada

Hi

Thanks a lot, it works well. ^O^

Nan

···

2007/7/31, Nobuyoshi Nakada <nobu@ruby-lang.org>:

Hi,

At Tue, 31 Jul 2007 12:21:33 +0900,
nan wu wrote in [ruby-talk:262574]:
> Now my question is how I can get the file you patched?
> I have visited the http://svn.ruby-lang.org/repos/ruby/trunk/lib/uri/
> But I found the file is not patched.

You can patch with "patch" command, or:
<http://www.rubyist.net/~nobu/ruby/uri/generic.rb&gt;

--
Nobu Nakada