blob: 4a5aecf5e51d7802566c0219e37752db9cefb584 [file] [log] [blame]
/*
* Copyright 1999-2004 The Apache Software Foundation.
*
* Licensed 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.cocoon.util;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.util.BitSet;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import org.apache.excalibur.source.SourceParameters;
import org.apache.cocoon.environment.Request;
/**
* A collection of <code>File</code>, <code>URL</code> and filename
* utility methods
*
* @author <a href="mailto:stefano@apache.org">Stefano Mazzocchi</a>
* @version CVS $Id: NetUtils.java,v 1.9 2004/03/05 13:03:00 bdelacretaz Exp $
*/
public class NetUtils {
/**
* Array containing the safe characters set as defined by RFC 1738
*/
private static BitSet safeCharacters;
private static final char[] hexadecimal =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F'};
static {
safeCharacters = new BitSet(256);
int i;
// 'lowalpha' rule
for (i = 'a'; i <= 'z'; i++) {
safeCharacters.set(i);
}
// 'hialpha' rule
for (i = 'A'; i <= 'Z'; i++) {
safeCharacters.set(i);
}
// 'digit' rule
for (i = '0'; i <= '9'; i++) {
safeCharacters.set(i);
}
// 'safe' rule
safeCharacters.set('$');
safeCharacters.set('-');
safeCharacters.set('_');
safeCharacters.set('.');
safeCharacters.set('+');
// 'extra' rule
safeCharacters.set('!');
safeCharacters.set('*');
safeCharacters.set('\'');
safeCharacters.set('(');
safeCharacters.set(')');
safeCharacters.set(',');
// special characters common to http: file: and ftp: URLs ('fsegment' and 'hsegment' rules)
safeCharacters.set('/');
safeCharacters.set(':');
safeCharacters.set('@');
safeCharacters.set('&');
safeCharacters.set('=');
}
/**
* Decode a path.
*
* <p>Interprets %XX (where XX is hexadecimal number) as UTF-8 encoded bytes.
* <p>The validity of the input path is not checked (i.e. characters that were not encoded will
* not be reported as errors).
* <p>This method differs from URLDecoder.decode in that it always uses UTF-8 (while URLDecoder
* uses the platform default encoding, often ISO-8859-1), and doesn't translate + characters to spaces.
*
* @param path the path to decode
* @return the decoded path
*/
public static String decodePath(String path) {
StringBuffer translatedPath = new StringBuffer(path.length());
byte[] encodedchars = new byte[path.length() / 3];
int i = 0;
int length = path.length();
int encodedcharsLength = 0;
while (i < length) {
if (path.charAt(i) == '%') {
// we must process all consecutive %-encoded characters in one go, because they represent
// an UTF-8 encoded string, and in UTF-8 one character can be encoded as multiple bytes
while (i < length && path.charAt(i) == '%') {
if (i + 2 < length) {
try {
byte x = (byte)Integer.parseInt(path.substring(i + 1, i + 3), 16);
encodedchars[encodedcharsLength] = x;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("NetUtils.decodePath: illegal hex characters in pattern %" + path.substring(i + 1, i + 3));
}
encodedcharsLength++;
i += 3;
} else {
throw new IllegalArgumentException("NetUtils.decodePath: % character should be followed by 2 hexadecimal characters.");
}
}
try {
String translatedPart = new String(encodedchars, 0, encodedcharsLength, "UTF-8");
translatedPath.append(translatedPart);
} catch (UnsupportedEncodingException e) {
// the situation that UTF-8 is not supported is quite theoretical, so throw a runtime exception
throw new RuntimeException("Problem in decodePath: UTF-8 encoding not supported.");
}
encodedcharsLength = 0;
} else {
// a normal character
translatedPath.append(path.charAt(i));
i++;
}
}
return translatedPath.toString();
}
/**
* Encode a path as required by the URL specificatin (<a href="http://www.ietf.org/rfc/rfc1738.txt">
* RFC 1738</a>). This differs from <code>java.net.URLEncoder.encode()</code> which encodes according
* to the <code>x-www-form-urlencoded</code> MIME format.
*
* @param path the path to encode
* @return the encoded path
*/
public static String encodePath(String path) {
// stolen from org.apache.catalina.servlets.DefaultServlet ;)
/**
* Note: This code portion is very similar to URLEncoder.encode.
* Unfortunately, there is no way to specify to the URLEncoder which
* characters should be encoded. Here, ' ' should be encoded as "%20"
* and '/' shouldn't be encoded.
*/
int maxBytesPerChar = 10;
StringBuffer rewrittenPath = new StringBuffer(path.length());
ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar);
OutputStreamWriter writer = null;
try {
writer = new OutputStreamWriter(buf, "UTF8");
} catch (Exception e) {
e.printStackTrace();
writer = new OutputStreamWriter(buf);
}
for (int i = 0; i < path.length(); i++) {
int c = path.charAt(i);
if (safeCharacters.get(c)) {
rewrittenPath.append((char)c);
} else {
// convert to external encoding before hex conversion
try {
writer.write(c);
writer.flush();
} catch(IOException e) {
buf.reset();
continue;
}
byte[] ba = buf.toByteArray();
for (int j = 0; j < ba.length; j++) {
// Converting each byte in the buffer
byte toEncode = ba[j];
rewrittenPath.append('%');
int low = (toEncode & 0x0f);
int high = ((toEncode & 0xf0) >> 4);
rewrittenPath.append(hexadecimal[high]);
rewrittenPath.append(hexadecimal[low]);
}
buf.reset();
}
}
return rewrittenPath.toString();
}
/**
* Returns the path of the given resource.
*
* @param uri The URI of the resource
* @return the resource path
*/
public static String getPath(String uri) {
int i = uri.lastIndexOf('/');
if (i > -1) {
return uri.substring(0, i);
}
i = uri.indexOf(':');
return (i > -1) ? uri.substring(i + 1, uri.length()) : "";
}
/**
* Remove path and file information from a filename returning only its
* extension component
*
* @param uri The filename
* @return The filename extension (with starting dot!)
*/
public static String getExtension(String uri) {
int dot = uri.lastIndexOf('.');
if (dot > -1) {
uri = uri.substring(dot);
int slash = uri.lastIndexOf('/');
if (slash > -1) {
return null;
} else {
int sharp = uri.lastIndexOf('#');
if (sharp > -1) {
// uri starts with dot already
return uri.substring(0, sharp);
} else {
int mark = uri.lastIndexOf('?');
if (mark > -1) {
// uri starts with dot already
return uri.substring(0, mark);
} else {
return uri;
}
}
}
} else {
return null;
}
}
/**
* Absolutize a relative resource path on the given absolute base path.
*
* @param path The absolute base path
* @param resource The relative resource path
* @return The absolutized resource path
*/
public static String absolutize(String path, String resource) {
if (path == null || path.length() == 0) {
// Base path is empty
return resource;
}
if (resource == null || resource.length() == 0) {
// Resource path is empty
return path;
}
if (resource.charAt(0) == '/') {
// Resource path is already absolute
return resource;
}
int length = path.length() - 1;
boolean slash = (path.charAt(length) == '/');
StringBuffer b = new StringBuffer();
b.append(path);
if (!slash) {
b.append('/');
}
b.append(resource);
return b.toString();
}
/**
* Relativize an absolute resource on a given absolute path.
*
* @param path The absolute path
* @param absoluteResource The absolute resource
* @return the resource relative to the given path
*/
public static String relativize(String path, String absoluteResource) {
if (path == null || "".equals(path)) {
return absoluteResource;
}
if (path.charAt(path.length() - 1) != '/') {
path += "/";
}
if (absoluteResource.startsWith(path)) {
// resource is direct descentant
return absoluteResource.substring(path.length());
} else {
// resource is not direct descendant
int index = StringUtils.matchStrings(path, absoluteResource);
if (index > 0 && path.charAt(index-1) != '/') {
index = path.substring(0, index).lastIndexOf('/');
index++;
}
String pathDiff = path.substring(index);
String resource = absoluteResource.substring(index);
int levels = StringUtils.count(pathDiff, '/');
StringBuffer b = new StringBuffer();
for (int i = 0; i < levels; i++) {
b.append("../");
}
b.append(resource);
return b.toString();
}
}
/**
* Normalize a uri containing ../ and ./ paths.
*
* @param uri The uri path to normalize
* @return The normalized uri
*/
public static String normalize(String uri) {
String[] dirty = StringUtils.split(uri, "/");
int length = dirty.length;
String[] clean = new String[length];
boolean path;
boolean finished;
while (true) {
path = false;
finished = true;
for (int i = 0, j = 0; (i < length) && (dirty[i] != null); i++) {
if (".".equals(dirty[i])) {
// ignore
} else if ("..".equals(dirty[i])) {
clean[j++] = dirty[i];
if (path) finished = false;
} else {
if ((i+1 < length) && ("..".equals(dirty[i+1]))) {
i++;
} else {
clean[j++] = dirty[i];
path = true;
}
}
}
if (finished) {
break;
} else {
dirty = clean;
clean = new String[length];
}
}
StringBuffer b = new StringBuffer(uri.length());
for (int i = 0; (i < length) && (clean[i] != null); i++) {
b.append(clean[i]);
if ((i+1 < length) && (clean[i+1] != null)) {
b.append("/");
}
}
return b.toString();
}
/**
* Remove parameters from a uri.
* Resulting Map will have either String for single value attributes,
* or String arrays for multivalue attributes.
*
* @param uri The uri path to deparameterize.
* @param parameters The map that collects parameters.
* @return The cleaned uri
*/
public static String deparameterize(String uri, Map parameters) {
int i = uri.lastIndexOf('?');
if (i == -1) {
return uri;
}
String[] params = StringUtils.split(uri.substring(i + 1), "&");
for (int j = 0; j < params.length; j++) {
String p = params[j];
int k = p.indexOf('=');
if (k == -1) {
break;
}
String name = p.substring(0, k);
String value = p.substring(k + 1);
Object values = parameters.get(name);
if (values == null) {
parameters.put(name, value);
} else if (values.getClass().isArray()) {
String[] v1 = (String[])values;
String[] v2 = new String[v1.length + 1];
System.arraycopy(v1, 0, v2, 0, v1.length);
v2[v1.length] = value;
parameters.put(name, v2);
} else {
parameters.put(name, new String[]{values.toString(), value});
}
}
return uri.substring(0, i);
}
/**
* Add parameters stored in the Map to the uri string.
* Map can contain Object values which will be converted to the string,
* or Object arrays, which will be treated as multivalue attributes.
*
* @param uri The uri to add parameters into
* @param parameters The map containing parameters to be added
* @return The uri with added parameters
*/
public static String parameterize(String uri, Map parameters) {
if (parameters.size() == 0) {
return uri;
}
StringBuffer buffer = new StringBuffer(uri);
if (uri.indexOf('?') == -1) {
buffer.append('?');
} else {
buffer.append('&');
}
for (Iterator i = parameters.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Map.Entry)i.next();
if (entry.getValue().getClass().isArray()) {
Object[] value = (Object[])entry.getValue();
for (int j = 0; j < value.length; j++) {
if (j > 0) {
buffer.append('&');
}
buffer.append(entry.getKey());
buffer.append('=');
buffer.append(value[j]);
}
} else {
buffer.append(entry.getKey());
buffer.append('=');
buffer.append(entry.getValue());
}
if (i.hasNext()) {
buffer.append('&');
}
}
return buffer.toString();
}
/**
* Create new <code>SourceParameters</code> with the same
* parameters as the current request
*/
public static SourceParameters createParameters(Request request) {
final SourceParameters pars = new SourceParameters();
if (null != request) {
final Enumeration names = request.getParameterNames();
while (names.hasMoreElements()) {
final String current = (String)names.nextElement();
final String[] values = request.getParameterValues(current);
if (null != values) {
for(int i=0; i < values.length; i++) {
pars.setParameter(current, values[i]);
}
}
}
}
return pars;
}
/**
* Remove any authorisation details from a URI
*/
public static String removeAuthorisation(String uri) {
if (uri.indexOf("@")!=-1 && (uri.startsWith("ftp://") || uri.startsWith("http://"))) {
return uri.substring(0, uri.indexOf(":")+2)+uri.substring(uri.indexOf("@")+1);
}
return uri;
}
}