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: }