198 lines
6.7 KiB
Java
198 lines
6.7 KiB
Java
/*
|
|
* 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 final char[] HEX =
|
|
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
|
|
|
|
private static final Pattern PATTERN_EXCLAMATION_MARK = Pattern.compile("!/");
|
|
private static final Pattern PATTERN_CARET = Pattern.compile("\\^/");
|
|
private static final Pattern PATTERN_ASTERISK = Pattern.compile("\\*/");
|
|
private static final Pattern PATTERN_CUSTOM;
|
|
private static final String REPLACE_CUSTOM;
|
|
|
|
private static final String WAR_SEPARATOR;
|
|
|
|
static {
|
|
String custom = System.getProperty("org.apache.tomcat.util.buf.UriUtil.WAR_SEPARATOR");
|
|
if (custom == null) {
|
|
WAR_SEPARATOR = "*/";
|
|
PATTERN_CUSTOM = null;
|
|
REPLACE_CUSTOM = null;
|
|
} else {
|
|
WAR_SEPARATOR = custom + "/";
|
|
PATTERN_CUSTOM = Pattern.compile(Pattern.quote(WAR_SEPARATOR));
|
|
StringBuffer sb = new StringBuffer(custom.length() * 3);
|
|
// Deliberately use the platform's default encoding
|
|
byte[] ba = custom.getBytes();
|
|
for (int j = 0; j < ba.length; j++) {
|
|
// Converting each byte in the buffer
|
|
byte toEncode = ba[j];
|
|
sb.append('%');
|
|
int low = toEncode & 0x0f;
|
|
int high = (toEncode & 0xf0) >> 4;
|
|
sb.append(HEX[high]);
|
|
sb.append(HEX[low]);
|
|
}
|
|
REPLACE_CUSTOM = sb.toString();
|
|
}
|
|
}
|
|
|
|
|
|
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(safeString);
|
|
sb.append("!/");
|
|
if (entryPath != null) {
|
|
sb.append(makeSafeForJarUrl(entryPath));
|
|
}
|
|
return new URL("jar", null, -1, 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/");
|
|
tmp = PATTERN_ASTERISK.matcher(tmp).replaceAll("%2a/");
|
|
if (PATTERN_CUSTOM != null) {
|
|
tmp = PATTERN_CUSTOM.matcher(tmp).replaceAll(REPLACE_CUSTOM);
|
|
}
|
|
return tmp;
|
|
}
|
|
|
|
|
|
/**
|
|
* Convert a URL of the form <code>war:file:...</code> to
|
|
* <code>jar:file:...</code>.
|
|
*
|
|
* @param warUrl The WAR URL to convert
|
|
*
|
|
* @return The equivalent JAR URL
|
|
*
|
|
* @throws MalformedURLException If the conversion fails
|
|
*/
|
|
public static URL warToJar(URL warUrl) throws MalformedURLException {
|
|
// Assumes that the spec is absolute and starts war:file:/...
|
|
String file = warUrl.getFile();
|
|
if (file.contains("*/")) {
|
|
file = file.replaceFirst("\\*/", "!/");
|
|
} else if (file.contains("^/")) {
|
|
file = file.replaceFirst("\\^/", "!/");
|
|
} else if (PATTERN_CUSTOM != null) {
|
|
file = file.replaceFirst(PATTERN_CUSTOM.pattern(), "!/");
|
|
}
|
|
|
|
return new URL("jar", warUrl.getHost(), warUrl.getPort(), file);
|
|
}
|
|
|
|
|
|
public static String getWarSeparator() {
|
|
return WAR_SEPARATOR;
|
|
}
|
|
}
|