001: /*
002: * HttpUtil.java
003: *
004: * Brazil project web application Framework,
005: * export version: 1.1
006: * Copyright (c) 1999-2000 Sun Microsystems, Inc.
007: *
008: * Sun Public License Notice
009: *
010: * The contents of this file are subject to the Sun Public License Version
011: * 1.0 (the "License"). You may not use this file except in compliance with
012: * the License. A copy of the License is included as the file "license.terms",
013: * and also available at http://www.sun.com/
014: *
015: * The Original Code is from:
016: * Brazil project web application Framework release 1.1.
017: * The Initial Developer of the Original Code is: cstevens.
018: * Portions created by cstevens are Copyright (C) Sun Microsystems, Inc.
019: * All Rights Reserved.
020: *
021: * Contributor(s): cstevens, suhler.
022: *
023: * Version: 1.11
024: * Created by cstevens on 99/09/15
025: * Last modified by suhler on 00/11/20 13:22:31
026: */
027:
028: package sunlabs.brazil.util.http;
029:
030: import java.text.SimpleDateFormat;
031: import java.text.ParsePosition;
032: import java.util.Date;
033: import java.util.Dictionary;
034: import java.util.Locale;
035: import java.util.SimpleTimeZone;
036: import java.util.StringTokenizer;
037:
038: /**
039: * The <code>HttpUtil</code> class contains methods for performing simple
040: * HTTP operations.
041: *
042: * @author Colin Stevens (colin.stevens@sun.com)
043: * @version 1.11 00/11/20
044: */
045: public class HttpUtil {
046: private HttpUtil() {
047: }
048:
049: /**
050: * Which ascii characters may be sent in HTML without escaping
051: */
052: private static final boolean[] safeHtml = new boolean[256];
053:
054: /**
055: * Initialize <code>safeHtml</code> array
056: * @see #safeHtml
057: */
058: static {
059: for (int i = 32; i < 126; i++) {
060: safeHtml[i] = true;
061: }
062: safeHtml['"'] = false;
063: safeHtml['&'] = false;
064: safeHtml['<'] = false;
065: safeHtml['>'] = false;
066: }
067:
068: /**
069: * Converts a string into a valid HTML fragment. Escapes the characters
070: * <i>"</i>, <i>&</i>, <i><</i>, <i>></i>, and all
071: * non-printables into the form <code>&#xx;</code> (their "decimal
072: * reference" form).
073: *
074: * @param src
075: * The string to convert.
076: *
077: * @return The string with all the special characters converted to
078: * decimal reference form.
079: */
080: public static String htmlEncode(String src) {
081: StringBuffer result = new StringBuffer();
082:
083: int length = (src == null) ? 0 : src.length();
084: for (int i = 0; i < length; i++) {
085: int ch = src.charAt(i) & 0xff;
086: if (safeHtml[ch]) {
087: result.append((char) ch);
088: } else {
089: result.append("&#" + ch + ";");
090: }
091: }
092: return result.toString();
093: }
094:
095: private static final boolean[] safeUrl = new boolean[256];
096: static {
097: for (int i = 'a'; i <= 'z'; i++) {
098: safeUrl[i] = true;
099: }
100: for (int i = 'A'; i <= 'Z'; i++) {
101: safeUrl[i] = true;
102: }
103: for (int i = '0'; i <= '9'; i++) {
104: safeUrl[i] = true;
105: }
106: safeUrl['_'] = true;
107: safeUrl[':'] = true;
108: safeUrl['/'] = true;
109: safeUrl['.'] = true;
110: safeUrl['~'] = true;
111: }
112:
113: /**
114: * Maps a string to be used in a query or post into a form that is
115: * acceptable in an URL. Typically used when the caller wants to
116: * safely generate an HREF containing an arbitrary string that may
117: * have special characters.
118: * <p>
119: * URL strings may not contain non-alphanumeric characters. All
120: * non-alphanumeric characters are converted to the escape sequence
121: * "%XX", where XX is the hexadecimal value of that character's code.
122: * <p>
123: * Note that the space character " " is NOT converted to "+". That is
124: * a common misconception. "+" represents a space only in query strings,
125: * not in the URL. "%20" is how an actual space character must be
126: * passed in an URL, and is also an acceptable way of passing a space in
127: * a query string.
128: *
129: * @param string
130: * The string to convert.
131: *
132: * @return The URL-encoded version of the given string.
133: */
134: public static String urlEncode(String src) {
135: StringBuffer result = new StringBuffer();
136: int length = (src == null) ? 0 : src.length();
137: for (int i = 0; i < length; i++) {
138: int ch = src.charAt(i) & 0xff;
139: if (safeUrl[ch]) {
140: result.append((char) ch);
141: } else {
142: result.append('%');
143: result.append(Character.forDigit((ch >> 4) & 0x0f, 16));
144: result.append(Character.forDigit(ch & 0x0f, 16));
145: }
146: }
147:
148: return result.toString();
149: }
150:
151: /**
152: * Decodes a URL-encoded string by replacing all the "%XX" escape
153: * sequences in the string with the corresponding character.
154: * <p>
155: * Malformed "%XX" sequences are silently ignored.
156: *
157: * @param string
158: * The URL-encoded string.
159: *
160: * @return The decoded version of the given string.
161: */
162: public static String urlDecode(String src) {
163: if (src == null) {
164: return "";
165: }
166: int i = src.indexOf('%');
167: if (i < 0) {
168: return src;
169: }
170:
171: StringBuffer result = new StringBuffer(src.substring(0, i));
172: int length = src.length();
173: for (; i < length; i++) {
174: char ch = src.charAt(i);
175: if (ch == '%') {
176: try {
177: ch = (char) Integer.parseInt(src.substring(i + 1,
178: i + 3), 16);
179: i += 2;
180: } catch (Exception e) {
181: // Ignore malformed % sequences, just insert the '%'.
182: }
183: }
184: result.append(ch);
185: }
186: return result.toString();
187: }
188:
189: /**
190: * The format describing an http date.
191: */
192: private static SimpleDateFormat dateFormat;
193: static {
194: dateFormat = new SimpleDateFormat(
195: "EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
196: dateFormat.setTimeZone(new SimpleTimeZone(0, "GMT"));
197: dateFormat.setLenient(true);
198: }
199:
200: /**
201: * Returns a string containing the current time as an HTTP-formatted
202: * date.
203: *
204: * @return HTTP date string representing the current time.
205: */
206: public static String formatTime() {
207: return formatTime(System.currentTimeMillis());
208: }
209:
210: /**
211: * Returns a string containing an HTTP-formatted date.
212: *
213: * @param time
214: * The date to format (current time in msec).
215: *
216: * @return HTTP date string representing the given time.
217: */
218: public static String formatTime(long time) {
219: return dateFormat.format(new Date(time)).substring(0, 29);
220: }
221:
222: /**
223: * Convert a last-modified date in "standard" format
224: * into a time stamp. This "inverses" formatTime.
225: *
226: * @param time
227: * A correctly formatted HTTP date string.
228: * @return milliseconds since the epoch, or 0 if the conversion
229: * failed.
230: */
231:
232: public static long parseTime(String time) {
233: try {
234: return dateFormat.parse(time.trim(), new ParsePosition(0))
235: .getTime();
236: } catch (Exception e) {
237: return 0;
238: }
239: }
240:
241: /**
242: * Turns x-www-form-urlencoded form data into a dictionary.
243: *
244: * @param query
245: * The x-www-form-urlencoded string. May be <code>null</code>
246: *
247: * @param table
248: * The dictionary to insert the form data into.
249: */
250: public static void extractQuery(String query, Dictionary table) {
251: if (query == null) {
252: return;
253: }
254:
255: query = query.replace('+', ' ');
256: StringTokenizer st = new StringTokenizer(query, "&");
257: while (st.hasMoreTokens()) {
258: String field = st.nextToken();
259: int index = field.indexOf('=');
260: if (index < 0) {
261: table.put(urlDecode(field), "");
262: } else {
263: table.put(urlDecode(field.substring(0, index)),
264: urlDecode(field.substring(index + 1)));
265: }
266: }
267: }
268:
269: /**
270: * Returns the HTTP error string associated with the integer error code.
271: * This error string can be used in HTTP responses. Unknown codes
272: * return the string "Error"
273: *
274: * @param code
275: * The code to look up.
276: *
277: * @result The associated error string.
278: */
279: public static String getStatusPhrase(int code) {
280: switch (code) {
281: case 100:
282: return "Continue";
283: case 101:
284: return "Switching Protocols";
285: case 200:
286: return "OK";
287: case 201:
288: return "Created";
289: case 202:
290: return "Accepted";
291: case 203:
292: return "Non-Authoritative Information";
293: case 204:
294: return "No Content";
295: case 205:
296: return "Reset Content";
297: case 206:
298: return "Partial Content";
299: case 300:
300: return "Multiple Choices";
301: case 301:
302: return "Moved Permanently";
303: case 302:
304: return "Moved Temporarily";
305: case 303:
306: return "See Other";
307: case 304:
308: return "Not Modified";
309: case 305:
310: return "Use Proxy";
311: case 400:
312: return "Bad Request";
313: case 401:
314: return "Unauthorized";
315: case 402:
316: return "Payment Required";
317: case 403:
318: return "Forbidden";
319: case 404:
320: return "Not Found";
321: case 405:
322: return "Method Not Allowed";
323: case 406:
324: return "Not Acceptable";
325: case 407:
326: return "Proxy Authentication Required";
327: case 408:
328: return "Request Time-out";
329: case 409:
330: return "Conflict";
331: case 410:
332: return "Gone";
333: case 411:
334: return "Length Required";
335: case 412:
336: return "Precondition Failed";
337: case 413:
338: return "Request Entity Too Large";
339: case 414:
340: return "Request-URI Too Large";
341: case 415:
342: return "Unsupported Media Type";
343: case 500:
344: return "Server Error";
345: case 501:
346: return "Not Implemented";
347: case 502:
348: return "Bad Gateway";
349: case 503:
350: return "Service Unavailable";
351: case 504:
352: return "Gateway Time-out";
353: case 505:
354: return "HTTP Version not supported";
355: default:
356: return "Error";
357: }
358: }
359: }
|