2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
17 package gr.ebs.gss.server.webdav;
19 import java.io.UnsupportedEncodingException;
20 import java.text.SimpleDateFormat;
22 import java.util.TimeZone;
26 * General purpose request parsing and encoding utility methods.
28 * @author Craig R. McClanahan
30 * @version $Revision$ $Date$
33 public final class RequestUtil {
37 * The DateFormat to use for generating readable dates in cookies.
39 private static SimpleDateFormat format =
40 new SimpleDateFormat(" EEEE, dd-MMM-yy kk:mm:ss zz");
43 format.setTimeZone(TimeZone.getTimeZone("GMT"));
48 * Filter the specified message string for characters that are sensitive
49 * in HTML. This avoids potential attacks caused by including JavaScript
50 * codes in the request URL that is often reported in error messages.
52 * @param message The message string to be filtered
53 * @return the filtered string
55 public static String filter(String message) {
60 char content[] = new char[message.length()];
61 message.getChars(0, message.length(), content, 0);
62 StringBuffer result = new StringBuffer(content.length + 50);
63 for (int i = 0; i < content.length; i++)
66 result.append("<");
69 result.append(">");
72 result.append("&");
75 result.append(""");
78 result.append(content[i]);
80 return result.toString();
86 * Normalize a relative URI path that may have relative values ("/./",
87 * "/../", and so on ) it it. <strong>WARNING</strong> - This method is
88 * useful only for normalizing application-generated paths. It does not
89 * try to perform security checks for malicious input.
91 * @param path Relative path to be normalized
92 * @return the normalized string
94 public static String normalize(String path) {
95 return normalize(path, true);
99 * Normalize a relative URI path that may have relative values ("/./",
100 * "/../", and so on ) it it. <strong>WARNING</strong> - This method is
101 * useful only for normalizing application-generated paths. It does not
102 * try to perform security checks for malicious input.
104 * @param path Relative path to be normalized
105 * @param replaceBackSlash Should '\\' be replaced with '/'
107 public static String normalize(String path, boolean replaceBackSlash) {
112 // Create a place for the normalized path
113 String normalized = path;
115 if (replaceBackSlash && normalized.indexOf('\\') >= 0)
116 normalized = normalized.replace('\\', '/');
118 if (normalized.equals("/."))
121 // Add a leading "/" if necessary
122 if (!normalized.startsWith("/"))
123 normalized = "/" + normalized;
125 // Resolve occurrences of "//" in the normalized path
127 int index = normalized.indexOf("//");
130 normalized = normalized.substring(0, index) +
131 normalized.substring(index + 1);
134 // Resolve occurrences of "/./" in the normalized path
136 int index = normalized.indexOf("/./");
139 normalized = normalized.substring(0, index) +
140 normalized.substring(index + 2);
143 // Resolve occurrences of "/../" in the normalized path
145 int index = normalized.indexOf("/../");
149 return null; // Trying to go outside our context
150 int index2 = normalized.lastIndexOf('/', index - 1);
151 normalized = normalized.substring(0, index2) +
152 normalized.substring(index + 3);
155 // Return the normalized path that we have completed
162 * Append request parameters from the specified String to the specified
163 * Map. It is presumed that the specified Map is not accessed from any
164 * other thread, so no synchronization is performed.
166 * <strong>IMPLEMENTATION NOTE</strong>: URL decoding is performed
167 * individually on the parsed name and value elements, rather than on
168 * the entire query string ahead of time, to properly deal with the case
169 * where the name or value includes an encoded "=" or "&" character
170 * that would otherwise be interpreted as a delimiter.
172 * @param map Map that accumulates the resulting parameters
173 * @param data Input string containing request parameters
175 * @throws UnsupportedEncodingException
177 * @exception IllegalArgumentException if the data is malformed
179 public static void parseParameters(Map map, String data, String encoding)
180 throws UnsupportedEncodingException {
182 if (data != null && data.length() > 0) {
184 // use the specified encoding to extract bytes out of the
185 // given string so that the encoding is not lost. If an
186 // encoding is not specified, let it use platform default
189 if (encoding == null)
190 bytes = data.getBytes();
192 bytes = data.getBytes(encoding);
193 } catch (UnsupportedEncodingException uee) {
196 parseParameters(map, bytes, encoding);
203 * Decode and return the specified URL-encoded String.
204 * When the byte array is converted to a string, the system default
205 * character encoding is used... This may be different than some other
206 * servers. It is assumed the string is not a query string.
208 * @param str The url-encoded string
209 * @return the decoded string
211 * @exception IllegalArgumentException if a '%' character is not followed
212 * by a valid 2-digit hexadecimal number
214 public static String URLDecode(String str) {
215 return URLDecode(str, null);
220 * Decode and return the specified URL-encoded String. It is assumed the
221 * string is not a query string.
223 * @param str The url-encoded string
224 * @param enc The encoding to use; if null, the default encoding is used
225 * @return the decoded string
226 * @exception IllegalArgumentException if a '%' character is not followed
227 * by a valid 2-digit hexadecimal number
229 public static String URLDecode(String str, String enc) {
230 return URLDecode(str, enc, false);
234 * Decode and return the specified URL-encoded String.
236 * @param str The url-encoded string
237 * @param enc The encoding to use; if null, the default encoding is used
238 * @param isQuery Is this a query string being processed
239 * @return the decoded string
240 * @exception IllegalArgumentException if a '%' character is not followed
241 * by a valid 2-digit hexadecimal number
243 public static String URLDecode(String str, String enc, boolean isQuery) {
247 // use the specified encoding to extract bytes out of the
248 // given string so that the encoding is not lost. If an
249 // encoding is not specified, let it use platform default
253 bytes = str.getBytes();
255 bytes = str.getBytes(enc);
256 } catch (UnsupportedEncodingException uee) {}
258 return URLDecode(bytes, enc, isQuery);
264 * Decode and return the specified URL-encoded byte array. It is assumed
265 * the string is not a query string.
267 * @param bytes The url-encoded byte array
268 * @return the decoded string
269 * @exception IllegalArgumentException if a '%' character is not followed
270 * by a valid 2-digit hexadecimal number
272 public static String URLDecode(byte[] bytes) {
273 return URLDecode(bytes, null);
278 * Decode and return the specified URL-encoded byte array. It is assumed
279 * the string is not a query string.
281 * @param bytes The url-encoded byte array
282 * @param enc The encoding to use; if null, the default encoding is used
283 * @return the decoded string
284 * @exception IllegalArgumentException if a '%' character is not followed
285 * by a valid 2-digit hexadecimal number
287 public static String URLDecode(byte[] bytes, String enc) {
288 return URLDecode(bytes, null, false);
292 * Decode and return the specified URL-encoded byte array.
294 * @param bytes The url-encoded byte array
295 * @param enc The encoding to use; if null, the default encoding is used
296 * @param isQuery Is this a query string being processed
297 * @return the decoded string
298 * @exception IllegalArgumentException if a '%' character is not followed
299 * by a valid 2-digit hexadecimal number
301 public static String URLDecode(byte[] bytes, String enc, boolean isQuery) {
306 int len = bytes.length;
310 byte b = bytes[ix++]; // Get byte to test
311 if (b == '+' && isQuery)
314 b = (byte) ((convertHexDigit(bytes[ix++]) << 4)
315 + convertHexDigit(bytes[ix++]));
320 return new String(bytes, 0, ox, enc);
321 } catch (Exception e) {
324 return new String(bytes, 0, ox);
330 * Convert a byte character value to hexidecimal digit value.
332 * @param b the character value byte
333 * @return the converted value
335 private static byte convertHexDigit( byte b ) {
336 if (b >= '0' && b <= '9') return (byte)(b - '0');
337 if (b >= 'a' && b <= 'f') return (byte)(b - 'a' + 10);
338 if (b >= 'A' && b <= 'F') return (byte)(b - 'A' + 10);
344 * Put name and value pair in map. When name already exist, add value
345 * to array of values.
347 * @param map The map to populate
348 * @param name The parameter name
349 * @param value The parameter value
351 private static void putMapEntry( Map map, String name, String value) {
352 String[] newValues = null;
353 String[] oldValues = (String[]) map.get(name);
354 if (oldValues == null) {
355 newValues = new String[1];
356 newValues[0] = value;
358 newValues = new String[oldValues.length + 1];
359 System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
360 newValues[oldValues.length] = value;
362 map.put(name, newValues);
367 * Append request parameters from the specified String to the specified
368 * Map. It is presumed that the specified Map is not accessed from any
369 * other thread, so no synchronization is performed.
371 * <strong>IMPLEMENTATION NOTE</strong>: URL decoding is performed
372 * individually on the parsed name and value elements, rather than on
373 * the entire query string ahead of time, to properly deal with the case
374 * where the name or value includes an encoded "=" or "&" character
375 * that would otherwise be interpreted as a delimiter.
377 * NOTE: byte array data is modified by this method. Caller beware.
379 * @param map Map that accumulates the resulting parameters
380 * @param data Input string containing request parameters
381 * @param encoding Encoding to use for converting hex
383 * @exception UnsupportedEncodingException if the data is malformed
385 public static void parseParameters(Map map, byte[] data, String encoding)
386 throws UnsupportedEncodingException {
388 if (data != null && data.length > 0) {
393 while (ix < data.length) {
397 value = new String(data, 0, ox, encoding);
399 putMapEntry(map, key, value);
406 key = new String(data, 0, ox, encoding);
412 data[ox++] = (byte)' ';
415 data[ox++] = (byte)((convertHexDigit(data[ix++]) << 4)
416 + convertHexDigit(data[ix++]));
422 //The last value does not end in '&'. So save it now.
424 value = new String(data, 0, ox, encoding);
425 putMapEntry(map, key, value);