KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > catalina > util > RequestUtil


1 /*
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
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
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.
16  */

17
18
19 package org.apache.catalina.util;
20
21 import java.io.UnsupportedEncodingException JavaDoc;
22 import java.text.SimpleDateFormat JavaDoc;
23 import java.util.ArrayList JavaDoc;
24 import java.util.Map JavaDoc;
25 import java.util.TimeZone JavaDoc;
26
27 import javax.servlet.http.Cookie JavaDoc;
28
29
30 /**
31  * General purpose request parsing and encoding utility methods.
32  *
33  * @author Craig R. McClanahan
34  * @author Tim Tye
35  * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
36  */

37
38 public final class RequestUtil {
39
40
41     /**
42      * The DateFormat to use for generating readable dates in cookies.
43      */

44     private static SimpleDateFormat JavaDoc format =
45         new SimpleDateFormat JavaDoc(" EEEE, dd-MMM-yy kk:mm:ss zz");
46
47     static {
48         format.setTimeZone(TimeZone.getTimeZone("GMT"));
49     }
50
51
52     /**
53      * Encode a cookie as per RFC 2109. The resulting string can be used
54      * as the value for a <code>Set-Cookie</code> header.
55      *
56      * @param cookie The cookie to encode.
57      * @return A string following RFC 2109.
58      */

59     public static String JavaDoc encodeCookie(Cookie JavaDoc cookie) {
60
61         StringBuffer JavaDoc buf = new StringBuffer JavaDoc( cookie.getName() );
62         buf.append("=");
63         buf.append(cookie.getValue());
64
65         if (cookie.getComment() != null) {
66             buf.append("; Comment=\"");
67             buf.append(cookie.getComment());
68             buf.append("\"");
69         }
70
71         if (cookie.getDomain() != null) {
72             buf.append("; Domain=\"");
73             buf.append(cookie.getDomain());
74             buf.append("\"");
75         }
76
77         long age = cookie.getMaxAge();
78         if (cookie.getMaxAge() >= 0) {
79             buf.append("; Max-Age=\"");
80             buf.append(cookie.getMaxAge());
81             buf.append("\"");
82         }
83
84         if (cookie.getPath() != null) {
85             buf.append("; Path=\"");
86             buf.append(cookie.getPath());
87             buf.append("\"");
88         }
89
90         if (cookie.getSecure()) {
91             buf.append("; Secure");
92         }
93
94         if (cookie.getVersion() > 0) {
95             buf.append("; Version=\"");
96             buf.append(cookie.getVersion());
97             buf.append("\"");
98         }
99
100         return (buf.toString());
101     }
102
103
104     /**
105      * Filter the specified message string for characters that are sensitive
106      * in HTML. This avoids potential attacks caused by including JavaScript
107      * codes in the request URL that is often reported in error messages.
108      *
109      * @param message The message string to be filtered
110      */

111     public static String JavaDoc filter(String JavaDoc message) {
112
113         if (message == null)
114             return (null);
115
116         char content[] = new char[message.length()];
117         message.getChars(0, message.length(), content, 0);
118         StringBuffer JavaDoc result = new StringBuffer JavaDoc(content.length + 50);
119         for (int i = 0; i < content.length; i++) {
120             switch (content[i]) {
121             case '<':
122                 result.append("&lt;");
123                 break;
124             case '>':
125                 result.append("&gt;");
126                 break;
127             case '&':
128                 result.append("&amp;");
129                 break;
130             case '"':
131                 result.append("&quot;");
132                 break;
133             default:
134                 result.append(content[i]);
135             }
136         }
137         return (result.toString());
138
139     }
140
141
142     /**
143      * Normalize a relative URI path that may have relative values ("/./",
144      * "/../", and so on ) it it. <strong>WARNING</strong> - This method is
145      * useful only for normalizing application-generated paths. It does not
146      * try to perform security checks for malicious input.
147      *
148      * @param path Relative path to be normalized
149      */

150     public static String JavaDoc normalize(String JavaDoc path) {
151
152         if (path == null)
153             return null;
154
155         // Create a place for the normalized path
156
String JavaDoc normalized = path;
157
158         if (normalized.equals("/."))
159             return "/";
160
161         // Add a leading "/" if necessary
162
if (!normalized.startsWith("/"))
163             normalized = "/" + normalized;
164
165         // Resolve occurrences of "//" in the normalized path
166
while (true) {
167             int index = normalized.indexOf("//");
168             if (index < 0)
169                 break;
170             normalized = normalized.substring(0, index) +
171                 normalized.substring(index + 1);
172         }
173
174         // Resolve occurrences of "/./" in the normalized path
175
while (true) {
176             int index = normalized.indexOf("/./");
177             if (index < 0)
178                 break;
179             normalized = normalized.substring(0, index) +
180                 normalized.substring(index + 2);
181         }
182
183         // Resolve occurrences of "/../" in the normalized path
184
while (true) {
185             int index = normalized.indexOf("/../");
186             if (index < 0)
187                 break;
188             if (index == 0)
189                 return (null); // Trying to go outside our context
190
int index2 = normalized.lastIndexOf('/', index - 1);
191             normalized = normalized.substring(0, index2) +
192                 normalized.substring(index + 3);
193         }
194
195         // Return the normalized path that we have completed
196
return (normalized);
197
198     }
199
200
201     /**
202      * Parse the character encoding from the specified content type header.
203      * If the content type is null, or there is no explicit character encoding,
204      * <code>null</code> is returned.
205      *
206      * @param contentType a content type header
207      */

208     public static String JavaDoc parseCharacterEncoding(String JavaDoc contentType) {
209
210         if (contentType == null)
211             return (null);
212         int start = contentType.indexOf("charset=");
213         if (start < 0)
214             return (null);
215         String JavaDoc encoding = contentType.substring(start + 8);
216         int end = encoding.indexOf(';');
217         if (end >= 0)
218             encoding = encoding.substring(0, end);
219         encoding = encoding.trim();
220         if ((encoding.length() > 2) && (encoding.startsWith("\""))
221             && (encoding.endsWith("\"")))
222             encoding = encoding.substring(1, encoding.length() - 1);
223         return (encoding.trim());
224
225     }
226
227
228     /**
229      * Parse a cookie header into an array of cookies according to RFC 2109.
230      *
231      * @param header Value of an HTTP "Cookie" header
232      */

233     public static Cookie JavaDoc[] parseCookieHeader(String JavaDoc header) {
234
235         if ((header == null) || (header.length() < 1))
236             return (new Cookie JavaDoc[0]);
237
238         ArrayList JavaDoc cookies = new ArrayList JavaDoc();
239         while (header.length() > 0) {
240             int semicolon = header.indexOf(';');
241             if (semicolon < 0)
242                 semicolon = header.length();
243             if (semicolon == 0)
244                 break;
245             String JavaDoc token = header.substring(0, semicolon);
246             if (semicolon < header.length())
247                 header = header.substring(semicolon + 1);
248             else
249                 header = "";
250             try {
251                 int equals = token.indexOf('=');
252                 if (equals > 0) {
253                     String JavaDoc name = token.substring(0, equals).trim();
254                     String JavaDoc value = token.substring(equals+1).trim();
255                     cookies.add(new Cookie JavaDoc(name, value));
256                 }
257             } catch (Throwable JavaDoc e) {
258                 ;
259             }
260         }
261
262         return ((Cookie JavaDoc[]) cookies.toArray(new Cookie JavaDoc[cookies.size()]));
263
264     }
265
266
267     /**
268      * Append request parameters from the specified String to the specified
269      * Map. It is presumed that the specified Map is not accessed from any
270      * other thread, so no synchronization is performed.
271      * <p>
272      * <strong>IMPLEMENTATION NOTE</strong>: URL decoding is performed
273      * individually on the parsed name and value elements, rather than on
274      * the entire query string ahead of time, to properly deal with the case
275      * where the name or value includes an encoded "=" or "&" character
276      * that would otherwise be interpreted as a delimiter.
277      *
278      * @param map Map that accumulates the resulting parameters
279      * @param data Input string containing request parameters
280      *
281      * @exception IllegalArgumentException if the data is malformed
282      */

283     public static void parseParameters(Map JavaDoc map, String JavaDoc data, String JavaDoc encoding)
284         throws UnsupportedEncodingException JavaDoc {
285
286         if ((data != null) && (data.length() > 0)) {
287
288             // use the specified encoding to extract bytes out of the
289
// given string so that the encoding is not lost. If an
290
// encoding is not specified, let it use platform default
291
byte[] bytes = null;
292             try {
293                 if (encoding == null) {
294                     bytes = data.getBytes();
295                 } else {
296                     bytes = data.getBytes(encoding);
297                 }
298             } catch (UnsupportedEncodingException JavaDoc uee) {
299             }
300
301             parseParameters(map, bytes, encoding);
302         }
303
304     }
305
306
307     /**
308      * Decode and return the specified URL-encoded String.
309      * When the byte array is converted to a string, the system default
310      * character encoding is used... This may be different than some other
311      * servers.
312      *
313      * @param str The url-encoded string
314      *
315      * @exception IllegalArgumentException if a '%' character is not followed
316      * by a valid 2-digit hexadecimal number
317      */

318     public static String JavaDoc URLDecode(String JavaDoc str) {
319
320         return URLDecode(str, null);
321
322     }
323
324
325     /**
326      * Decode and return the specified URL-encoded String.
327      *
328      * @param str The url-encoded string
329      * @param enc The encoding to use; if null, the default encoding is used
330      * @exception IllegalArgumentException if a '%' character is not followed
331      * by a valid 2-digit hexadecimal number
332      */

333     public static String JavaDoc URLDecode(String JavaDoc str, String JavaDoc enc) {
334
335         if (str == null)
336             return (null);
337
338         // use the specified encoding to extract bytes out of the
339
// given string so that the encoding is not lost. If an
340
// encoding is not specified, let it use platform default
341
byte[] bytes = null;
342         try {
343             if (enc == null) {
344                 bytes = str.getBytes();
345             } else {
346                 bytes = str.getBytes(enc);
347             }
348         } catch (UnsupportedEncodingException JavaDoc uee) {}
349
350         return URLDecode(bytes, enc);
351
352     }
353
354
355     /**
356      * Decode and return the specified URL-encoded byte array.
357      *
358      * @param bytes The url-encoded byte array
359      * @exception IllegalArgumentException if a '%' character is not followed
360      * by a valid 2-digit hexadecimal number
361      */

362     public static String JavaDoc URLDecode(byte[] bytes) {
363         return URLDecode(bytes, null);
364     }
365
366
367     /**
368      * Decode and return the specified URL-encoded byte array.
369      *
370      * @param bytes The url-encoded byte array
371      * @param enc The encoding to use; if null, the default encoding is used
372      * @exception IllegalArgumentException if a '%' character is not followed
373      * by a valid 2-digit hexadecimal number
374      */

375     public static String JavaDoc URLDecode(byte[] bytes, String JavaDoc enc) {
376
377         if (bytes == null)
378             return (null);
379
380         int len = bytes.length;
381         int ix = 0;
382         int ox = 0;
383         while (ix < len) {
384             byte b = bytes[ix++]; // Get byte to test
385
if (b == '+') {
386                 b = (byte)' ';
387             } else if (b == '%') {
388                 b = (byte) ((convertHexDigit(bytes[ix++]) << 4)
389                             + convertHexDigit(bytes[ix++]));
390             }
391             bytes[ox++] = b;
392         }
393         if (enc != null) {
394             try {
395                 return new String JavaDoc(bytes, 0, ox, enc);
396             } catch (Exception JavaDoc e) {
397                 e.printStackTrace();
398             }
399         }
400         return new String JavaDoc(bytes, 0, ox);
401
402     }
403
404
405     /**
406      * Convert a byte character value to hexidecimal digit value.
407      *
408      * @param b the character value byte
409      */

410     private static byte convertHexDigit( byte b ) {
411         if ((b >= '0') && (b <= '9')) return (byte)(b - '0');
412         if ((b >= 'a') && (b <= 'f')) return (byte)(b - 'a' + 10);
413         if ((b >= 'A') && (b <= 'F')) return (byte)(b - 'A' + 10);
414         return 0;
415     }
416
417
418     /**
419      * Put name and value pair in map. When name already exist, add value
420      * to array of values.
421      *
422      * @param map The map to populate
423      * @param name The parameter name
424      * @param value The parameter value
425      */

426     private static void putMapEntry( Map JavaDoc map, String JavaDoc name, String JavaDoc value) {
427         String JavaDoc[] newValues = null;
428         String JavaDoc[] oldValues = (String JavaDoc[]) map.get(name);
429         if (oldValues == null) {
430             newValues = new String JavaDoc[1];
431             newValues[0] = value;
432         } else {
433             newValues = new String JavaDoc[oldValues.length + 1];
434             System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
435             newValues[oldValues.length] = value;
436         }
437         map.put(name, newValues);
438     }
439
440
441     /**
442      * Append request parameters from the specified String to the specified
443      * Map. It is presumed that the specified Map is not accessed from any
444      * other thread, so no synchronization is performed.
445      * <p>
446      * <strong>IMPLEMENTATION NOTE</strong>: URL decoding is performed
447      * individually on the parsed name and value elements, rather than on
448      * the entire query string ahead of time, to properly deal with the case
449      * where the name or value includes an encoded "=" or "&" character
450      * that would otherwise be interpreted as a delimiter.
451      *
452      * NOTE: byte array data is modified by this method. Caller beware.
453      *
454      * @param map Map that accumulates the resulting parameters
455      * @param data Input string containing request parameters
456      * @param encoding Encoding to use for converting hex
457      *
458      * @exception UnsupportedEncodingException if the data is malformed
459      */

460     public static void parseParameters(Map JavaDoc map, byte[] data, String JavaDoc encoding)
461         throws UnsupportedEncodingException JavaDoc {
462
463         if (data != null && data.length > 0) {
464             int pos = 0;
465             int ix = 0;
466             int ox = 0;
467             String JavaDoc key = null;
468             String JavaDoc value = null;
469             while (ix < data.length) {
470                 byte c = data[ix++];
471                 switch ((char) c) {
472                 case '&':
473                     value = new String JavaDoc(data, 0, ox, encoding);
474                     if (key != null) {
475                         putMapEntry(map, key, value);
476                         key = null;
477                     }
478                     ox = 0;
479                     break;
480                 case '=':
481                     if (key == null) {
482                         key = new String JavaDoc(data, 0, ox, encoding);
483                         ox = 0;
484                     } else {
485                         data[ox++] = c;
486                     }
487                     break;
488                 case '+':
489                     data[ox++] = (byte)' ';
490                     break;
491                 case '%':
492                     data[ox++] = (byte)((convertHexDigit(data[ix++]) << 4)
493                                     + convertHexDigit(data[ix++]));
494                     break;
495                 default:
496                     data[ox++] = c;
497                 }
498             }
499             //The last value does not end in '&'. So save it now.
500
if (key != null) {
501                 value = new String JavaDoc(data, 0, ox, encoding);
502                 putMapEntry(map, key, value);
503             }
504         }
505
506     }
507
508
509
510 }
511
Popular Tags