Coverage Report - org.apache.commons.messagelet.impl.RequestUtil
 
Classes in this File Line Coverage Branch Coverage Complexity
RequestUtil
0%
0/183
0%
0/106
6.077
 
 1  
 /*
 2  
  * Copyright 1999,2004 The Apache Software Foundation.
 3  
  * 
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  * 
 8  
  *      http://www.apache.org/licenses/LICENSE-2.0
 9  
  * 
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  
  * See the License for the specific language governing permissions and
 14  
  * limitations under the License.
 15  
  */
 16  
 
 17  
 
 18  
 package org.apache.commons.messagelet.impl;
 19  
 
 20  
 import java.io.UnsupportedEncodingException;
 21  
 import java.text.SimpleDateFormat;
 22  
 import java.util.ArrayList;
 23  
 import java.util.Map;
 24  
 import java.util.TimeZone;
 25  
 
 26  
 import javax.servlet.http.Cookie;
 27  
 
 28  
 
 29  
 /**
 30  
  * General purpose request parsing and encoding utility methods.
 31  
  *
 32  
  * @author Craig R. McClanahan
 33  
  * @author Tim Tye
 34  
  * @version $Revision: 155459 $ $Date: 2005-02-26 13:24:44 +0000 (Sat, 26 Feb 2005) $
 35  
  */
 36  
 
 37  0
 public final class RequestUtil {
 38  
 
 39  
 
 40  
     /**
 41  
      * The DateFormat to use for generating readable dates in cookies.
 42  
      */
 43  0
     private static SimpleDateFormat format =
 44  
         new SimpleDateFormat(" EEEE, dd-MMM-yy kk:mm:ss zz");
 45  
 
 46  
     static {
 47  0
         format.setTimeZone(TimeZone.getTimeZone("GMT"));
 48  0
     }
 49  
 
 50  
 
 51  
     /**
 52  
      * Encode a cookie as per RFC 2109.  The resulting string can be used
 53  
      * as the value for a <code>Set-Cookie</code> header.
 54  
      *
 55  
      * @param cookie The cookie to encode.
 56  
      * @return A string following RFC 2109.
 57  
      */
 58  
     public static String encodeCookie(Cookie cookie) {
 59  
 
 60  0
         StringBuffer buf = new StringBuffer( cookie.getName() );
 61  0
         buf.append("=");
 62  0
         buf.append(cookie.getValue());
 63  
 
 64  0
         if (cookie.getComment() != null) {
 65  0
             buf.append("; Comment=\"");
 66  0
             buf.append(cookie.getComment());
 67  0
             buf.append("\"");
 68  
         }
 69  
 
 70  0
         if (cookie.getDomain() != null) {
 71  0
             buf.append("; Domain=\"");
 72  0
             buf.append(cookie.getDomain());
 73  0
             buf.append("\"");
 74  
         }
 75  
 
 76  0
         long age = cookie.getMaxAge();
 77  0
         if (cookie.getMaxAge() >= 0) {
 78  0
             buf.append("; Max-Age=\"");
 79  0
             buf.append(cookie.getMaxAge());
 80  0
             buf.append("\"");
 81  
         }
 82  
 
 83  0
         if (cookie.getPath() != null) {
 84  0
             buf.append("; Path=\"");
 85  0
             buf.append(cookie.getPath());
 86  0
             buf.append("\"");
 87  
         }
 88  
 
 89  0
         if (cookie.getSecure()) {
 90  0
             buf.append("; Secure");
 91  
         }
 92  
 
 93  0
         if (cookie.getVersion() > 0) {
 94  0
             buf.append("; Version=\"");
 95  0
             buf.append(cookie.getVersion());
 96  0
             buf.append("\"");
 97  
         }
 98  
 
 99  0
         return (buf.toString());
 100  
     }
 101  
 
 102  
 
 103  
     /**
 104  
      * Filter the specified message string for characters that are sensitive
 105  
      * in HTML.  This avoids potential attacks caused by including JavaScript
 106  
      * codes in the request URL that is often reported in error messages.
 107  
      *
 108  
      * @param message The message string to be filtered
 109  
      */
 110  
     public static String filter(String message) {
 111  
 
 112  0
         if (message == null)
 113  0
             return (null);
 114  
 
 115  0
         char content[] = new char[message.length()];
 116  0
         message.getChars(0, message.length(), content, 0);
 117  0
         StringBuffer result = new StringBuffer(content.length + 50);
 118  0
         for (int i = 0; i < content.length; i++) {
 119  0
             switch (content[i]) {
 120  
             case '<':
 121  0
                 result.append("&lt;");
 122  0
                 break;
 123  
             case '>':
 124  0
                 result.append("&gt;");
 125  0
                 break;
 126  
             case '&':
 127  0
                 result.append("&amp;");
 128  0
                 break;
 129  
             case '"':
 130  0
                 result.append("&quot;");
 131  0
                 break;
 132  
             default:
 133  0
                 result.append(content[i]);
 134  
             }
 135  
         }
 136  0
         return (result.toString());
 137  
 
 138  
     }
 139  
 
 140  
 
 141  
     /**
 142  
      * Normalize a relative URI path that may have relative values ("/./",
 143  
      * "/../", and so on ) it it.  <strong>WARNING</strong> - This method is
 144  
      * useful only for normalizing application-generated paths.  It does not
 145  
      * try to perform security checks for malicious input.
 146  
      *
 147  
      * @param path Relative path to be normalized
 148  
      */
 149  
     public static String normalize(String path) {
 150  
 
 151  0
         if (path == null)
 152  0
             return null;
 153  
 
 154  
         // Create a place for the normalized path
 155  0
         String normalized = path;
 156  
 
 157  0
         if (normalized.equals("/."))
 158  0
             return "/";
 159  
 
 160  
         // Add a leading "/" if necessary
 161  0
         if (!normalized.startsWith("/"))
 162  0
             normalized = "/" + normalized;
 163  
 
 164  
         // Resolve occurrences of "//" in the normalized path
 165  
         while (true) {
 166  0
             int index = normalized.indexOf("//");
 167  0
             if (index < 0)
 168  0
                 break;
 169  0
             normalized = normalized.substring(0, index) +
 170  
                 normalized.substring(index + 1);
 171  0
         }
 172  
 
 173  
         // Resolve occurrences of "/./" in the normalized path
 174  
         while (true) {
 175  0
             int index = normalized.indexOf("/./");
 176  0
             if (index < 0)
 177  0
                 break;
 178  0
             normalized = normalized.substring(0, index) +
 179  
                 normalized.substring(index + 2);
 180  0
         }
 181  
 
 182  
         // Resolve occurrences of "/../" in the normalized path
 183  
         while (true) {
 184  0
             int index = normalized.indexOf("/../");
 185  0
             if (index < 0)
 186  0
                 break;
 187  0
             if (index == 0)
 188  0
                 return (null);  // Trying to go outside our context
 189  0
             int index2 = normalized.lastIndexOf('/', index - 1);
 190  0
             normalized = normalized.substring(0, index2) +
 191  
                 normalized.substring(index + 3);
 192  0
         }
 193  
 
 194  
         // Return the normalized path that we have completed
 195  0
         return (normalized);
 196  
 
 197  
     }
 198  
 
 199  
 
 200  
     /**
 201  
      * Parse the character encoding from the specified content type header.
 202  
      * If the content type is null, or there is no explicit character encoding,
 203  
      * <code>null</code> is returned.
 204  
      *
 205  
      * @param contentType a content type header
 206  
      */
 207  
     public static String parseCharacterEncoding(String contentType) {
 208  
 
 209  0
         if (contentType == null)
 210  0
             return (null);
 211  0
         int start = contentType.indexOf("charset=");
 212  0
         if (start < 0)
 213  0
             return (null);
 214  0
         String encoding = contentType.substring(start + 8);
 215  0
         int end = encoding.indexOf(';');
 216  0
         if (end >= 0)
 217  0
             encoding = encoding.substring(0, end);
 218  0
         encoding = encoding.trim();
 219  0
         if ((encoding.length() > 2) && (encoding.startsWith("\""))
 220  
             && (encoding.endsWith("\"")))
 221  0
             encoding = encoding.substring(1, encoding.length() - 1);
 222  0
         return (encoding.trim());
 223  
 
 224  
     }
 225  
 
 226  
 
 227  
     /**
 228  
      * Parse a cookie header into an array of cookies according to RFC 2109.
 229  
      *
 230  
      * @param header Value of an HTTP "Cookie" header
 231  
      */
 232  
     public static Cookie[] parseCookieHeader(String header) {
 233  
 
 234  0
         if ((header == null) || (header.length() < 1))
 235  0
             return (new Cookie[0]);
 236  
 
 237  0
         ArrayList cookies = new ArrayList();
 238  0
         while (header.length() > 0) {
 239  0
             int semicolon = header.indexOf(';');
 240  0
             if (semicolon < 0)
 241  0
                 semicolon = header.length();
 242  0
             if (semicolon == 0)
 243  0
                 break;
 244  0
             String token = header.substring(0, semicolon);
 245  0
             if (semicolon < header.length())
 246  0
                 header = header.substring(semicolon + 1);
 247  
             else
 248  0
                 header = "";
 249  
             try {
 250  0
                 int equals = token.indexOf('=');
 251  0
                 if (equals > 0) {
 252  0
                     String name = URLDecode(token.substring(0, equals).trim());
 253  0
                     String value = URLDecode(token.substring(equals+1).trim());
 254  0
                     cookies.add(new Cookie(name, value));
 255  
                 }
 256  0
             } catch (Throwable e) {
 257  
                 ;
 258  0
             }
 259  0
         }
 260  
 
 261  0
         return ((Cookie[]) cookies.toArray(new Cookie[cookies.size()]));
 262  
 
 263  
     }
 264  
 
 265  
 
 266  
     /**
 267  
      * Append request parameters from the specified String to the specified
 268  
      * Map.  It is presumed that the specified Map is not accessed from any
 269  
      * other thread, so no synchronization is performed.
 270  
      * <p>
 271  
      * <strong>IMPLEMENTATION NOTE</strong>:  URL decoding is performed
 272  
      * individually on the parsed name and value elements, rather than on
 273  
      * the entire query string ahead of time, to properly deal with the case
 274  
      * where the name or value includes an encoded "=" or "&" character
 275  
      * that would otherwise be interpreted as a delimiter.
 276  
      *
 277  
      * @param map Map that accumulates the resulting parameters
 278  
      * @param data Input string containing request parameters
 279  
      * @param urlParameters true if we're parsing parameters on the URL
 280  
      *
 281  
      * @exception IllegalArgumentException if the data is malformed
 282  
      */
 283  
     public static void parseParameters(Map map, String data, String encoding)
 284  
         throws UnsupportedEncodingException {
 285  
 
 286  0
         if ((data != null) && (data.length() > 0)) {
 287  0
             int len = data.length();
 288  0
             byte[] bytes = new byte[len];
 289  0
             data.getBytes(0, len, bytes, 0);
 290  0
             parseParameters(map, bytes, encoding);
 291  
         }
 292  
 
 293  0
     }
 294  
 
 295  
 
 296  
     /**
 297  
      * Decode and return the specified URL-encoded String.
 298  
      * When the byte array is converted to a string, the system default
 299  
      * character encoding is used...  This may be different than some other
 300  
      * servers.
 301  
      *
 302  
      * @param str The url-encoded string
 303  
      *
 304  
      * @exception IllegalArgumentException if a '%' character is not followed
 305  
      * by a valid 2-digit hexadecimal number
 306  
      */
 307  
     public static String URLDecode(String str) {
 308  
 
 309  0
         return URLDecode(str, null);
 310  
 
 311  
     }
 312  
 
 313  
 
 314  
     /**
 315  
      * Decode and return the specified URL-encoded String.
 316  
      *
 317  
      * @param str The url-encoded string
 318  
      * @param enc The encoding to use; if null, the default encoding is used
 319  
      * @exception IllegalArgumentException if a '%' character is not followed
 320  
      * by a valid 2-digit hexadecimal number
 321  
      */
 322  
     public static String URLDecode(String str, String enc) {
 323  
 
 324  0
         if (str == null)
 325  0
             return (null);
 326  
 
 327  0
         int len = str.length();
 328  0
         byte[] bytes = new byte[len];
 329  0
         str.getBytes(0, len, bytes, 0);
 330  
 
 331  0
         return URLDecode(bytes, enc);
 332  
 
 333  
     }
 334  
 
 335  
 
 336  
     /**
 337  
      * Decode and return the specified URL-encoded byte array.
 338  
      *
 339  
      * @param bytes The url-encoded byte array
 340  
      * @exception IllegalArgumentException if a '%' character is not followed
 341  
      * by a valid 2-digit hexadecimal number
 342  
      */
 343  
     public static String URLDecode(byte[] bytes) {
 344  0
         return URLDecode(bytes, null);
 345  
     }
 346  
 
 347  
 
 348  
     /**
 349  
      * Decode and return the specified URL-encoded byte array.
 350  
      *
 351  
      * @param bytes The url-encoded byte array
 352  
      * @param enc The encoding to use; if null, the default encoding is used
 353  
      * @exception IllegalArgumentException if a '%' character is not followed
 354  
      * by a valid 2-digit hexadecimal number
 355  
      */
 356  
     public static String URLDecode(byte[] bytes, String enc) {
 357  
 
 358  0
         if (bytes == null)
 359  0
             return (null);
 360  
 
 361  0
         int len = bytes.length;
 362  0
         int ix = 0;
 363  0
         int ox = 0;
 364  0
         while (ix < len) {
 365  0
             byte b = bytes[ix++];     // Get byte to test
 366  0
             if (b == '+') {
 367  0
                 b = (byte)' ';
 368  0
             } else if (b == '%') {
 369  0
                 b = (byte) ((convertHexDigit(bytes[ix++]) << 4)
 370  
                             + convertHexDigit(bytes[ix++]));
 371  
             }
 372  0
             bytes[ox++] = b;
 373  0
         }
 374  0
         if (enc != null) {
 375  
             try {
 376  0
                 return new String(bytes, 0, ox, enc);
 377  0
             } catch (Exception e) {
 378  0
                 e.printStackTrace();
 379  
             }
 380  
         }
 381  0
         return new String(bytes, 0, ox);
 382  
 
 383  
     }
 384  
 
 385  
 
 386  
     /**
 387  
      * Convert a byte character value to hexidecimal digit value.
 388  
      *
 389  
      * @param b the character value byte
 390  
      */
 391  
     private static byte convertHexDigit( byte b ) {
 392  0
         if ((b >= '0') && (b <= '9')) return (byte)(b - '0');
 393  0
         if ((b >= 'a') && (b <= 'f')) return (byte)(b - 'a' + 10);
 394  0
         if ((b >= 'A') && (b <= 'F')) return (byte)(b - 'A' + 10);
 395  0
         return 0;
 396  
     }
 397  
 
 398  
 
 399  
     /**
 400  
      * Put name value pair in map.
 401  
      *
 402  
      * @param b the character value byte
 403  
      *
 404  
      * Put name and value pair in map.  When name already exist, add value
 405  
      * to array of values.
 406  
      */
 407  
     private static void putMapEntry( Map map, String name, String value) {
 408  0
         String[] newValues = null;
 409  0
         String[] oldValues = (String[]) map.get(name);
 410  0
         if (oldValues == null) {
 411  0
             newValues = new String[1];
 412  0
             newValues[0] = value;
 413  
         } else {
 414  0
             newValues = new String[oldValues.length + 1];
 415  0
             System.arraycopy(oldValues, 0, newValues, 0, oldValues.length);
 416  0
             newValues[oldValues.length] = value;
 417  
         }
 418  0
         map.put(name, newValues);
 419  0
     }
 420  
 
 421  
 
 422  
     /**
 423  
      * Append request parameters from the specified String to the specified
 424  
      * Map.  It is presumed that the specified Map is not accessed from any
 425  
      * other thread, so no synchronization is performed.
 426  
      * <p>
 427  
      * <strong>IMPLEMENTATION NOTE</strong>:  URL decoding is performed
 428  
      * individually on the parsed name and value elements, rather than on
 429  
      * the entire query string ahead of time, to properly deal with the case
 430  
      * where the name or value includes an encoded "=" or "&" character
 431  
      * that would otherwise be interpreted as a delimiter.
 432  
      *
 433  
      * NOTE: byte array data is modified by this method.  Caller beware.
 434  
      *
 435  
      * @param map Map that accumulates the resulting parameters
 436  
      * @param data Input string containing request parameters
 437  
      * @param encoding Encoding to use for converting hex
 438  
      *
 439  
      * @exception UnsupportedEncodingException if the data is malformed
 440  
      */
 441  
     public static void parseParameters(Map map, byte[] data, String encoding)
 442  
         throws UnsupportedEncodingException {
 443  
 
 444  0
         if (data != null && data.length > 0) {
 445  0
             int    pos = 0;
 446  0
             int    ix = 0;
 447  0
             int    ox = 0;
 448  0
             String key = null;
 449  0
             String value = null;
 450  0
             while (ix < data.length) {
 451  0
                 byte c = data[ix++];
 452  0
                 switch ((char) c) {
 453  
                 case '&':
 454  0
                     value = new String(data, 0, ox, encoding);
 455  0
                     if (key != null) {
 456  0
                         putMapEntry(map, key, value);
 457  0
                         key = null;
 458  
                     }
 459  0
                     ox = 0;
 460  0
                     break;
 461  
                 case '=':
 462  0
                     key = new String(data, 0, ox, encoding);
 463  0
                     ox = 0;
 464  0
                     break;
 465  
                 case '+':
 466  0
                     data[ox++] = (byte)' ';
 467  0
                     break;
 468  
                 case '%':
 469  0
                     data[ox++] = (byte)((convertHexDigit(data[ix++]) << 4)
 470  
                                     + convertHexDigit(data[ix++]));
 471  0
                     break;
 472  
                 default:
 473  0
                     data[ox++] = c;
 474  
                 }
 475  0
             }
 476  
             //The last value does not end in '&'.  So save it now.
 477  0
             if (key != null) {
 478  0
                 value = new String(data, 0, ox, encoding);
 479  0
                 putMapEntry(map, key, value);
 480  
             }
 481  
         }
 482  
 
 483  0
     }
 484  
 
 485  
 
 486  
 
 487  
 }
 488