001: /*
002: * The Apache Software License, Version 1.1
003: *
004: * Copyright (c) 2001-2004 Caucho Technology, Inc. All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions
008: * are met:
009: *
010: * 1. Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * 2. Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in
015: * the documentation and/or other materials provided with the
016: * distribution.
017: *
018: * 3. The end-user documentation included with the redistribution, if
019: * any, must include the following acknowlegement:
020: * "This product includes software developed by the
021: * Caucho Technology (http://www.caucho.com/)."
022: * Alternately, this acknowlegement may appear in the software itself,
023: * if and wherever such third-party acknowlegements normally appear.
024: *
025: * 4. The names "Hessian", "Resin", and "Caucho" must not be used to
026: * endorse or promote products derived from this software without prior
027: * written permission. For written permission, please contact
028: * info@caucho.com.
029: *
030: * 5. Products derived from this software may not be called "Resin"
031: * nor may "Resin" appear in their names without prior written
032: * permission of Caucho Technology.
033: *
034: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
035: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
036: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
037: * DISCLAIMED. IN NO EVENT SHALL CAUCHO TECHNOLOGY OR ITS CONTRIBUTORS
038: * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
039: * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
040: * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
041: * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
042: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
043: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
044: * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
045: *
046: * @author Sam
047: */
048:
049: package com.caucho.portal.generic;
050:
051: import java.io.IOException;
052: import java.io.Writer;
053: import java.util.LinkedHashSet;
054: import java.util.Map;
055: import java.util.Set;
056: import java.util.regex.Matcher;
057: import java.util.regex.Pattern;
058:
059: /**
060: * HTTP utilities.
061: *
062: * Encoding and decoding is for utf strings encoded with the rules defined for
063: * the "application/x-www-form-urlencoded" MIME format.
064: *
065: */
066: public class HttpUtil {
067: /**
068: * benchmarks
069: * URLEncoder(717) encode(103) URLDecoder(165) decode(70)
070: * without _buffer: encode(153) decode(95) encodeJ(1034) decodeJ(229)
071: */
072: private static Object _bufferLock = new Integer(1);
073: private static StringBuffer _buffer = new StringBuffer(256);
074:
075: static private StringBuffer getStringBuffer() {
076: StringBuffer buf;
077:
078: synchronized (_bufferLock) {
079: buf = _buffer;
080: _buffer = null;
081: }
082:
083: if (buf == null)
084: buf = new StringBuffer(256);
085:
086: return buf;
087: }
088:
089: static private void releaseStringBuffer(StringBuffer buf) {
090: if (buf.capacity() <= 1024) {
091:
092: synchronized (_bufferLock) {
093: if (_buffer == null
094: || _buffer.capacity() < buf.capacity()) {
095: buf.setLength(0);
096: _buffer = buf;
097: }
098: }
099: }
100: }
101:
102: private static Pattern _headerPattern = Pattern
103: .compile("s/[,;\\s]*([^;,\\s]+)[^,]*//");
104:
105: /**
106: * Return an ordered Set of header elements from an http header. If there
107: * are no header elements found, null is returned.
108: *
109: * <pre>
110: * text/html; q=1.0, text/*; q=0.8, image/gif; q=0.6, image/jpeg; q=0.8, image/*; q=0.5
111: * </pre>
112: *
113: * returns:
114: *
115: * <ul>
116: * <li>text/html
117: * <li>text/*
118: * <li>image/gif
119: * <li>image/jpeg
120: * <li>image/*
121: * </ul>
122: *
123: * Note that the qs value is ignored.
124: *
125: * @return null or a Set with at least one elemement
126: */
127: static public Set<String> getHeaderElements(String headerValue) {
128: if (headerValue == null)
129: return null;
130:
131: Matcher matcher = _headerPattern.matcher(headerValue);
132:
133: Set<String> resultSet = null;
134:
135: while (matcher.find()) {
136:
137: if (resultSet == null)
138: return new LinkedHashSet<String>();
139:
140: resultSet.add(matcher.group(1));
141: }
142:
143: return resultSet;
144: }
145:
146: /**
147: * Return only the first header element, null if headerValue is null or there
148: * are no header elements.
149: *
150: * A headerValue with a String like "text/html; charset=xxx" returns
151: * "text/html".
152: *
153: * A headerValue with a String like " en; q=1.0, fr; q=0.8 "
154: * returns "en".
155: */
156: static public String getFirstHeaderElement(String headerValue) {
157: if (headerValue == null)
158: return null;
159:
160: Matcher matcher = _headerPattern.matcher(headerValue);
161:
162: if (matcher.find())
163: return matcher.group(1);
164: else
165: return null;
166: }
167:
168: /**
169: * Extract and decode parameters out of the query string portion of the path
170: * and add them to the map. The parameters are found by looking for the '?'
171: * character.
172: *
173: * @param map the Map to put the parameters in
174: * @param url the url
175: *
176: * @returns the url without the query string
177: */
178: static public String extractParameters(Map<String, String[]> map,
179: String url) {
180: int beginIndex = url.indexOf('?');
181:
182: if (beginIndex == -1)
183: return url;
184:
185: return extractParameters(map, url, beginIndex + 1);
186: }
187:
188: /**
189: * Extract and decode parameters out of the query string portion of the path
190: * and add them to the map.
191: *
192: * @param map the Map to put the parameters in
193: * @param url the url
194: * @param beginIndex the index of the character that follows the '?' character
195: *
196: * @returns the url without the query string
197: */
198: static public String extractParameters(Map<String, String[]> map,
199: String url, int beginIndex) {
200: if (beginIndex == -1)
201: return url;
202:
203: String result = url.substring(0, beginIndex);
204:
205: StringBuffer buf = getStringBuffer();
206:
207: if (buf == null)
208: buf = new StringBuffer(256);
209:
210: String name = null;
211: String value = null;
212:
213: int len = url.length();
214:
215: do {
216: int endIndex = url.indexOf('=', beginIndex);
217:
218: if (endIndex == -1) {
219: endIndex = len;
220: } else {
221: buf.setLength(0);
222: HttpUtil.decode(url, beginIndex, endIndex, buf);
223: name = buf.toString();
224: }
225:
226: if (endIndex == len) {
227: value = "";
228: } else {
229: beginIndex = endIndex + 1;
230: endIndex = url.indexOf('&', beginIndex);
231:
232: if (endIndex == -1)
233: endIndex = len;
234:
235: buf.setLength(0);
236: HttpUtil.decode(url, beginIndex, endIndex, buf);
237: value = buf.toString();
238: }
239:
240: String[] values = map.get(name);
241:
242: if (values == null) {
243: map.put(name, new String[] { value });
244: } else {
245: int valuesLen = values.length;
246:
247: String[] newValues = new String[valuesLen + 1];
248:
249: for (int valuesIndex = 0; valuesIndex < valuesLen; valuesIndex++) {
250: newValues[valuesIndex] = values[valuesIndex];
251: }
252:
253: newValues[valuesLen] = value;
254:
255: map.put(name, newValues);
256: }
257:
258: if (endIndex == len)
259: beginIndex = -1;
260: else
261: beginIndex = url.indexOf('&', endIndex) + 1;
262:
263: } while (beginIndex > 0);
264:
265: releaseStringBuffer(buf);
266:
267: return result;
268: }
269:
270: /**
271: * Encode a string.
272: *
273: * @param source the String to encode
274: *
275: * @return the encoded String
276: */
277: static public String encode(String source) {
278: StringBuffer dest = getStringBuffer();
279:
280: encodeUri(source, 0, source.length(), dest);
281:
282: String result = dest.toString();
283:
284: releaseStringBuffer(dest);
285:
286: return result;
287: }
288:
289: /**
290: * Encode a string.
291: *
292: * @param source the String to encode
293: * @param dest a StringBuffer that receives the encoded result
294: */
295: static public void encode(String source, StringBuffer dest) {
296: encodeUri(source, 0, source.length(), dest);
297: }
298:
299: /**
300: * Extract and encode a portion of a String.
301: *
302: * @param source the String to encode
303: * @param beginIndex the begin index, inclusive
304: * @param endIndex the end index, exclusive
305: * @param dest a StringBuffer that receives the encoded result
306: */
307: static public void encode(String source, int beginIndex,
308: int endIndex, StringBuffer dest) {
309: encodeUri(source, beginIndex, endIndex, dest);
310: }
311:
312: /**
313: * Extract and encode a portion of a StringBuffer.
314: *
315: * @param source the StringBuffer to encode
316: * @param beginIndex the begin index, inclusive
317: * @param endIndex the end index, exclusive
318: * @param dest a StringBuffer that receives the encoded result
319: */
320: static public void encode(StringBuffer source, int beginIndex,
321: int endIndex, StringBuffer dest) {
322: encodeUri(source, beginIndex, endIndex, dest);
323: }
324:
325: static public void encodeUri(CharSequence source, int beginIndex,
326: int endIndex, StringBuffer dest) {
327: for (int i = beginIndex; i < endIndex; i++) {
328: char ch = source.charAt(i);
329: if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'z')
330: || (ch >= '0' && ch <= '9') || ch == '-'
331: || ch == '_' || ch == '.' || ch == '*') {
332: dest.append(ch);
333: } else if (ch == ' ') {
334: dest.append('+');
335: } else if (ch <= 0xff) {
336: // 8 byte (utf-8)
337: dest.append('%');
338: dest.append(encodeHex(ch >> 4));
339: dest.append(encodeHex(ch));
340: } else {
341: // 16 byte (utf-16)
342: dest.append('%');
343: dest.append('u');
344: dest.append(encodeHex(ch >> 12));
345: dest.append(encodeHex(ch >> 8));
346: dest.append(encodeHex(ch >> 4));
347: dest.append(encodeHex(ch));
348: }
349: }
350: }
351:
352: /**
353: * Encode a string.
354: *
355: * @param source the String to encode
356: * @param dest a Writer that receives the encoded result
357: *
358: * @return the encoded String
359: */
360: static public void encode(String source, Writer dest)
361: throws IOException {
362: encodeUri(source, 0, source.length(), dest);
363: }
364:
365: /**
366: * Extract and encode a portion of a String.
367: *
368: * @param source the String to encode
369: * @param beginIndex the begin index, inclusive
370: * @param endIndex the end index, exclusive
371: * @param dest a Writer that receives the encoded result
372: */
373: static public void encode(String source, int beginIndex,
374: int endIndex, Writer dest) throws IOException {
375: encodeUri(source, beginIndex, endIndex, dest);
376: }
377:
378: /**
379: * Extract and encode a portion of a StringBuffer.
380: *
381: * @param source the StringBuffer to encode
382: * @param beginIndex the begin index, inclusive
383: * @param endIndex the end index, exclusive
384: * @param dest a Writer that receives the encoded result
385: */
386: static public void encode(StringBuffer source, int beginIndex,
387: int endIndex, Writer dest) throws IOException {
388: encodeUri(source, beginIndex, endIndex, dest);
389: }
390:
391: static public void encodeUri(CharSequence source, int beginIndex,
392: int endIndex, Writer dest) throws IOException {
393: for (int i = beginIndex; i < endIndex; i++) {
394: char ch = source.charAt(i);
395: if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'z')
396: || (ch >= '0' && ch <= '9') || ch == '-'
397: || ch == '_' || ch == '.' || ch == '*') {
398: dest.write(ch);
399: } else if (ch == ' ') {
400: dest.write('+');
401: } else if (ch <= 0xff) {
402: // 8 byte (utf-8)
403: dest.write('%');
404: dest.write(encodeHex(ch >> 4));
405: dest.write(encodeHex(ch));
406: } else {
407: // 16 byte (utf-16)
408: dest.write('%');
409: dest.write('u');
410: dest.write(encodeHex(ch >> 12));
411: dest.write(encodeHex(ch >> 8));
412: dest.write(encodeHex(ch >> 4));
413: dest.write(encodeHex(ch));
414: }
415: }
416: }
417:
418: /**
419: * Decode a string.
420: *
421: * @param source the String to decode
422: *
423: * @return the decoded String
424: */
425: static public String decode(String source) {
426: StringBuffer dest = getStringBuffer();
427:
428: decodeUri(source, 0, source.length(), dest);
429:
430: String result = dest.toString();
431:
432: releaseStringBuffer(dest);
433:
434: return result;
435: }
436:
437: /**
438: * Decode a string.
439: *
440: * @param source the String to decode
441: * @param dest a StringBuffer that receives the decoded result
442: */
443: static public void decode(String source, StringBuffer dest) {
444: decodeUri(source, 0, source.length(), dest);
445: }
446:
447: /**
448: * Extract and decode an encoded portion of a string.
449: *
450: * @param source the String to extract from
451: * @param beginIndex the begin index, inclusive
452: * @param endIndex the end index, exclusive
453: * @param dest a StringBuffer that receives the decoded result
454: */
455: static public void decode(String source, int beginIndex,
456: int endIndex, StringBuffer dest) {
457: decodeUri(source, beginIndex, endIndex, dest);
458: }
459:
460: /**
461: * Extract and decode an encoded portion of a StringBuffer.
462: *
463: * @param source the StringBuffer to extract from
464: * @param beginIndex the begin index, inclusive
465: * @param endIndex the end index, exclusive
466: * @param dest a StringBuffer that receives the decoded result
467: */
468: static public void decode(StringBuffer source, int beginIndex,
469: int endIndex, StringBuffer dest) {
470: decodeUri(source, beginIndex, endIndex, dest);
471: }
472:
473: static private void decodeUri(CharSequence source, int beginIndex,
474: int endIndex, StringBuffer dest) {
475: int i = beginIndex;
476:
477: while (i < endIndex) {
478: char ch = source.charAt(i);
479: if (ch == '%')
480: i = scanUriEscape(source, i + 1, endIndex, dest);
481: else if (ch == '+') {
482: dest.append(' ');
483: i++;
484: } else {
485: dest.append(ch);
486: i++;
487: }
488: }
489: }
490:
491: private static int scanUriEscape(CharSequence source, int i,
492: int len, StringBuffer dest) {
493: int ch1 = i < len ? (((int) source.charAt(i++)) & 0xff) : -1;
494:
495: if (ch1 == 'u') {
496: ch1 = i < len ? (((int) source.charAt(i++)) & 0xff) : -1;
497: int ch2 = i < len ? (((int) source.charAt(i++)) & 0xff)
498: : -1;
499: int ch3 = i < len ? (((int) source.charAt(i++)) & 0xff)
500: : -1;
501: int ch4 = i < len ? (((int) source.charAt(i++)) & 0xff)
502: : -1;
503:
504: dest
505: .append((char) ((decodeHex(ch1) << 12)
506: + (decodeHex(ch2) << 8)
507: + (decodeHex(ch3) << 4) + (decodeHex(ch4))));
508: } else {
509: int ch2 = i < len ? (((int) source.charAt(i++)) & 0xff)
510: : -1;
511:
512: int b = (decodeHex(ch1) << 4) + decodeHex(ch2);
513: ;
514:
515: dest.append((char) b);
516: }
517:
518: return i;
519: }
520:
521: static char encodeHex(int ch) {
522: ch &= 0xf;
523: if (ch < 10)
524: return (char) (ch + '0');
525: else
526: return (char) (ch + 'a' - 10);
527: }
528:
529: private static int decodeHex(int ch) {
530: if (ch >= '0' && ch <= '9')
531: return ch - '0';
532: else if (ch >= 'a' && ch <= 'f')
533: return ch - 'a' + 10;
534: else if (ch >= 'A' && ch <= 'F')
535: return ch - 'A' + 10;
536: else
537: return -1;
538: }
539: }
|