michael@0: /* michael@0: * ==================================================================== michael@0: * michael@0: * Licensed to the Apache Software Foundation (ASF) under one or more michael@0: * contributor license agreements. See the NOTICE file distributed with michael@0: * this work for additional information regarding copyright ownership. michael@0: * The ASF licenses this file to You under the Apache License, Version 2.0 michael@0: * (the "License"); you may not use this file except in compliance with michael@0: * the License. You may obtain a copy of the License at michael@0: * michael@0: * http://www.apache.org/licenses/LICENSE-2.0 michael@0: * michael@0: * Unless required by applicable law or agreed to in writing, software michael@0: * distributed under the License is distributed on an "AS IS" BASIS, michael@0: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. michael@0: * See the License for the specific language governing permissions and michael@0: * limitations under the License. michael@0: * ==================================================================== michael@0: * michael@0: * This software consists of voluntary contributions made by many michael@0: * individuals on behalf of the Apache Software Foundation. For more michael@0: * information on the Apache Software Foundation, please see michael@0: * . michael@0: * michael@0: */ michael@0: michael@0: package ch.boye.httpclientandroidlib.client.utils; michael@0: michael@0: import java.net.URI; michael@0: import java.net.URISyntaxException; michael@0: import java.util.Stack; michael@0: michael@0: import ch.boye.httpclientandroidlib.annotation.Immutable; michael@0: michael@0: import ch.boye.httpclientandroidlib.HttpHost; michael@0: michael@0: /** michael@0: * A collection of utilities for {@link URI URIs}, to workaround michael@0: * bugs within the class or for ease-of-use features. michael@0: * michael@0: * @since 4.0 michael@0: */ michael@0: @Immutable michael@0: public class URIUtils { michael@0: michael@0: /** michael@0: * Constructs a {@link URI} using all the parameters. This should be michael@0: * used instead of michael@0: * {@link URI#URI(String, String, String, int, String, String, String)} michael@0: * or any of the other URI multi-argument URI constructors. michael@0: * michael@0: * @param scheme michael@0: * Scheme name michael@0: * @param host michael@0: * Host name michael@0: * @param port michael@0: * Port number michael@0: * @param path michael@0: * Path michael@0: * @param query michael@0: * Query michael@0: * @param fragment michael@0: * Fragment michael@0: * michael@0: * @throws URISyntaxException michael@0: * If both a scheme and a path are given but the path is michael@0: * relative, if the URI string constructed from the given michael@0: * components violates RFC 2396, or if the authority michael@0: * component of the string is present but cannot be parsed michael@0: * as a server-based authority michael@0: */ michael@0: public static URI createURI( michael@0: final String scheme, michael@0: final String host, michael@0: int port, michael@0: final String path, michael@0: final String query, michael@0: final String fragment) throws URISyntaxException { michael@0: michael@0: StringBuilder buffer = new StringBuilder(); michael@0: if (host != null) { michael@0: if (scheme != null) { michael@0: buffer.append(scheme); michael@0: buffer.append("://"); michael@0: } michael@0: buffer.append(host); michael@0: if (port > 0) { michael@0: buffer.append(':'); michael@0: buffer.append(port); michael@0: } michael@0: } michael@0: if (path == null || !path.startsWith("/")) { michael@0: buffer.append('/'); michael@0: } michael@0: if (path != null) { michael@0: buffer.append(path); michael@0: } michael@0: if (query != null) { michael@0: buffer.append('?'); michael@0: buffer.append(query); michael@0: } michael@0: if (fragment != null) { michael@0: buffer.append('#'); michael@0: buffer.append(fragment); michael@0: } michael@0: return new URI(buffer.toString()); michael@0: } michael@0: michael@0: /** michael@0: * A convenience method for creating a new {@link URI} whose scheme, host michael@0: * and port are taken from the target host, but whose path, query and michael@0: * fragment are taken from the existing URI. The fragment is only used if michael@0: * dropFragment is false. michael@0: * michael@0: * @param uri michael@0: * Contains the path, query and fragment to use. michael@0: * @param target michael@0: * Contains the scheme, host and port to use. michael@0: * @param dropFragment michael@0: * True if the fragment should not be copied. michael@0: * michael@0: * @throws URISyntaxException michael@0: * If the resulting URI is invalid. michael@0: */ michael@0: public static URI rewriteURI( michael@0: final URI uri, michael@0: final HttpHost target, michael@0: boolean dropFragment) throws URISyntaxException { michael@0: if (uri == null) { michael@0: throw new IllegalArgumentException("URI may nor be null"); michael@0: } michael@0: if (target != null) { michael@0: return URIUtils.createURI( michael@0: target.getSchemeName(), michael@0: target.getHostName(), michael@0: target.getPort(), michael@0: normalizePath(uri.getRawPath()), michael@0: uri.getRawQuery(), michael@0: dropFragment ? null : uri.getRawFragment()); michael@0: } else { michael@0: return URIUtils.createURI( michael@0: null, michael@0: null, michael@0: -1, michael@0: normalizePath(uri.getRawPath()), michael@0: uri.getRawQuery(), michael@0: dropFragment ? null : uri.getRawFragment()); michael@0: } michael@0: } michael@0: michael@0: private static String normalizePath(String path) { michael@0: if (path == null) { michael@0: return null; michael@0: } michael@0: int n = 0; michael@0: for (; n < path.length(); n++) { michael@0: if (path.charAt(n) != '/') { michael@0: break; michael@0: } michael@0: } michael@0: if (n > 1) { michael@0: path = path.substring(n - 1); michael@0: } michael@0: return path; michael@0: } michael@0: michael@0: /** michael@0: * A convenience method for michael@0: * {@link URIUtils#rewriteURI(URI, HttpHost, boolean)} that always keeps the michael@0: * fragment. michael@0: */ michael@0: public static URI rewriteURI( michael@0: final URI uri, michael@0: final HttpHost target) throws URISyntaxException { michael@0: return rewriteURI(uri, target, false); michael@0: } michael@0: michael@0: /** michael@0: * Resolves a URI reference against a base URI. Work-around for bug in michael@0: * java.net.URI () michael@0: * michael@0: * @param baseURI the base URI michael@0: * @param reference the URI reference michael@0: * @return the resulting URI michael@0: */ michael@0: public static URI resolve(final URI baseURI, final String reference) { michael@0: return URIUtils.resolve(baseURI, URI.create(reference)); michael@0: } michael@0: michael@0: /** michael@0: * Resolves a URI reference against a base URI. Work-around for bugs in michael@0: * java.net.URI (e.g. ) michael@0: * michael@0: * @param baseURI the base URI michael@0: * @param reference the URI reference michael@0: * @return the resulting URI michael@0: */ michael@0: public static URI resolve(final URI baseURI, URI reference){ michael@0: if (baseURI == null) { michael@0: throw new IllegalArgumentException("Base URI may nor be null"); michael@0: } michael@0: if (reference == null) { michael@0: throw new IllegalArgumentException("Reference URI may nor be null"); michael@0: } michael@0: String s = reference.toString(); michael@0: if (s.startsWith("?")) { michael@0: return resolveReferenceStartingWithQueryString(baseURI, reference); michael@0: } michael@0: boolean emptyReference = s.length() == 0; michael@0: if (emptyReference) { michael@0: reference = URI.create("#"); michael@0: } michael@0: URI resolved = baseURI.resolve(reference); michael@0: if (emptyReference) { michael@0: String resolvedString = resolved.toString(); michael@0: resolved = URI.create(resolvedString.substring(0, michael@0: resolvedString.indexOf('#'))); michael@0: } michael@0: return removeDotSegments(resolved); michael@0: } michael@0: michael@0: /** michael@0: * Resolves a reference starting with a query string. michael@0: * michael@0: * @param baseURI the base URI michael@0: * @param reference the URI reference starting with a query string michael@0: * @return the resulting URI michael@0: */ michael@0: private static URI resolveReferenceStartingWithQueryString( michael@0: final URI baseURI, final URI reference) { michael@0: String baseUri = baseURI.toString(); michael@0: baseUri = baseUri.indexOf('?') > -1 ? michael@0: baseUri.substring(0, baseUri.indexOf('?')) : baseUri; michael@0: return URI.create(baseUri + reference.toString()); michael@0: } michael@0: michael@0: /** michael@0: * Removes dot segments according to RFC 3986, section 5.2.4 michael@0: * michael@0: * @param uri the original URI michael@0: * @return the URI without dot segments michael@0: */ michael@0: private static URI removeDotSegments(URI uri) { michael@0: String path = uri.getPath(); michael@0: if ((path == null) || (path.indexOf("/.") == -1)) { michael@0: // No dot segments to remove michael@0: return uri; michael@0: } michael@0: String[] inputSegments = path.split("/"); michael@0: Stack outputSegments = new Stack(); michael@0: for (int i = 0; i < inputSegments.length; i++) { michael@0: if ((inputSegments[i].length() == 0) michael@0: || (".".equals(inputSegments[i]))) { michael@0: // Do nothing michael@0: } else if ("..".equals(inputSegments[i])) { michael@0: if (!outputSegments.isEmpty()) { michael@0: outputSegments.pop(); michael@0: } michael@0: } else { michael@0: outputSegments.push(inputSegments[i]); michael@0: } michael@0: } michael@0: StringBuilder outputBuffer = new StringBuilder(); michael@0: for (String outputSegment : outputSegments) { michael@0: outputBuffer.append('/').append(outputSegment); michael@0: } michael@0: try { michael@0: return new URI(uri.getScheme(), uri.getAuthority(), michael@0: outputBuffer.toString(), uri.getQuery(), uri.getFragment()); michael@0: } catch (URISyntaxException e) { michael@0: throw new IllegalArgumentException(e); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Extracts target host from the given {@link URI}. michael@0: * michael@0: * @param uri michael@0: * @return the target host if the URI is absolute or null if the URI is michael@0: * relative or does not contain a valid host name. michael@0: * michael@0: * @since 4.1 michael@0: */ michael@0: public static HttpHost extractHost(final URI uri) { michael@0: if (uri == null) { michael@0: return null; michael@0: } michael@0: HttpHost target = null; michael@0: if (uri.isAbsolute()) { michael@0: int port = uri.getPort(); // may be overridden later michael@0: String host = uri.getHost(); michael@0: if (host == null) { // normal parse failed; let's do it ourselves michael@0: // authority does not seem to care about the valid character-set for host names michael@0: host = uri.getAuthority(); michael@0: if (host != null) { michael@0: // Strip off any leading user credentials michael@0: int at = host.indexOf('@'); michael@0: if (at >= 0) { michael@0: if (host.length() > at+1 ) { michael@0: host = host.substring(at+1); michael@0: } else { michael@0: host = null; // @ on its own michael@0: } michael@0: } michael@0: // Extract the port suffix, if present michael@0: if (host != null) { michael@0: int colon = host.indexOf(':'); michael@0: if (colon >= 0) { michael@0: if (colon+1 < host.length()) { michael@0: port = Integer.parseInt(host.substring(colon+1)); michael@0: } michael@0: host = host.substring(0,colon); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: String scheme = uri.getScheme(); michael@0: if (host != null) { michael@0: target = new HttpHost(host, port, scheme); michael@0: } michael@0: } michael@0: return target; michael@0: } michael@0: michael@0: /** michael@0: * This class should not be instantiated. michael@0: */ michael@0: private URIUtils() { michael@0: } michael@0: michael@0: }