001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: * Free SoftwareFoundation, Inc.
023: * 59 Temple Place, Suite 330
024: * Boston, MA 02111-1307 USA
025: *
026: * @author Scott Ferguson
027: */
028:
029: package com.caucho.server.dispatch;
030:
031: import com.caucho.config.ConfigException;
032: import com.caucho.i18n.CharacterEncoding;
033: import com.caucho.log.Log;
034: import com.caucho.server.util.CauchoSystem;
035: import com.caucho.util.CharBuffer;
036: import com.caucho.util.L10N;
037: import com.caucho.vfs.ByteToChar;
038:
039: import java.io.IOException;
040: import java.io.UnsupportedEncodingException;
041: import java.util.logging.Level;
042: import java.util.logging.Logger;
043:
044: /**
045: * Decodes invocation URI.
046: */
047: public class InvocationDecoder {
048: static final Logger log = Log.open(InvocationDecoder.class);
049: static final L10N L = new L10N(InvocationDecoder.class);
050:
051: // The character encoding
052: private String _encoding = "UTF-8";
053:
054: private String _sessionCookie = "JSESSIONID";
055: private String _sslSessionCookie;
056:
057: // The URL-encoded session suffix
058: private String _sessionSuffix = ";jsessionid=";
059: private char _sessionSuffixChar = ';';
060:
061: // The URL-encoded session prefix
062: private String _sessionPrefix;
063:
064: /**
065: * Creates the invocation decoder.
066: */
067: public InvocationDecoder() {
068: _encoding = CharacterEncoding.getLocalEncoding();
069: if (_encoding == null)
070: _encoding = "UTF-8";
071: }
072:
073: /**
074: * Returns the character encoding.
075: */
076: public String getEncoding() {
077: return _encoding;
078: }
079:
080: /**
081: * Sets the character encoding.
082: */
083: public void setEncoding(String encoding) {
084: _encoding = encoding;
085: }
086:
087: /**
088: * Sets the session cookie
089: */
090: public void setSessionCookie(String cookie) {
091: _sessionCookie = cookie;
092: }
093:
094: /**
095: * Gets the session cookie
096: */
097: public String getSessionCookie() {
098: return _sessionCookie;
099: }
100:
101: /**
102: * Sets the SSL session cookie
103: */
104: public void setSSLSessionCookie(String cookie) {
105: _sslSessionCookie = cookie;
106: }
107:
108: /**
109: * Gets the SSL session cookie
110: */
111: public String getSSLSessionCookie() {
112: if (_sslSessionCookie != null)
113: return _sslSessionCookie;
114: else
115: return _sessionCookie;
116: }
117:
118: /**
119: * Sets the session url prefix.
120: */
121: public void setSessionURLPrefix(String prefix) {
122: _sessionSuffix = prefix;
123: if (_sessionSuffix != null)
124: _sessionSuffixChar = _sessionSuffix.charAt(0);
125: }
126:
127: /**
128: * Gets the session url prefix.
129: */
130: public String getSessionURLPrefix() {
131: return _sessionSuffix;
132: }
133:
134: /**
135: * Sets the alternate session url prefix.
136: */
137: public void setAlternateSessionURLPrefix(String prefix)
138: throws ConfigException {
139: if (!prefix.startsWith("/"))
140: prefix = '/' + prefix;
141:
142: if (prefix.lastIndexOf('/') > 0)
143: throw new ConfigException(
144: L
145: .l(
146: "`{0}' is an invalidate alternate-session-url-prefix. The url-prefix must not have any embedded '/'.",
147: prefix));
148:
149: _sessionPrefix = prefix;
150: _sessionSuffix = null;
151: }
152:
153: /**
154: * Gets the session url prefix.
155: */
156: public String getAlternateSessionURLPrefix() {
157: return _sessionPrefix;
158: }
159:
160: /**
161: * Splits out the query string and unescape the value.
162: */
163: public void splitQueryAndUnescape(Invocation invocation,
164: byte[] rawURI, int uriLength) throws IOException {
165: for (int i = 0; i < uriLength; i++) {
166: if (rawURI[i] == '?') {
167: i++;
168:
169: // XXX: should be the host encoding?
170: String queryString = byteToChar(rawURI, i, uriLength
171: - i, "ISO-8859-1");
172: invocation.setQueryString(queryString);
173:
174: uriLength = i - 1;
175: break;
176: }
177: }
178:
179: String rawURIString = byteToChar(rawURI, 0, uriLength,
180: "ISO-8859-1");
181: invocation.setRawURI(rawURIString);
182:
183: String decodedURI = normalizeUriEscape(rawURI, 0, uriLength,
184: _encoding);
185:
186: if (_sessionSuffix != null) {
187: int p = decodedURI.indexOf(_sessionSuffix);
188:
189: if (p >= 0) {
190: int suffixLength = _sessionSuffix.length();
191: int tail = decodedURI.indexOf(';', p + suffixLength);
192: String sessionId;
193:
194: if (tail > 0)
195: sessionId = decodedURI.substring(p + suffixLength,
196: tail);
197: else
198: sessionId = decodedURI.substring(p + suffixLength);
199:
200: decodedURI = decodedURI.substring(0, p);
201:
202: invocation.setSessionId(sessionId);
203:
204: p = rawURIString.indexOf(_sessionSuffix);
205: if (p > 0) {
206: rawURIString = rawURIString.substring(0, p);
207: invocation.setRawURI(rawURIString);
208: }
209: }
210: } else if (_sessionPrefix != null) {
211: if (decodedURI.startsWith(_sessionPrefix)) {
212: int prefixLength = _sessionPrefix.length();
213:
214: int tail = decodedURI.indexOf('/', prefixLength);
215: String sessionId;
216:
217: if (tail > 0) {
218: sessionId = decodedURI
219: .substring(prefixLength, tail);
220: decodedURI = decodedURI.substring(tail);
221: invocation.setRawURI(rawURIString.substring(tail));
222: } else {
223: sessionId = decodedURI.substring(prefixLength);
224: decodedURI = "/";
225: invocation.setRawURI("/");
226: }
227:
228: invocation.setSessionId(sessionId);
229: }
230: }
231:
232: String uri = normalizeUri(decodedURI);
233:
234: invocation.setURI(uri);
235: invocation.setContextURI(uri);
236: }
237:
238: /**
239: * Splits out the query string, and normalizes the URI, assuming nothing
240: * needs unescaping.
241: */
242: public void splitQuery(Invocation invocation, String rawURI)
243: throws IOException {
244: int p = rawURI.indexOf('?');
245: if (p > 0) {
246: invocation.setQueryString(rawURI.substring(p + 1));
247:
248: rawURI = rawURI.substring(0, p);
249: }
250:
251: invocation.setRawURI(rawURI);
252:
253: String uri = normalizeUri(rawURI);
254:
255: invocation.setURI(uri);
256: invocation.setContextURI(uri);
257: }
258:
259: /**
260: * Just normalize the URI.
261: */
262: public void normalizeURI(Invocation invocation, String rawURI)
263: throws IOException {
264: invocation.setRawURI(rawURI);
265:
266: String uri = normalizeUri(rawURI);
267:
268: invocation.setURI(uri);
269: invocation.setContextURI(uri);
270: }
271:
272: /**
273: * Splits out the session.
274: */
275: public void splitSession(Invocation invocation) {
276: if (_sessionSuffix != null) {
277: String uri = invocation.getURI();
278: } else if (_sessionPrefix != null) {
279: String uri = invocation.getURI();
280: }
281: }
282:
283: private String byteToChar(byte[] buffer, int offset, int length,
284: String encoding) {
285: ByteToChar converter = ByteToChar.create();
286: // XXX: make this configurable
287:
288: if (encoding == null)
289: encoding = "utf-8";
290:
291: try {
292: converter.setEncoding(encoding);
293: } catch (UnsupportedEncodingException e) {
294: log.log(Level.FINE, e.toString(), e);
295: }
296:
297: try {
298: for (; length > 0; length--)
299: converter.addByte(buffer[offset++]);
300:
301: return converter.getConvertedString();
302: } catch (IOException e) {
303: return "unknown";
304: }
305: }
306:
307: /**
308: * Normalize a uri to remove '///', '/./', 'foo/..', etc.
309: *
310: * @param uri the raw uri to be normalized
311: * @return a normalized URI
312: */
313: public static String normalizeUri(String uri) throws IOException {
314: return normalizeUri(uri, CauchoSystem.isWindows());
315: }
316:
317: /**
318: * Normalize a uri to remove '///', '/./', 'foo/..', etc.
319: *
320: * @param uri the raw uri to be normalized
321: * @return a normalized URI
322: */
323: public static String normalizeUri(String uri, boolean isWindows)
324: throws IOException {
325: CharBuffer cb = new CharBuffer();
326:
327: int len = uri.length();
328:
329: if (len > 1024)
330: throw new BadRequestException(L
331: .l("The request contains an illegal URL."));
332:
333: boolean isBogus;
334: char ch;
335: char ch1;
336: if (len == 0 || (ch = uri.charAt(0)) != '/' && ch != '\\')
337: cb.append('/');
338:
339: for (int i = 0; i < len; i++) {
340: ch = uri.charAt(i);
341:
342: if (ch == '/' || ch == '\\') {
343: dots: while (i + 1 < len) {
344: ch = uri.charAt(i + 1);
345:
346: if (ch == '/' || ch == '\\')
347: i++;
348: else if (ch != '.')
349: break dots;
350: else if (len <= i + 2
351: || (ch = uri.charAt(i + 2)) == '/'
352: || ch == '\\') {
353: i += 2;
354: } else if (ch != '.')
355: break dots;
356: else if (len <= i + 3
357: || (ch = uri.charAt(i + 3)) == '/'
358: || ch == '\\') {
359: int j;
360:
361: for (j = cb.length() - 1; j >= 0; j--) {
362: if ((ch = cb.charAt(j)) == '/'
363: || ch == '\\')
364: break;
365: }
366: if (j > 0)
367: cb.setLength(j);
368: else
369: cb.setLength(0);
370: i += 3;
371: } else {
372: throw new BadRequestException(
373: L
374: .l("The request contains an illegal URL."));
375: }
376: }
377:
378: while (isWindows
379: && cb.getLength() > 0
380: && ((ch = cb.getLastChar()) == '.' || ch == ' ')) {
381: cb.setLength(cb.getLength() - 1);
382: }
383:
384: cb.append('/');
385: } else if (ch == 0)
386: throw new BadRequestException(L
387: .l("The request contains an illegal URL."));
388: else
389: cb.append(ch);
390: }
391:
392: while (isWindows && cb.getLength() > 0
393: && ((ch = cb.getLastChar()) == '.' || ch == ' ')) {
394: cb.setLength(cb.getLength() - 1);
395: }
396:
397: return cb.toString();
398: }
399:
400: /**
401: * Converts the escaped URI to a string.
402: *
403: * @param rawUri the escaped URI
404: * @param i index into the URI
405: * @param len the length of the uri
406: * @param encoding the character encoding to handle %xx
407: *
408: * @return the converted URI
409: */
410: private static String normalizeUriEscape(byte[] rawUri, int i,
411: int len, String encoding) throws IOException {
412: ByteToChar converter = ByteToChar.create();
413: // XXX: make this configurable
414:
415: if (encoding == null)
416: encoding = "utf-8";
417:
418: try {
419: converter.setEncoding(encoding);
420: } catch (UnsupportedEncodingException e) {
421: log.log(Level.FINE, e.toString(), e);
422: }
423:
424: try {
425: while (i < len) {
426: int ch = rawUri[i++] & 0xff;
427:
428: if (ch == '%')
429: i = scanUriEscape(converter, rawUri, i, len);
430: else
431: converter.addByte(ch);
432: }
433:
434: return converter.getConvertedString();
435: } catch (Exception e) {
436: throw new BadRequestException(
437: L
438: .l(
439: "The URL contains escaped bytes unsupported by the {0} encoding.",
440: encoding));
441: }
442: }
443:
444: /**
445: * Scans the next character from URI, adding it to the converter.
446: *
447: * @param converter the byte-to-character converter
448: * @param rawUri the raw URI
449: * @param i index into the URI
450: * @param len the raw URI length
451: *
452: * @return next index into the URI
453: */
454: private static int scanUriEscape(ByteToChar converter,
455: byte[] rawUri, int i, int len) throws IOException {
456: int ch1 = i < len ? (rawUri[i++] & 0xff) : -1;
457:
458: if (ch1 == 'u') {
459: ch1 = i < len ? (rawUri[i++] & 0xff) : -1;
460: int ch2 = i < len ? (rawUri[i++] & 0xff) : -1;
461: int ch3 = i < len ? (rawUri[i++] & 0xff) : -1;
462: int ch4 = i < len ? (rawUri[i++] & 0xff) : -1;
463:
464: converter
465: .addChar((char) ((toHex(ch1) << 12)
466: + (toHex(ch2) << 8) + (toHex(ch3) << 4) + (toHex(ch4))));
467: } else {
468: int ch2 = i < len ? (rawUri[i++] & 0xff) : -1;
469:
470: int b = (toHex(ch1) << 4) + toHex(ch2);
471: ;
472:
473: converter.addByte(b);
474: }
475:
476: return i;
477: }
478:
479: /**
480: * Convert a character to hex
481: */
482: private static int toHex(int ch) {
483: if (ch >= '0' && ch <= '9')
484: return ch - '0';
485: else if (ch >= 'a' && ch <= 'f')
486: return ch - 'a' + 10;
487: else if (ch >= 'A' && ch <= 'F')
488: return ch - 'A' + 10;
489: else
490: return -1;
491: }
492: }
|