/* * 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. */ package org.apache.tomcat.util.buf; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.util.regex.Pattern; /** * Utility class for working with URIs and URLs. */ public final class UriUtil { private static Pattern PATTERN_EXCLAMATION_MARK = Pattern.compile("!/"); private static Pattern PATTERN_CARET = Pattern.compile("\\^/"); private static Pattern PATTERN_ASTERISK = Pattern.compile("\\*/"); private UriUtil() { // Utility class. Hide default constructor } /** * Determine if the character is allowed in the scheme of a URI. * See RFC 2396, Section 3.1 * * @param c The character to test * * @return {@code true} if a the character is allowed, otherwise {code * @false} */ private static boolean isSchemeChar(char c) { return Character.isLetterOrDigit(c) || c == '+' || c == '-' || c == '.'; } /** * Determine if a URI string has a <code>scheme</code> component. * * @param uri The URI to test * * @return {@code true} if a scheme is present, otherwise {code @false} */ public static boolean hasScheme(CharSequence uri) { int len = uri.length(); for(int i=0; i < len ; i++) { char c = uri.charAt(i); if(c == ':') { return i > 0; } else if(!UriUtil.isSchemeChar(c)) { return false; } } return false; } public static URL buildJarUrl(File jarFile) throws MalformedURLException { return buildJarUrl(jarFile, null); } public static URL buildJarUrl(File jarFile, String entryPath) throws MalformedURLException { return buildJarUrl(jarFile.toURI().toString(), entryPath); } public static URL buildJarUrl(String fileUrlString) throws MalformedURLException { return buildJarUrl(fileUrlString, null); } public static URL buildJarUrl(String fileUrlString, String entryPath) throws MalformedURLException { String safeString = makeSafeForJarUrl(fileUrlString); StringBuilder sb = new StringBuilder(); sb.append("jar:"); sb.append(safeString); sb.append("!/"); if (entryPath != null) { sb.append(makeSafeForJarUrl(entryPath)); } return new URL(sb.toString()); } public static URL buildJarSafeUrl(File file) throws MalformedURLException { String safe = makeSafeForJarUrl(file.toURI().toString()); return new URL(safe); } /* * When testing on markt's desktop each iteration was taking ~1420ns when * using String.replaceAll(). * * Switching the implementation to use pre-compiled patterns and * Pattern.matcher(input).replaceAll(replacement) reduced this by ~10%. * * Note: Given the very small absolute time of a single iteration, even for * a web application with 1000 JARs this is only going to add ~3ms. * It is therefore unlikely that further optimisation will be * necessary. */ /* * Pulled out into a separate method in case we need to handle other unusual * sequences in the future. */ private static String makeSafeForJarUrl(String input) { // Since "!/" has a special meaning in a JAR URL, make sure that the // sequence is properly escaped if present. String tmp = PATTERN_EXCLAMATION_MARK.matcher(input).replaceAll("%21/"); // Tomcat's custom jar:war: URL handling treats */ and ^/ as special tmp = PATTERN_CARET.matcher(tmp).replaceAll("%5e/"); return PATTERN_ASTERISK.matcher(tmp).replaceAll("%2a/"); } }