From 55d9c6f161b50c152fb31f77054022101acb8a7b Mon Sep 17 00:00:00 2001 From: Oleg Kalnichevski Date: Thu, 16 Jul 2009 21:30:38 +0000 Subject: [PATCH] HTTPCLIENT-861: URI reference resolution fails examples in RFC 3986 git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@794870 13f79535-47bb-0310-9956-ffa450edef68 --- RELEASE_NOTES.txt | 105 ++------ .../apache/http/client/utils/URIUtils.java | 66 ++++- .../http/client/utils/TestRfc3492Idn.java | 4 - .../http/client/utils/TestURIUtils.java | 251 ++++++++++++++++++ 4 files changed, 328 insertions(+), 98 deletions(-) create mode 100644 httpclient/src/test/java/org/apache/http/client/utils/TestURIUtils.java diff --git a/RELEASE_NOTES.txt b/RELEASE_NOTES.txt index 968eb2007..7b4ab2f42 100644 --- a/RELEASE_NOTES.txt +++ b/RELEASE_NOTES.txt @@ -47,21 +47,25 @@ Important notes Some protected variables in connection management class have been made final in order to help ensure their thread safety: - org.apache.http.conn.BasicEofSensorWatcher#attemptReuse - org.apache.http.conn.BasicEofSensorWatcher#managedConn - org.apache.http.impl.conn.DefaultClientConnectionOperator#schemeRegistry - org.apache.http.impl.conn.DefaultHttpRoutePlanner#schemeRegistry - org.apache.http.impl.conn.ProxySelectorRoutePlanner#schemeRegistry - org.apache.http.impl.conn.SingleClientConnManager#alwaysShutDown - org.apache.http.impl.conn.SingleClientConnManager#connOperator - org.apache.http.impl.conn.SingleClientConnManager#schemeRegistry - org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager#connOperator - org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager#schemeRegistry - + org.apache.http.conn.BasicEofSensorWatcher#attemptReuse + org.apache.http.conn.BasicEofSensorWatcher#managedConn + org.apache.http.impl.conn.DefaultClientConnectionOperator#schemeRegistry + org.apache.http.impl.conn.DefaultHttpRoutePlanner#schemeRegistry + org.apache.http.impl.conn.ProxySelectorRoutePlanner#schemeRegistry + org.apache.http.impl.conn.SingleClientConnManager#alwaysShutDown + org.apache.http.impl.conn.SingleClientConnManager#connOperator + org.apache.http.impl.conn.SingleClientConnManager#schemeRegistry + org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager#connOperator + org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager#schemeRegistry + Bug fixes since 4.0 BETA2 release ------------------- +* [HTTPCLIENT-861] URIUtils#resolve is now compatible with all examples given + in RFC 3986. + Contributed by Johannes Koch + * [HTTPCLIENT-860] HttpClient no longer converts redirects of PUT/POST to GET for status codes 301, 302, 307, as required by the HTTP spec. Contributed by Oleg Kalnichevski @@ -153,85 +157,6 @@ All upstream projects are strongly encouraged to upgrade. Contributed by Oleg Kalnichevski -HttpClient API changes (generated by JarDiff 0.2) --------------------------------------- -Class changed: org.apache.http.conn.scheme.PlainSocketFactory - Methods removed: - public boolean equals(java.lang.Object); - public int hashCode(); - -Class changed: org.apache.http.conn.ssl.AbstractVerifier - Method changed: - old: - public final boolean verify(java.lang.String, javax.net.ssl.SSLSession); - - new: - deprecated: public final boolean verify(java.lang.String, javax.net.ssl.SSLSession); - -Class changed: org.apache.http.impl.conn.tsccm.BasicPoolEntry - Methods added: - protected void shutdownEntry(); - -Class changed: org.apache.http.impl.cookie.RFC2965Spec - Methods added: - protected java.util.List parse(org.apache.http.HeaderElement[], org.apache.http.cookie.CookieOrigin) throws org.apache.http.cookie.MalformedCookieException; - -API diff generated by JarDiff http://www.osjava.org/jardiff/ - -HttpMime API changes (generated by JarDiff 0.2) --------------------------------------- -Class added: - public org.apache.http.entity.mime.UnexpectedMimeException extends java.lang.RuntimeException -Class added: - public abstract org.apache.http.entity.mime.content.AbstractContentBody extends org.apache.james.mime4j.message.AbstractBody implements org.apache.http.entity.mime.content.ContentBody -Class changed: org.apache.http.entity.mime.content.FileBody - Methods removed: - public java.util.Map getContentTypeParameters(); - public java.lang.String getMediaType(); - public java.lang.String getMimeType(); - public java.lang.String getSubType(); - - Methods added: - public FileBody(java.io.File, java.lang.String); - - Class descriptor changed: - old: - public org.apache.http.entity.mime.content.FileBody extends org.apache.james.mime4j.message.AbstractBody implements org.apache.james.mime4j.message.BinaryBody, org.apache.http.entity.mime.content.ContentBody - new: - public org.apache.http.entity.mime.content.FileBody extends org.apache.http.entity.mime.content.AbstractContentBody implements org.apache.james.mime4j.message.BinaryBody -Class changed: org.apache.http.entity.mime.content.InputStreamBody - Methods removed: - public java.util.Map getContentTypeParameters(); - public java.lang.String getMediaType(); - public java.lang.String getMimeType(); - public java.lang.String getSubType(); - - Methods added: - public InputStreamBody(java.io.InputStream, java.lang.String, java.lang.String); - - Class descriptor changed: - old: - public org.apache.http.entity.mime.content.InputStreamBody extends org.apache.james.mime4j.message.AbstractBody implements org.apache.james.mime4j.message.BinaryBody, org.apache.http.entity.mime.content.ContentBody - new: - public org.apache.http.entity.mime.content.InputStreamBody extends org.apache.http.entity.mime.content.AbstractContentBody implements org.apache.james.mime4j.message.BinaryBody -Class changed: org.apache.http.entity.mime.content.StringBody - Methods removed: - public java.lang.String getMediaType(); - public java.lang.String getMimeType(); - public java.lang.String getSubType(); - - Methods added: - public StringBody(java.lang.String, java.lang.String, java.nio.charset.Charset) throws java.io.UnsupportedEncodingException; - - Class descriptor changed: - old: - public org.apache.http.entity.mime.content.StringBody extends org.apache.james.mime4j.message.AbstractBody implements org.apache.james.mime4j.message.TextBody, org.apache.http.entity.mime.content.ContentBody - new: - public org.apache.http.entity.mime.content.StringBody extends org.apache.http.entity.mime.content.AbstractContentBody implements org.apache.james.mime4j.message.TextBody -API diff generated by JarDiff http://www.osjava.org/jardiff/ - --------------------------------------- - 4.0 Beta 1 ------------------- diff --git a/httpclient/src/main/java/org/apache/http/client/utils/URIUtils.java b/httpclient/src/main/java/org/apache/http/client/utils/URIUtils.java index c256cf924..8e971a0da 100644 --- a/httpclient/src/main/java/org/apache/http/client/utils/URIUtils.java +++ b/httpclient/src/main/java/org/apache/http/client/utils/URIUtils.java @@ -28,6 +28,7 @@ import java.net.URI; import java.net.URISyntaxException; +import java.util.Stack; import net.jcip.annotations.Immutable; @@ -171,8 +172,8 @@ public static URI resolve(final URI baseURI, final String reference) { } /** - * Resolves a URI reference against a base URI. Work-around for bug in - * java.net.URI () + * Resolves a URI reference against a base URI. Work-around for bugs in + * java.net.URI (e.g. ) * * @param baseURI the base URI * @param reference the URI reference @@ -185,7 +186,11 @@ public static URI resolve(final URI baseURI, URI reference){ if (reference == null) { throw new IllegalArgumentException("Reference URI may nor be null"); } - boolean emptyReference = reference.toString().length() == 0; + String s = reference.toString(); + if (s.startsWith("?")) { + return resolveReferenceStartingWithQueryString(baseURI, reference); + } + boolean emptyReference = s.length() == 0; if (emptyReference) { reference = URI.create("#"); } @@ -195,7 +200,60 @@ public static URI resolve(final URI baseURI, URI reference){ resolved = URI.create(resolvedString.substring(0, resolvedString.indexOf('#'))); } - return resolved; + return removeDotSegments(resolved); + } + + /** + * Resolves a reference starting with a query string. + * + * @param baseURI the base URI + * @param reference the URI reference starting with a query string + * @return the resulting URI + */ + private static URI resolveReferenceStartingWithQueryString( + final URI baseURI, final URI reference) { + String baseUri = baseURI.toString(); + baseUri = baseUri.indexOf('?') > -1 ? + baseUri.substring(0, baseUri.indexOf('?')) : baseUri; + return URI.create(baseUri + reference.toString()); + } + + /** + * Removes dot segments according to RFC 3986, section 5.2.4 + * + * @param uri the original URI + * @return the URI without dot segments + */ + private static URI removeDotSegments(URI uri) { + String path = uri.getPath(); + if ((path == null) || (path.indexOf("/.") == -1)) { + // No dot segments to remove + return uri; + } + String[] inputSegments = path.split("/"); + Stack outputSegments = new Stack(); + for (int i = 0; i < inputSegments.length; i++) { + if ((inputSegments[i].length() == 0) + || (".".equals(inputSegments[i]))) { + // Do nothing + } else if ("..".equals(inputSegments[i])) { + if (!outputSegments.isEmpty()) { + outputSegments.pop(); + } + } else { + outputSegments.push(inputSegments[i]); + } + } + StringBuffer outputBuffer = new StringBuffer(); + for (String outputSegment : outputSegments) { + outputBuffer.append('/').append(outputSegment); + } + try { + return new URI(uri.getScheme(), uri.getAuthority(), + outputBuffer.toString(), uri.getQuery(), uri.getFragment()); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } } /** diff --git a/httpclient/src/test/java/org/apache/http/client/utils/TestRfc3492Idn.java b/httpclient/src/test/java/org/apache/http/client/utils/TestRfc3492Idn.java index 120e396b2..265820328 100644 --- a/httpclient/src/test/java/org/apache/http/client/utils/TestRfc3492Idn.java +++ b/httpclient/src/test/java/org/apache/http/client/utils/TestRfc3492Idn.java @@ -1,8 +1,4 @@ /* - * $HeadURL$ - * $Revision$ - * $Date$ - * * ==================================================================== * * Licensed to the Apache Software Foundation (ASF) under one or more diff --git a/httpclient/src/test/java/org/apache/http/client/utils/TestURIUtils.java b/httpclient/src/test/java/org/apache/http/client/utils/TestURIUtils.java new file mode 100644 index 000000000..50425ea1e --- /dev/null +++ b/httpclient/src/test/java/org/apache/http/client/utils/TestURIUtils.java @@ -0,0 +1,251 @@ +/* + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.http.client.utils; + +import java.net.URI; + +import junit.framework.Assert; +import junit.framework.Test; +import junit.framework.TestCase; +import junit.framework.TestSuite; + +/** + * This TestCase contains test methods for URI resolving according to RFC 3986. + * The examples are listed in section "5.4 Reference Resolution Examples" + */ +public class TestURIUtils extends TestCase { + + private URI baseURI = URI.create("http://a/b/c/d;p?q"); + + public TestURIUtils(final String testName) { + super(testName); + } + + public static void main(String args[]) { + String[] testCaseName = { TestURIUtils.class.getName() }; + junit.textui.TestRunner.main(testCaseName); + } + + public static Test suite() { + return new TestSuite(TestURIUtils.class); + } + + public void testResolve00() { + Assert.assertEquals("g:h", URIUtils.resolve(this.baseURI, "g:h").toString()); + } + + public void testResolve01() { + Assert.assertEquals("http://a/b/c/g", URIUtils.resolve(this.baseURI, "g").toString()); + } + + public void testResolve02() { + Assert.assertEquals("http://a/b/c/g", URIUtils.resolve(this.baseURI, "./g").toString()); + } + + public void testResolve03() { + Assert.assertEquals("http://a/b/c/g/", URIUtils.resolve(this.baseURI, "g/").toString()); + } + + public void testResolve04() { + Assert.assertEquals("http://a/g", URIUtils.resolve(this.baseURI, "/g").toString()); + } + + public void testResolve05() { + Assert.assertEquals("http://g", URIUtils.resolve(this.baseURI, "//g").toString()); + } + + public void testResolve06() { + Assert.assertEquals("http://a/b/c/d;p?y", URIUtils.resolve(this.baseURI, "?y").toString()); + } + + public void testResolve06_() { + Assert.assertEquals("http://a/b/c/d;p?y#f", URIUtils.resolve(this.baseURI, "?y#f") + .toString()); + } + + public void testResolve07() { + Assert.assertEquals("http://a/b/c/g?y", URIUtils.resolve(this.baseURI, "g?y").toString()); + } + + public void testResolve08() { + Assert + .assertEquals("http://a/b/c/d;p?q#s", URIUtils.resolve(this.baseURI, "#s") + .toString()); + } + + public void testResolve09() { + Assert.assertEquals("http://a/b/c/g#s", URIUtils.resolve(this.baseURI, "g#s").toString()); + } + + public void testResolve10() { + Assert.assertEquals("http://a/b/c/g?y#s", URIUtils.resolve(this.baseURI, "g?y#s") + .toString()); + } + + public void testResolve11() { + Assert.assertEquals("http://a/b/c/;x", URIUtils.resolve(this.baseURI, ";x").toString()); + } + + public void testResolve12() { + Assert.assertEquals("http://a/b/c/g;x", URIUtils.resolve(this.baseURI, "g;x").toString()); + } + + public void testResolve13() { + Assert.assertEquals("http://a/b/c/g;x?y#s", URIUtils.resolve(this.baseURI, "g;x?y#s") + .toString()); + } + + public void testResolve14() { + Assert.assertEquals("http://a/b/c/d;p?q", URIUtils.resolve(this.baseURI, "").toString()); + } + + public void testResolve15() { + Assert.assertEquals("http://a/b/c/", URIUtils.resolve(this.baseURI, ".").toString()); + } + + public void testResolve16() { + Assert.assertEquals("http://a/b/c/", URIUtils.resolve(this.baseURI, "./").toString()); + } + + public void testResolve17() { + Assert.assertEquals("http://a/b/", URIUtils.resolve(this.baseURI, "..").toString()); + } + + public void testResolve18() { + Assert.assertEquals("http://a/b/", URIUtils.resolve(this.baseURI, "../").toString()); + } + + public void testResolve19() { + Assert.assertEquals("http://a/b/g", URIUtils.resolve(this.baseURI, "../g").toString()); + } + + public void testResolve20() { + Assert.assertEquals("http://a/", URIUtils.resolve(this.baseURI, "../..").toString()); + } + + public void testResolve21() { + Assert.assertEquals("http://a/", URIUtils.resolve(this.baseURI, "../../").toString()); + } + + public void testResolve22() { + Assert.assertEquals("http://a/g", URIUtils.resolve(this.baseURI, "../../g").toString()); + } + + public void testResolveAbnormal23() { + Assert.assertEquals("http://a/g", URIUtils.resolve(this.baseURI, "../../../g").toString()); + } + + public void testResolveAbnormal24() { + Assert.assertEquals("http://a/g", URIUtils.resolve(this.baseURI, "../../../../g") + .toString()); + } + + public void testResolve25() { + Assert.assertEquals("http://a/g", URIUtils.resolve(this.baseURI, "/./g").toString()); + } + + public void testResolve26() { + Assert.assertEquals("http://a/g", URIUtils.resolve(this.baseURI, "/../g").toString()); + } + + public void testResolve27() { + Assert.assertEquals("http://a/b/c/g.", URIUtils.resolve(this.baseURI, "g.").toString()); + } + + public void testResolve28() { + Assert.assertEquals("http://a/b/c/.g", URIUtils.resolve(this.baseURI, ".g").toString()); + } + + public void testResolve29() { + Assert.assertEquals("http://a/b/c/g..", URIUtils.resolve(this.baseURI, "g..").toString()); + } + + public void testResolve30() { + Assert.assertEquals("http://a/b/c/..g", URIUtils.resolve(this.baseURI, "..g").toString()); + } + + public void testResolve31() { + Assert.assertEquals("http://a/b/g", URIUtils.resolve(this.baseURI, "./../g").toString()); + } + + public void testResolve32() { + Assert.assertEquals("http://a/b/c/g/", URIUtils.resolve(this.baseURI, "./g/.").toString()); + } + + public void testResolve33() { + Assert.assertEquals("http://a/b/c/g/h", URIUtils.resolve(this.baseURI, "g/./h").toString()); + } + + public void testResolve34() { + Assert.assertEquals("http://a/b/c/h", URIUtils.resolve(this.baseURI, "g/../h").toString()); + } + + public void testResolve35() { + Assert.assertEquals("http://a/b/c/g;x=1/y", URIUtils.resolve(this.baseURI, "g;x=1/./y") + .toString()); + } + + public void testResolve36() { + Assert.assertEquals("http://a/b/c/y", URIUtils.resolve(this.baseURI, "g;x=1/../y") + .toString()); + } + + public void testResolve37() { + Assert.assertEquals("http://a/b/c/g?y/./x", URIUtils.resolve(this.baseURI, "g?y/./x") + .toString()); + } + + public void testResolve38() { + Assert.assertEquals("http://a/b/c/g?y/../x", URIUtils.resolve(this.baseURI, "g?y/../x") + .toString()); + } + + public void testResolve39() { + Assert.assertEquals("http://a/b/c/g#s/./x", URIUtils.resolve(this.baseURI, "g#s/./x") + .toString()); + } + + public void testResolve40() { + Assert.assertEquals("http://a/b/c/g#s/../x", URIUtils.resolve(this.baseURI, "g#s/../x") + .toString()); + } + + public void testResolve41() { + Assert.assertEquals("http:g", URIUtils.resolve(this.baseURI, "http:g").toString()); + } + + // examples from section 5.2.4 + public void testResolve42() { + Assert.assertEquals("http://s/a/g", URIUtils.resolve(this.baseURI, + "http://s/a/b/c/./../../g").toString()); + } + + public void testResolve43() { + Assert.assertEquals("http://s/mid/6", URIUtils.resolve(this.baseURI, + "http://s/mid/content=5/../6").toString()); + } + +}