/* * HttpUtil.java * * Brazil project web application toolkit, * export version: 2.3 * Copyright (c) 1999-2008 Sun Microsystems, Inc. * * Sun Public License Notice * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is included as the file "license.terms", * and also available at http://www.sun.com/ * * The Original Code is from: * Brazil project web application toolkit release 2.3. * The Initial Developer of the Original Code is: cstevens. * Portions created by cstevens are Copyright (C) Sun Microsystems, Inc. * All Rights Reserved. * * Contributor(s): cstevens, guym, suhler. * * Version: 2.4 * Created by cstevens on 99/09/15 * Last modified by suhler on 08/03/04 10:58:03 * * Version Histories: * * 2.4 08/03/04-10:58:03 (suhler) * StringMap.java * * 2.3 04/11/30-15:19:46 (suhler) * fixed sccs version string * * 2.2 03/08/01-16:17:39 (suhler) * fixes for javadoc * * 2.1 02/10/01-16:37:05 (suhler) * version change * * 1.14 02/07/29-14:57:34 (suhler) * added convenience routines to extract parts of url's * * 1.13 01/06/11-17:13:22 (guym) * Fixed a mapping problem with '\n' in HTMLmap. * * 1.12 01/04/06-09:35:45 (suhler) * Generate entity references for <>&" instead of #xxx; * * 1.11 00/11/20-13:22:31 (suhler) * doc fixes * * 1.10 00/11/19-10:33:29 (suhler) * added "parseTime" method to convet mime dates into time stamps * * 1.9 00/04/12-16:05:55 (cstevens) * imports * * 1.8 00/03/29-16:47:35 (cstevens) * * 1.7 99/11/16-14:49:08 (cstevens) * null * * 1.6 99/11/09-20:24:49 (cstevens) * bugs revealed by writing tests. * * 1.5 99/10/14-13:02:28 (cstevens) * Documentation * HttpUtil.urlEncode was wrong. * * 1.4 99/09/30-14:13:06 (cstevens) * better error message * * 1.3 99/09/15-15:57:27 (cstevens) * * 1.2 99/09/15-14:52:16 (cstevens) * import *; * * 1.2 99/09/15-14:39:37 (Codemgr) * SunPro Code Manager data about conflicts, renames, etc... * Name history : 1 0 util/http/HttpUtil.java * * 1.1 99/09/15-14:39:36 (cstevens) * date and time created 99/09/15 14:39:36 by cstevens * */ package sunlabs.brazil.util.http; import java.text.SimpleDateFormat; import java.text.ParsePosition; import java.util.Date; import java.util.Dictionary; import java.util.Locale; import java.util.SimpleTimeZone; import java.util.StringTokenizer; import sunlabs.brazil.util.regexp.Regexp; /** * The HttpUtil class contains methods for performing simple * HTTP operations. * * @author Colin Stevens (colin.stevens@sun.com) * @version 2.4 */ public class HttpUtil { private HttpUtil() {} /** * Which ascii characters may be sent in HTML without escaping */ private static String[] htmlMap = new String[256]; /** * Initialize htmlMap array * @see #htmlMap */ static { for (int i = 0; i < 256; i++) { if (i >= 32 && i < 126) { htmlMap[i]=null; } else { htmlMap[i] = "#" + i; } } htmlMap['"'] = "quot"; htmlMap['&'] = "amp"; htmlMap['<'] = "lt"; htmlMap['>'] = "gt"; htmlMap['\n'] = null; htmlMap['\r'] = null; htmlMap['\t'] = null; } /** * Converts a string into a valid HTML fragment. Escapes the characters * ", &, <, >, and all * non-printables into the form &#xx; (their "decimal * reference" form). * * @param src * The string to convert. * * @return The string with all the special characters converted to * decimal reference form. */ public static String htmlEncode(String src) { StringBuffer result = new StringBuffer(); int length = (src == null) ? 0 : src.length(); for (int i = 0; i < length; i++) { int ch = src.charAt(i) & 0xff; if (htmlMap[ch]==null) { result.append((char) ch); } else { result.append("&" + htmlMap[ch] + ";"); } } return result.toString(); } private static final boolean[] safeUrl = new boolean[256]; static { for (int i = 'a'; i <= 'z'; i++) { safeUrl[i] = true; } for (int i = 'A'; i <= 'Z'; i++) { safeUrl[i] = true; } for (int i = '0'; i <= '9'; i++) { safeUrl[i] = true; } safeUrl['_'] = true; safeUrl[':'] = true; safeUrl['/'] = true; safeUrl['.'] = true; safeUrl['~'] = true; } /** * Maps a string to be used in a query or post into a form that is * acceptable in an URL. Typically used when the caller wants to * safely generate an HREF containing an arbitrary string that may * have special characters. *

* URL strings may not contain non-alphanumeric characters. All * non-alphanumeric characters are converted to the escape sequence * "%XX", where XX is the hexadecimal value of that character's code. *

* Note that the space character " " is NOT converted to "+". That is * a common misconception. "+" represents a space only in query strings, * not in the URL. "%20" is how an actual space character must be * passed in an URL, and is also an acceptable way of passing a space in * a query string. * * @param src * The string to convert. * * @return The URL-encoded version of the given string. */ public static String urlEncode(String src) { StringBuffer result = new StringBuffer(); int length = (src == null) ? 0 : src.length(); for (int i = 0; i < length; i++) { int ch = src.charAt(i) & 0xff; if (safeUrl[ch]) { result.append((char) ch); } else { result.append('%'); result.append(Character.forDigit((ch >> 4) & 0x0f, 16)); result.append(Character.forDigit(ch & 0x0f, 16)); } } return result.toString(); } /** * Decodes a URL-encoded string by replacing all the "%XX" escape * sequences in the string with the corresponding character. *

* Malformed "%XX" sequences are silently ignored. * * @param src * The URL-encoded string. * * @return The decoded version of the given string. */ public static String urlDecode(String src) { if (src == null) { return ""; } int i = src.indexOf('%'); if (i < 0) { return src; } StringBuffer result = new StringBuffer(src.substring(0, i)); int length = src.length(); for ( ; i < length; i++) { char ch = src.charAt(i); if (ch == '%') { try { ch = (char) Integer.parseInt(src.substring(i + 1, i + 3), 16); i += 2; } catch (Exception e) { // Ignore malformed % sequences, just insert the '%'. } } result.append(ch); } return result.toString(); } /** * The format describing an http date. */ private static SimpleDateFormat dateFormat; static { dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); dateFormat.setTimeZone(new SimpleTimeZone(0, "GMT")); dateFormat.setLenient(true); } /** * Returns a string containing the current time as an HTTP-formatted * date. * * @return HTTP date string representing the current time. */ public static String formatTime() { return formatTime(System.currentTimeMillis()); } /** * Returns a string containing an HTTP-formatted date. * * @param time * The date to format (current time in msec). * * @return HTTP date string representing the given time. */ public static String formatTime(long time) { return dateFormat.format(new Date(time)).substring(0, 29); } /** * Convert a last-modified date in "standard" format * into a time stamp. This "inverses" formatTime. * * @param time * A correctly formatted HTTP date string. * @return milliseconds since the epoch, or 0 if the conversion * failed. */ public static long parseTime(String time) { try { return dateFormat.parse(time.trim(),new ParsePosition(0)).getTime(); } catch (Exception e) { return 0; } } /** * Turns x-www-form-urlencoded form data into a dictionary. * * @param query * The x-www-form-urlencoded string. May be null * * @param table * The dictionary to insert the form data into. */ public static void extractQuery(String query, Dictionary table) { if (query == null) { return; } query = query.replace('+', ' '); StringTokenizer st = new StringTokenizer(query, "&"); while (st.hasMoreTokens()) { String field = st.nextToken(); int index = field.indexOf('='); if (index < 0) { table.put(urlDecode(field), ""); } else { table.put(urlDecode(field.substring(0, index)), urlDecode(field.substring(index + 1))); } } } /* * Extract parts of a URL. * part: 1=protocol, 2=host, 3=port, 4=path */ static Regexp urlRe = new Regexp("([^:]*)://([^:/]*)(:[0-9]+)?(.*)"); static String parseUrl(String url, int part) { String matches[] = new String[5]; return (urlRe.match(url, matches) ? matches[part] : null); } /** * Get the protocol portion of a Url String. * @return null if the string is an invalid URL. */ public static String extractUrlProtocol(String url) { return parseUrl(url, 1); } /** * Get the host portion of a Url String. * @return null if the string is an invalid URL. */ public static String extractUrlHost(String url) { return parseUrl(url, 2); } /** * Get the port portion of a Url String as a string. * @return null if the string is an invalid URL, the empy string * if no port was specified. */ public static String extractUrlPort(String url) { String port = parseUrl(url, 3); return (port == null ? null : port.substring(1)); } /** * Get the path portion of a Url String. * @return null if the string is an invalid URL. */ public static String extractUrlPath(String url) { String s = parseUrl(url, 4); if (s != null && s.equals("")) { return "/"; } else { return s; } } /** * Returns the HTTP error string associated with the integer error code. * This error string can be used in HTTP responses. Unknown codes * return the string "Error" * * @param code * The code to look up. * * @return The associated error string. */ public static String getStatusPhrase(int code) { switch (code) { case 100: return "Continue"; case 101: return "Switching Protocols"; case 200: return "OK"; case 201: return "Created"; case 202: return "Accepted"; case 203: return "Non-Authoritative Information"; case 204: return "No Content"; case 205: return "Reset Content"; case 206: return "Partial Content"; case 300: return "Multiple Choices"; case 301: return "Moved Permanently"; case 302: return "Moved Temporarily"; case 303: return "See Other"; case 304: return "Not Modified"; case 305: return "Use Proxy"; case 400: return "Bad Request"; case 401: return "Unauthorized"; case 402: return "Payment Required"; case 403: return "Forbidden"; case 404: return "Not Found"; case 405: return "Method Not Allowed"; case 406: return "Not Acceptable"; case 407: return "Proxy Authentication Required"; case 408: return "Request Time-out"; case 409: return "Conflict"; case 410: return "Gone"; case 411: return "Length Required"; case 412: return "Precondition Failed"; case 413: return "Request Entity Too Large"; case 414: return "Request-URI Too Large"; case 415: return "Unsupported Media Type"; case 500: return "Server Error"; case 501: return "Not Implemented"; case 502: return "Bad Gateway"; case 503: return "Service Unavailable"; case 504: return "Gateway Time-out"; case 505: return "HTTP Version not supported"; default: return "Error"; } } }