/* * ==================================================================== * * 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 * <http://www.apache.org/>. * */ package org.apache.ogt.http.client.utils; import java.net.URI; import java.net.URISyntaxException; import java.util.Stack; import org.apache.ogt.http.HttpHost; import org.apache.ogt.http.annotation.Immutable; /** * A collection of utilities for {@link URI URIs}, to workaround * bugs within the class or for ease-of-use features. * * @since 4.0 */ @Immutable public class URIUtils { /** * Constructs a {@link URI} using all the parameters. This should be * used instead of * {@link URI#URI(String, String, String, int, String, String, String)} * or any of the other URI multi-argument URI constructors. * * @param scheme * Scheme name * @param host * Host name * @param port * Port number * @param path * Path * @param query * Query * @param fragment * Fragment * * @throws URISyntaxException * If both a scheme and a path are given but the path is * relative, if the URI string constructed from the given * components violates RFC 2396, or if the authority * component of the string is present but cannot be parsed * as a server-based authority */ public static URI createURI( final String scheme, final String host, int port, final String path, final String query, final String fragment) throws URISyntaxException { StringBuilder buffer = new StringBuilder(); if (host != null) { if (scheme != null) { buffer.append(scheme); buffer.append("://"); } buffer.append(host); if (port > 0) { buffer.append(':'); buffer.append(port); } } if (path == null || !path.startsWith("/")) { buffer.append('/'); } if (path != null) { buffer.append(path); } if (query != null) { buffer.append('?'); buffer.append(query); } if (fragment != null) { buffer.append('#'); buffer.append(fragment); } return new URI(buffer.toString()); } /** * A convenience method for creating a new {@link URI} whose scheme, host * and port are taken from the target host, but whose path, query and * fragment are taken from the existing URI. The fragment is only used if * dropFragment is false. * * @param uri * Contains the path, query and fragment to use. * @param target * Contains the scheme, host and port to use. * @param dropFragment * True if the fragment should not be copied. * * @throws URISyntaxException * If the resulting URI is invalid. */ public static URI rewriteURI( final URI uri, final HttpHost target, boolean dropFragment) throws URISyntaxException { if (uri == null) { throw new IllegalArgumentException("URI may nor be null"); } if (target != null) { return URIUtils.createURI( target.getSchemeName(), target.getHostName(), target.getPort(), normalizePath(uri.getRawPath()), uri.getRawQuery(), dropFragment ? null : uri.getRawFragment()); } else { return URIUtils.createURI( null, null, -1, normalizePath(uri.getRawPath()), uri.getRawQuery(), dropFragment ? null : uri.getRawFragment()); } } private static String normalizePath(String path) { if (path == null) { return null; } int n = 0; for (; n < path.length(); n++) { if (path.charAt(n) != '/') { break; } } if (n > 1) { path = path.substring(n - 1); } return path; } /** * A convenience method for * {@link URIUtils#rewriteURI(URI, HttpHost, boolean)} that always keeps the * fragment. */ public static URI rewriteURI( final URI uri, final HttpHost target) throws URISyntaxException { return rewriteURI(uri, target, false); } /** * Resolves a URI reference against a base URI. Work-around for bug in * java.net.URI (<http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4708535>) * * @param baseURI the base URI * @param reference the URI reference * @return the resulting URI */ public static URI resolve(final URI baseURI, final String reference) { return URIUtils.resolve(baseURI, URI.create(reference)); } /** * Resolves a URI reference against a base URI. Work-around for bugs in * java.net.URI (e.g. <http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4708535>) * * @param baseURI the base URI * @param reference the URI reference * @return the resulting URI */ public static URI resolve(final URI baseURI, URI reference){ if (baseURI == null) { throw new IllegalArgumentException("Base URI may nor be null"); } if (reference == null) { throw new IllegalArgumentException("Reference URI may nor be null"); } String s = reference.toString(); if (s.startsWith("?")) { return resolveReferenceStartingWithQueryString(baseURI, reference); } boolean emptyReference = s.length() == 0; if (emptyReference) { reference = URI.create("#"); } URI resolved = baseURI.resolve(reference); if (emptyReference) { String resolvedString = resolved.toString(); resolved = URI.create(resolvedString.substring(0, resolvedString.indexOf('#'))); } 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<String> outputSegments = new Stack<String>(); 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]); } } StringBuilder outputBuffer = new StringBuilder(); 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); } } /** * Extracts target host from the given {@link URI}. * * @param uri * @return the target host if the URI is absolute or <code>null</null> if the URI is * relative or does not contain a valid host name. * * @since 4.1 */ public static HttpHost extractHost(final URI uri) { if (uri == null) { return null; } HttpHost target = null; if (uri.isAbsolute()) { int port = uri.getPort(); // may be overridden later String host = uri.getHost(); if (host == null) { // normal parse failed; let's do it ourselves // authority does not seem to care about the valid character-set for host names host = uri.getAuthority(); if (host != null) { // Strip off any leading user credentials int at = host.indexOf('@'); if (at >= 0) { if (host.length() > at+1 ) { host = host.substring(at+1); } else { host = null; // @ on its own } } // Extract the port suffix, if present if (host != null) { int colon = host.indexOf(':'); if (colon >= 0) { if (colon+1 < host.length()) { port = Integer.parseInt(host.substring(colon+1)); } host = host.substring(0,colon); } } } } String scheme = uri.getScheme(); if (host != null) { target = new HttpHost(host, port, scheme); } } return target; } /** * This class should not be instantiated. */ private URIUtils() { } }