001: /*
002: * Copyright 1999,2004 The Apache Software Foundation.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.apache.coyote.tomcat5;
018:
019: import java.io.IOException;
020:
021: import javax.servlet.http.Cookie;
022: import javax.servlet.http.HttpServletRequest;
023:
024: import org.apache.catalina.Context;
025: import org.apache.catalina.Globals;
026: import org.apache.catalina.Wrapper;
027: import org.apache.catalina.util.StringManager;
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030: import org.apache.coyote.ActionCode;
031: import org.apache.coyote.Adapter;
032: import org.apache.coyote.Request;
033: import org.apache.coyote.Response;
034: import org.apache.tomcat.util.buf.B2CConverter;
035: import org.apache.tomcat.util.buf.ByteChunk;
036: import org.apache.tomcat.util.buf.CharChunk;
037: import org.apache.tomcat.util.buf.MessageBytes;
038: import org.apache.tomcat.util.http.Cookies;
039: import org.apache.tomcat.util.http.ServerCookie;
040:
041: /**
042: * Implementation of a request processor which delegates the processing to a
043: * Coyote processor.
044: *
045: * @author Craig R. McClanahan
046: * @author Remy Maucherat
047: * @version $Revision: 1.26 $ $Date: 2004/05/14 11:00:25 $
048: */
049:
050: public class CoyoteAdapter implements Adapter {
051: private static Log log = LogFactory.getLog(CoyoteAdapter.class);
052:
053: // -------------------------------------------------------------- Constants
054:
055: public static final int ADAPTER_NOTES = 1;
056:
057: // ----------------------------------------------------------- Constructors
058:
059: /**
060: * Construct a new CoyoteProcessor associated with the specified connector.
061: *
062: * @param connector CoyoteConnector that owns this processor
063: * @param id Identifier of this CoyoteProcessor (unique per connector)
064: */
065: public CoyoteAdapter(CoyoteConnector connector) {
066:
067: super ();
068: this .connector = connector;
069: this .debug = connector.getDebug();
070:
071: }
072:
073: // ----------------------------------------------------- Instance Variables
074:
075: /**
076: * The CoyoteConnector with which this processor is associated.
077: */
078: private CoyoteConnector connector = null;
079:
080: /**
081: * The debugging detail level for this component.
082: */
083: private int debug = 0;
084:
085: /**
086: * The match string for identifying a session ID parameter.
087: */
088: private static final String match = ";"
089: + Globals.SESSION_PARAMETER_NAME + "=";
090:
091: /**
092: * The match string for identifying a session ID parameter.
093: */
094: private static final char[] SESSION_ID = match.toCharArray();
095:
096: /**
097: * The string manager for this package.
098: */
099: protected StringManager sm = StringManager
100: .getManager(Constants.Package);
101:
102: // -------------------------------------------------------- Adapter Methods
103:
104: /**
105: * Service method.
106: */
107: public void service(Request req, Response res) throws Exception {
108:
109: CoyoteRequest request = (CoyoteRequest) req
110: .getNote(ADAPTER_NOTES);
111: CoyoteResponse response = (CoyoteResponse) res
112: .getNote(ADAPTER_NOTES);
113:
114: if (request == null) {
115:
116: // Create objects
117: request = (CoyoteRequest) connector.createRequest();
118: request.setCoyoteRequest(req);
119: response = (CoyoteResponse) connector.createResponse();
120: response.setCoyoteResponse(res);
121:
122: // Link objects
123: request.setResponse(response);
124: response.setRequest(request);
125:
126: // Set as notes
127: req.setNote(ADAPTER_NOTES, request);
128: res.setNote(ADAPTER_NOTES, response);
129:
130: // Set query string encoding
131: req.getParameters().setQueryStringEncoding(
132: connector.getURIEncoding());
133:
134: }
135:
136: if (connector.isXpoweredBy()) {
137: response.addHeader("X-Powered-By", "Servlet/2.4");
138: }
139:
140: try {
141:
142: // Parse and set Catalina and configuration specific
143: // request parameters
144: if (postParseRequest(req, request, res, response)) {
145: // Calling the container
146: connector.getContainer().invoke(request, response);
147: }
148:
149: response.finishResponse();
150: req.action(ActionCode.ACTION_POST_REQUEST, null);
151:
152: } catch (IOException e) {
153: ;
154: } catch (Throwable t) {
155: log.error(sm.getString("coyoteAdapter.service"), t);
156: } finally {
157: // Recycle the wrapper request and response
158: request.recycle();
159: response.recycle();
160: }
161:
162: }
163:
164: // ------------------------------------------------------ Protected Methods
165:
166: /**
167: * Parse additional request parameters.
168: */
169: protected boolean postParseRequest(Request req,
170: CoyoteRequest request, Response res, CoyoteResponse response)
171: throws Exception {
172: // XXX the processor needs to set a correct scheme and port prior to this point,
173: // in ajp13 protocols dont make sense to get the port from the connector..
174: // XXX the processor may have set a correct scheme and port prior to this point,
175: // in ajp13 protocols dont make sense to get the port from the connector...
176: // otherwise, use connector configuration
177: if (!req.scheme().isNull()) {
178: // use processor specified scheme to determine secure state
179: request.setSecure(req.scheme().equals("https"));
180: } else {
181: // use connector scheme and secure configuration, (defaults to
182: // "http" and false respectively)
183: req.scheme().setString(connector.getScheme());
184: request.setSecure(connector.getSecure());
185: }
186:
187: // Filter trace method
188: if (!connector.getAllowTrace()
189: && req.method().equalsIgnoreCase("TRACE")) {
190: res.setStatus(403);
191: res.setMessage("TRACE method is not allowed");
192: return false;
193: }
194:
195: // FIXME: the code below doesnt belongs to here,
196: // this is only have sense
197: // in Http11, not in ajp13..
198: // At this point the Host header has been processed.
199: // Override if the proxyPort/proxyHost are set
200: String proxyName = connector.getProxyName();
201: int proxyPort = connector.getProxyPort();
202: if (proxyPort != 0) {
203: req.setServerPort(proxyPort);
204: }
205: if (proxyName != null) {
206: req.serverName().setString(proxyName);
207: }
208:
209: // URI decoding
210: MessageBytes decodedURI = req.decodedURI();
211: decodedURI.duplicate(req.requestURI());
212:
213: if (decodedURI.getType() == MessageBytes.T_BYTES) {
214: // %xx decoding of the URL
215: try {
216: req.getURLDecoder().convert(decodedURI, false);
217: } catch (IOException ioe) {
218: res.setStatus(400);
219: res.setMessage("Invalid URI");
220: throw ioe;
221: }
222: // Normalization
223: if (!normalize(req.decodedURI())) {
224: res.setStatus(400);
225: res.setMessage("Invalid URI");
226: return false;
227: }
228: // Character decoding
229: convertURI(decodedURI, request);
230: } else {
231: // The URL is chars or String, and has been sent using an in-memory
232: // protocol handler, we have to assume the URL has been properly
233: // decoded already
234: decodedURI.toChars();
235: }
236:
237: // Set the remote principal
238: String principal = req.getRemoteUser().toString();
239: if (principal != null) {
240: request.setUserPrincipal(new CoyotePrincipal(principal));
241: }
242:
243: // Set the authorization type
244: String authtype = req.getAuthType().toString();
245: if (authtype != null) {
246: request.setAuthType(authtype);
247: }
248:
249: // Parse session Id
250: parseSessionId(req, request);
251:
252: // Remove any remaining parameters (other than session id, which has
253: // already been removed in parseSessionId()) from the URI, so they
254: // won't be considered by the mapping algorithm.
255: CharChunk uriCC = decodedURI.getCharChunk();
256: int semicolon = uriCC.indexOf(';');
257: if (semicolon > 0) {
258: decodedURI.setChars(uriCC.getBuffer(), uriCC.getStart(),
259: semicolon);
260: }
261:
262: // Request mapping.
263: connector.getMapper().map(req.serverName(), decodedURI,
264: request.getMappingData());
265: request.setContext((Context) request.getMappingData().context);
266: request.setWrapper((Wrapper) request.getMappingData().wrapper);
267:
268: // Possible redirect
269: MessageBytes redirectPathMB = request.getMappingData().redirectPath;
270: if (!redirectPathMB.isNull()) {
271: String redirectPath = redirectPathMB.toString();
272: String query = request.getQueryString();
273: if (query != null) {
274: // This is not optimal, but as this is not very common, it
275: // shouldn't matter
276: redirectPath = redirectPath + "?" + query;
277: }
278: response.sendRedirect(redirectPath);
279: return false;
280: }
281:
282: // Parse session Id
283: parseSessionCookiesId(req, request);
284:
285: return true;
286: }
287:
288: /**
289: * Parse session id in URL.
290: */
291: protected void parseSessionId(Request req, CoyoteRequest request) {
292:
293: CharChunk uriCC = req.decodedURI().getCharChunk();
294: int semicolon = uriCC.indexOf(match, 0, match.length(), 0);
295:
296: if (semicolon > 0) {
297:
298: // Parse session ID, and extract it from the decoded request URI
299: int start = uriCC.getStart();
300: int end = uriCC.getEnd();
301:
302: int sessionIdStart = start + semicolon + match.length();
303: int semicolon2 = uriCC.indexOf(';', sessionIdStart);
304: if (semicolon2 >= 0) {
305: request.setRequestedSessionId(new String(uriCC
306: .getBuffer(), sessionIdStart, semicolon2
307: - semicolon - match.length()));
308: } else {
309: request.setRequestedSessionId(new String(uriCC
310: .getBuffer(), sessionIdStart, end
311: - sessionIdStart));
312: }
313: request.setRequestedSessionURL(true);
314:
315: // Extract session ID from request URI
316: ByteChunk uriBC = req.requestURI().getByteChunk();
317: start = uriBC.getStart();
318: end = uriBC.getEnd();
319: semicolon = uriBC.indexOf(match, 0, match.length(), 0);
320:
321: if (semicolon > 0) {
322: sessionIdStart = start + semicolon;
323: semicolon2 = uriCC.indexOf(';', start + semicolon
324: + match.length());
325: uriBC.setEnd(start + semicolon);
326: byte[] buf = uriBC.getBuffer();
327: if (semicolon2 >= 0) {
328: for (int i = 0; i < end - start - semicolon2; i++) {
329: buf[start + semicolon + i] = buf[start + i
330: + semicolon2];
331: }
332: uriBC.setBytes(buf, start, semicolon
333: + (end - start - semicolon2));
334: }
335: }
336:
337: } else {
338: request.setRequestedSessionId(null);
339: request.setRequestedSessionURL(false);
340: }
341:
342: }
343:
344: /**
345: * Parse session id in URL.
346: */
347: protected void parseSessionCookiesId(Request req,
348: CoyoteRequest request) {
349:
350: // Parse session id from cookies
351: Cookies serverCookies = req.getCookies();
352: int count = serverCookies.getCookieCount();
353: if (count <= 0)
354: return;
355:
356: for (int i = 0; i < count; i++) {
357: ServerCookie scookie = serverCookies.getCookie(i);
358: if (scookie.getName().equals(Globals.SESSION_COOKIE_NAME)) {
359: // Override anything requested in the URL
360: if (!request.isRequestedSessionIdFromCookie()) {
361: // Accept only the first session id cookie
362: request.setRequestedSessionId(scookie.getValue()
363: .toString());
364: request.setRequestedSessionCookie(true);
365: request.setRequestedSessionURL(false);
366: if (log.isDebugEnabled())
367: log.debug(" Requested cookie session id is "
368: + ((HttpServletRequest) request
369: .getRequest())
370: .getRequestedSessionId());
371: } else {
372: if (!request.isRequestedSessionIdValid()) {
373: // Replace the session id until one is valid
374: request.setRequestedSessionId(scookie
375: .getValue().toString());
376: }
377: }
378: }
379: }
380:
381: }
382:
383: /**
384: * Character conversion of the URI.
385: */
386: protected void convertURI(MessageBytes uri, CoyoteRequest request)
387: throws Exception {
388:
389: ByteChunk bc = uri.getByteChunk();
390: CharChunk cc = uri.getCharChunk();
391: cc.allocate(bc.getLength(), -1);
392:
393: String enc = connector.getURIEncoding();
394: if (enc != null) {
395: B2CConverter conv = request.getURIConverter();
396: try {
397: if (conv == null) {
398: conv = new B2CConverter(enc);
399: request.setURIConverter(conv);
400: } else {
401: conv.recycle();
402: }
403: } catch (IOException e) {
404: // Ignore
405: log.error("Invalid URI encoding; using HTTP default");
406: connector.setURIEncoding(null);
407: }
408: if (conv != null) {
409: try {
410: conv.convert(bc, cc);
411: uri.setChars(cc.getBuffer(), cc.getStart(), cc
412: .getLength());
413: return;
414: } catch (IOException e) {
415: log
416: .error("Invalid URI character encoding; trying ascii");
417: cc.recycle();
418: }
419: }
420: }
421:
422: // Default encoding: fast conversion
423: byte[] bbuf = bc.getBuffer();
424: char[] cbuf = cc.getBuffer();
425: int start = bc.getStart();
426: for (int i = 0; i < bc.getLength(); i++) {
427: cbuf[i] = (char) (bbuf[i + start] & 0xff);
428: }
429: uri.setChars(cbuf, 0, bc.getLength());
430:
431: }
432:
433: /**
434: * Normalize URI.
435: * <p>
436: * This method normalizes "\", "//", "/./" and "/../". This method will
437: * return false when trying to go above the root, or if the URI contains
438: * a null byte.
439: *
440: * @param uriMB URI to be normalized
441: */
442: public static boolean normalize(MessageBytes uriMB) {
443:
444: ByteChunk uriBC = uriMB.getByteChunk();
445: byte[] b = uriBC.getBytes();
446: int start = uriBC.getStart();
447: int end = uriBC.getEnd();
448:
449: // URL * is acceptable
450: if ((end - start == 1) && b[start] == (byte) '*')
451: return true;
452:
453: int pos = 0;
454: int index = 0;
455:
456: // Replace '\' with '/'
457: // Check for null byte
458: for (pos = start; pos < end; pos++) {
459: if (b[pos] == (byte) '\\')
460: b[pos] = (byte) '/';
461: if (b[pos] == (byte) 0)
462: return false;
463: }
464:
465: // The URL must start with '/'
466: if (b[start] != (byte) '/') {
467: return false;
468: }
469:
470: // Replace "//" with "/"
471: for (pos = start; pos < (end - 1); pos++) {
472: if (b[pos] == (byte) '/') {
473: while ((pos + 1 < end) && (b[pos + 1] == (byte) '/')) {
474: copyBytes(b, pos, pos + 1, end - pos - 1);
475: end--;
476: }
477: }
478: }
479:
480: // If the URI ends with "/." or "/..", then we append an extra "/"
481: // Note: It is possible to extend the URI by 1 without any side effect
482: // as the next character is a non-significant WS.
483: if (((end - start) >= 2) && (b[end - 1] == (byte) '.')) {
484: if ((b[end - 2] == (byte) '/')
485: || ((b[end - 2] == (byte) '.') && (b[end - 3] == (byte) '/'))) {
486: b[end] = (byte) '/';
487: end++;
488: }
489: }
490:
491: uriBC.setEnd(end);
492:
493: index = 0;
494:
495: // Resolve occurrences of "/./" in the normalized path
496: while (true) {
497: index = uriBC.indexOf("/./", 0, 3, index);
498: if (index < 0)
499: break;
500: copyBytes(b, start + index, start + index + 2, end - start
501: - index - 2);
502: end = end - 2;
503: uriBC.setEnd(end);
504: }
505:
506: index = 0;
507:
508: // Resolve occurrences of "/../" in the normalized path
509: while (true) {
510: index = uriBC.indexOf("/../", 0, 4, index);
511: if (index < 0)
512: break;
513: // Prevent from going outside our context
514: if (index == 0)
515: return false;
516: int index2 = -1;
517: for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos--) {
518: if (b[pos] == (byte) '/') {
519: index2 = pos;
520: }
521: }
522: copyBytes(b, start + index2, start + index + 3, end - start
523: - index - 3);
524: end = end + index2 - index - 3;
525: uriBC.setEnd(end);
526: index = index2;
527: }
528:
529: uriBC.setBytes(b, start, end);
530:
531: return true;
532:
533: }
534:
535: // ------------------------------------------------------ Protected Methods
536:
537: /**
538: * Copy an array of bytes to a different position. Used during
539: * normalization.
540: */
541: protected static void copyBytes(byte[] b, int dest, int src, int len) {
542: for (int pos = 0; pos < len; pos++) {
543: b[pos + dest] = b[pos + src];
544: }
545: }
546:
547: /**
548: * Log a message on the Logger associated with our Container (if any)
549: *
550: * @param message Message to be logged
551: */
552: protected void log(String message) {
553: log.info(message);
554: }
555:
556: /**
557: * Log a message on the Logger associated with our Container (if any)
558: *
559: * @param message Message to be logged
560: * @param throwable Associated exception
561: */
562: protected void log(String message, Throwable throwable) {
563: log.error(message, throwable);
564: }
565:
566: }
|