001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.catalina.connector;
019:
020: import java.io.IOException;
021:
022: import org.apache.catalina.CometEvent;
023: import org.apache.catalina.Context;
024: import org.apache.catalina.Globals;
025: import org.apache.catalina.Wrapper;
026: import org.apache.catalina.util.StringManager;
027: import org.apache.coyote.ActionCode;
028: import org.apache.coyote.Adapter;
029: import org.apache.juli.logging.Log;
030: import org.apache.juli.logging.LogFactory;
031: import org.apache.tomcat.util.buf.B2CConverter;
032: import org.apache.tomcat.util.buf.ByteChunk;
033: import org.apache.tomcat.util.buf.CharChunk;
034: import org.apache.tomcat.util.buf.MessageBytes;
035: import org.apache.tomcat.util.http.Cookies;
036: import org.apache.tomcat.util.http.ServerCookie;
037: import org.apache.tomcat.util.net.SocketStatus;
038:
039: /**
040: * Implementation of a request processor which delegates the processing to a
041: * Coyote processor.
042: *
043: * @author Craig R. McClanahan
044: * @author Remy Maucherat
045: * @version $Revision: 555304 $ $Date: 2007-07-11 17:28:52 +0200 (mer., 11 juil. 2007) $
046: */
047:
048: public class CoyoteAdapter implements Adapter {
049: private static Log log = LogFactory.getLog(CoyoteAdapter.class);
050:
051: // -------------------------------------------------------------- Constants
052:
053: public static final int ADAPTER_NOTES = 1;
054:
055: protected static final boolean ALLOW_BACKSLASH = Boolean
056: .valueOf(
057: System
058: .getProperty(
059: "org.apache.catalina.connector.CoyoteAdapter.ALLOW_BACKSLASH",
060: "false")).booleanValue();
061:
062: // ----------------------------------------------------------- Constructors
063:
064: /**
065: * Construct a new CoyoteProcessor associated with the specified connector.
066: *
067: * @param connector CoyoteConnector that owns this processor
068: */
069: public CoyoteAdapter(Connector connector) {
070:
071: super ();
072: this .connector = connector;
073:
074: }
075:
076: // ----------------------------------------------------- Instance Variables
077:
078: /**
079: * The CoyoteConnector with which this processor is associated.
080: */
081: private Connector connector = null;
082:
083: /**
084: * The match string for identifying a session ID parameter.
085: */
086: private static final String match = ";"
087: + Globals.SESSION_PARAMETER_NAME + "=";
088:
089: /**
090: * The string manager for this package.
091: */
092: protected StringManager sm = StringManager
093: .getManager(Constants.Package);
094:
095: // -------------------------------------------------------- Adapter Methods
096:
097: /**
098: * Event method.
099: *
100: * @return false to indicate an error, expected or not
101: */
102: public boolean event(org.apache.coyote.Request req,
103: org.apache.coyote.Response res, SocketStatus status) {
104:
105: Request request = (Request) req.getNote(ADAPTER_NOTES);
106: Response response = (Response) res.getNote(ADAPTER_NOTES);
107:
108: if (request.getWrapper() != null) {
109:
110: boolean error = false;
111: boolean read = false;
112: try {
113: if (status == SocketStatus.OPEN) {
114: if (response.isClosed()) {
115: // The event has been closed asynchronously, so call end instead of
116: // read to cleanup the pipeline
117: request.getEvent().setEventType(
118: CometEvent.EventType.END);
119: request.getEvent().setEventSubType(null);
120: } else {
121: try {
122: // Fill the read buffer of the servlet layer
123: if (request.read()) {
124: read = true;
125: }
126: } catch (IOException e) {
127: error = true;
128: }
129: if (read) {
130: request.getEvent().setEventType(
131: CometEvent.EventType.READ);
132: request.getEvent().setEventSubType(null);
133: } else if (error) {
134: request.getEvent().setEventType(
135: CometEvent.EventType.ERROR);
136: request
137: .getEvent()
138: .setEventSubType(
139: CometEvent.EventSubType.CLIENT_DISCONNECT);
140: } else {
141: request.getEvent().setEventType(
142: CometEvent.EventType.END);
143: request.getEvent().setEventSubType(null);
144: }
145: }
146: } else if (status == SocketStatus.DISCONNECT) {
147: request.getEvent().setEventType(
148: CometEvent.EventType.ERROR);
149: request.getEvent().setEventSubType(
150: CometEvent.EventSubType.CLIENT_DISCONNECT);
151: error = true;
152: } else if (status == SocketStatus.ERROR) {
153: request.getEvent().setEventType(
154: CometEvent.EventType.ERROR);
155: request.getEvent().setEventSubType(
156: CometEvent.EventSubType.IOEXCEPTION);
157: error = true;
158: } else if (status == SocketStatus.STOP) {
159: request.getEvent().setEventType(
160: CometEvent.EventType.END);
161: request.getEvent().setEventSubType(
162: CometEvent.EventSubType.SERVER_SHUTDOWN);
163: } else if (status == SocketStatus.TIMEOUT) {
164: if (response.isClosed()) {
165: // The event has been closed asynchronously, so call end instead of
166: // read to cleanup the pipeline
167: request.getEvent().setEventType(
168: CometEvent.EventType.END);
169: request.getEvent().setEventSubType(null);
170: } else {
171: request.getEvent().setEventType(
172: CometEvent.EventType.ERROR);
173: request.getEvent().setEventSubType(
174: CometEvent.EventSubType.TIMEOUT);
175: }
176: }
177:
178: req.getRequestProcessor().setWorkerThreadName(
179: Thread.currentThread().getName());
180:
181: // Calling the container
182: connector.getContainer().getPipeline().getFirst()
183: .event(request, response, request.getEvent());
184:
185: if (!error
186: && !response.isClosed()
187: && (request
188: .getAttribute(Globals.EXCEPTION_ATTR) != null)) {
189: // An unexpected exception occurred while processing the event, so
190: // error should be called
191: request.getEvent().setEventType(
192: CometEvent.EventType.ERROR);
193: request.getEvent().setEventSubType(null);
194: error = true;
195: connector.getContainer().getPipeline().getFirst()
196: .event(request, response,
197: request.getEvent());
198: }
199: if (response.isClosed() || !request.isComet()) {
200: res.action(ActionCode.ACTION_COMET_END, null);
201: } else if (!error && read && request.getAvailable()) {
202: // If this was a read and not all bytes have been read, or if no data
203: // was read from the connector, then it is an error
204: request.getEvent().setEventType(
205: CometEvent.EventType.ERROR);
206: request.getEvent().setEventSubType(
207: CometEvent.EventSubType.IOEXCEPTION);
208: error = true;
209: connector.getContainer().getPipeline().getFirst()
210: .event(request, response,
211: request.getEvent());
212: }
213: return (!error);
214: } catch (Throwable t) {
215: if (!(t instanceof IOException)) {
216: log.error(sm.getString("coyoteAdapter.service"), t);
217: }
218: error = true;
219: return false;
220: } finally {
221: req.getRequestProcessor().setWorkerThreadName(null);
222: // Recycle the wrapper request and response
223: if (error || response.isClosed() || !request.isComet()) {
224: request.recycle();
225: request.setFilterChain(null);
226: response.recycle();
227: }
228: }
229:
230: } else {
231: return false;
232: }
233: }
234:
235: /**
236: * Service method.
237: */
238: public void service(org.apache.coyote.Request req,
239: org.apache.coyote.Response res) throws Exception {
240:
241: Request request = (Request) req.getNote(ADAPTER_NOTES);
242: Response response = (Response) res.getNote(ADAPTER_NOTES);
243:
244: if (request == null) {
245:
246: // Create objects
247: request = (Request) connector.createRequest();
248: request.setCoyoteRequest(req);
249: response = (Response) connector.createResponse();
250: response.setCoyoteResponse(res);
251:
252: // Link objects
253: request.setResponse(response);
254: response.setRequest(request);
255:
256: // Set as notes
257: req.setNote(ADAPTER_NOTES, request);
258: res.setNote(ADAPTER_NOTES, response);
259:
260: // Set query string encoding
261: req.getParameters().setQueryStringEncoding(
262: connector.getURIEncoding());
263:
264: }
265:
266: if (connector.getXpoweredBy()) {
267: response.addHeader("X-Powered-By", "Servlet/2.5");
268: }
269:
270: boolean comet = false;
271:
272: try {
273:
274: // Parse and set Catalina and configuration specific
275: // request parameters
276: req.getRequestProcessor().setWorkerThreadName(
277: Thread.currentThread().getName());
278: if (postParseRequest(req, request, res, response)) {
279: // Calling the container
280: connector.getContainer().getPipeline().getFirst()
281: .invoke(request, response);
282:
283: if (request.isComet()) {
284: if (!response.isClosed() && !response.isError()) {
285: if (request.getAvailable()) {
286: // Invoke a read event right away if there are available bytes
287: if (event(req, res, SocketStatus.OPEN)) {
288: comet = true;
289: res.action(
290: ActionCode.ACTION_COMET_BEGIN,
291: null);
292: }
293: } else {
294: comet = true;
295: res.action(ActionCode.ACTION_COMET_BEGIN,
296: null);
297: }
298: } else {
299: // Clear the filter chain, as otherwise it will not be reset elsewhere
300: // since this is a Comet request
301: request.setFilterChain(null);
302: }
303: }
304:
305: }
306:
307: if (!comet) {
308: response.finishResponse();
309: req.action(ActionCode.ACTION_POST_REQUEST, null);
310: }
311:
312: } catch (IOException e) {
313: ;
314: } catch (Throwable t) {
315: log.error(sm.getString("coyoteAdapter.service"), t);
316: } finally {
317: req.getRequestProcessor().setWorkerThreadName(null);
318: // Recycle the wrapper request and response
319: if (!comet) {
320: request.recycle();
321: response.recycle();
322: } else {
323: // Clear converters so that the minimum amount of memory
324: // is used by this processor
325: request.clearEncoders();
326: response.clearEncoders();
327: }
328: }
329:
330: }
331:
332: // ------------------------------------------------------ Protected Methods
333:
334: /**
335: * Parse additional request parameters.
336: */
337: protected boolean postParseRequest(org.apache.coyote.Request req,
338: Request request, org.apache.coyote.Response res,
339: Response response) throws Exception {
340:
341: // XXX the processor needs to set a correct scheme and port prior to this point,
342: // in ajp13 protocols dont make sense to get the port from the connector..
343: // XXX the processor may have set a correct scheme and port prior to this point,
344: // in ajp13 protocols dont make sense to get the port from the connector...
345: // otherwise, use connector configuration
346: if (!req.scheme().isNull()) {
347: // use processor specified scheme to determine secure state
348: request.setSecure(req.scheme().equals("https"));
349: } else {
350: // use connector scheme and secure configuration, (defaults to
351: // "http" and false respectively)
352: req.scheme().setString(connector.getScheme());
353: request.setSecure(connector.getSecure());
354: }
355:
356: // FIXME: the code below doesnt belongs to here,
357: // this is only have sense
358: // in Http11, not in ajp13..
359: // At this point the Host header has been processed.
360: // Override if the proxyPort/proxyHost are set
361: String proxyName = connector.getProxyName();
362: int proxyPort = connector.getProxyPort();
363: if (proxyPort != 0) {
364: req.setServerPort(proxyPort);
365: }
366: if (proxyName != null) {
367: req.serverName().setString(proxyName);
368: }
369:
370: // Parse session Id
371: parseSessionId(req, request);
372:
373: // URI decoding
374: MessageBytes decodedURI = req.decodedURI();
375: decodedURI.duplicate(req.requestURI());
376:
377: if (decodedURI.getType() == MessageBytes.T_BYTES) {
378: // Remove any path parameters
379: ByteChunk uriBB = decodedURI.getByteChunk();
380: int semicolon = uriBB.indexOf(';', 0);
381: if (semicolon > 0) {
382: decodedURI.setBytes(uriBB.getBuffer(),
383: uriBB.getStart(), semicolon);
384: }
385: // %xx decoding of the URL
386: try {
387: req.getURLDecoder().convert(decodedURI, false);
388: } catch (IOException ioe) {
389: res.setStatus(400);
390: res.setMessage("Invalid URI: " + ioe.getMessage());
391: return false;
392: }
393: // Normalization
394: if (!normalize(req.decodedURI())) {
395: res.setStatus(400);
396: res.setMessage("Invalid URI");
397: return false;
398: }
399: // Character decoding
400: convertURI(decodedURI, request);
401: } else {
402: // The URL is chars or String, and has been sent using an in-memory
403: // protocol handler, we have to assume the URL has been properly
404: // decoded already
405: decodedURI.toChars();
406: // Remove any path parameters
407: CharChunk uriCC = decodedURI.getCharChunk();
408: int semicolon = uriCC.indexOf(';');
409: if (semicolon > 0) {
410: decodedURI.setChars(uriCC.getBuffer(),
411: uriCC.getStart(), semicolon);
412: }
413: }
414:
415: // Set the remote principal
416: String principal = req.getRemoteUser().toString();
417: if (principal != null) {
418: request.setUserPrincipal(new CoyotePrincipal(principal));
419: }
420:
421: // Set the authorization type
422: String authtype = req.getAuthType().toString();
423: if (authtype != null) {
424: request.setAuthType(authtype);
425: }
426:
427: // Request mapping.
428: MessageBytes serverName;
429: if (connector.getUseIPVHosts()) {
430: serverName = req.localName();
431: if (serverName.isNull()) {
432: // well, they did ask for it
433: res.action(ActionCode.ACTION_REQ_LOCAL_NAME_ATTRIBUTE,
434: null);
435: }
436: } else {
437: serverName = req.serverName();
438: }
439: connector.getMapper().map(serverName, decodedURI,
440: request.getMappingData());
441: request.setContext((Context) request.getMappingData().context);
442: request.setWrapper((Wrapper) request.getMappingData().wrapper);
443:
444: // Filter trace method
445: if (!connector.getAllowTrace()
446: && req.method().equalsIgnoreCase("TRACE")) {
447: Wrapper wrapper = request.getWrapper();
448: String header = null;
449: if (wrapper != null) {
450: String[] methods = wrapper.getServletMethods();
451: if (methods != null) {
452: for (int i = 0; i < methods.length; i++) {
453: if ("TRACE".equals(methods[i])) {
454: continue;
455: }
456: if (header == null) {
457: header = methods[i];
458: } else {
459: header += ", " + methods[i];
460: }
461: }
462: }
463: }
464: res.setStatus(405);
465: res.addHeader("Allow", header);
466: res.setMessage("TRACE method is not allowed");
467: return false;
468: }
469:
470: // Possible redirect
471: MessageBytes redirectPathMB = request.getMappingData().redirectPath;
472: if (!redirectPathMB.isNull()) {
473: String redirectPath = redirectPathMB.toString();
474: String query = request.getQueryString();
475: if (request.isRequestedSessionIdFromURL()) {
476: // This is not optimal, but as this is not very common, it
477: // shouldn't matter
478: redirectPath = redirectPath + ";"
479: + Globals.SESSION_PARAMETER_NAME + "="
480: + request.getRequestedSessionId();
481: }
482: if (query != null) {
483: // This is not optimal, but as this is not very common, it
484: // shouldn't matter
485: redirectPath = redirectPath + "?" + query;
486: }
487: response.sendRedirect(redirectPath);
488: return false;
489: }
490:
491: // Parse session Id
492: parseSessionCookiesId(req, request);
493:
494: return true;
495: }
496:
497: /**
498: * Parse session id in URL.
499: */
500: protected void parseSessionId(org.apache.coyote.Request req,
501: Request request) {
502:
503: ByteChunk uriBC = req.requestURI().getByteChunk();
504: int semicolon = uriBC.indexOf(match, 0, match.length(), 0);
505:
506: if (semicolon > 0) {
507:
508: // Parse session ID, and extract it from the decoded request URI
509: int start = uriBC.getStart();
510: int end = uriBC.getEnd();
511:
512: int sessionIdStart = semicolon + match.length();
513: int semicolon2 = uriBC.indexOf(';', sessionIdStart);
514: if (semicolon2 >= 0) {
515: request.setRequestedSessionId(new String(uriBC
516: .getBuffer(), start + sessionIdStart,
517: semicolon2 - sessionIdStart));
518: // Extract session ID from request URI
519: byte[] buf = uriBC.getBuffer();
520: for (int i = 0; i < end - start - semicolon2; i++) {
521: buf[start + semicolon + i] = buf[start + i
522: + semicolon2];
523: }
524: uriBC.setBytes(buf, start, end - start - semicolon2
525: + semicolon);
526: } else {
527: request.setRequestedSessionId(new String(uriBC
528: .getBuffer(), start + sessionIdStart,
529: (end - start) - sessionIdStart));
530: uriBC.setEnd(start + semicolon);
531: }
532: request.setRequestedSessionURL(true);
533:
534: } else {
535: request.setRequestedSessionId(null);
536: request.setRequestedSessionURL(false);
537: }
538:
539: }
540:
541: /**
542: * Parse session id in URL.
543: */
544: protected void parseSessionCookiesId(org.apache.coyote.Request req,
545: Request request) {
546:
547: // Parse session id from cookies
548: Cookies serverCookies = req.getCookies();
549: int count = serverCookies.getCookieCount();
550: if (count <= 0)
551: return;
552:
553: for (int i = 0; i < count; i++) {
554: ServerCookie scookie = serverCookies.getCookie(i);
555: if (scookie.getName().equals(Globals.SESSION_COOKIE_NAME)) {
556: // Override anything requested in the URL
557: if (!request.isRequestedSessionIdFromCookie()) {
558: // Accept only the first session id cookie
559: convertMB(scookie.getValue());
560: request.setRequestedSessionId(scookie.getValue()
561: .toString());
562: request.setRequestedSessionCookie(true);
563: request.setRequestedSessionURL(false);
564: if (log.isDebugEnabled())
565: log.debug(" Requested cookie session id is "
566: + request.getRequestedSessionId());
567: } else {
568: if (!request.isRequestedSessionIdValid()) {
569: // Replace the session id until one is valid
570: convertMB(scookie.getValue());
571: request.setRequestedSessionId(scookie
572: .getValue().toString());
573: }
574: }
575: }
576: }
577:
578: }
579:
580: /**
581: * Character conversion of the URI.
582: */
583: protected void convertURI(MessageBytes uri, Request request)
584: throws Exception {
585:
586: ByteChunk bc = uri.getByteChunk();
587: int length = bc.getLength();
588: CharChunk cc = uri.getCharChunk();
589: cc.allocate(length, -1);
590:
591: String enc = connector.getURIEncoding();
592: if (enc != null) {
593: B2CConverter conv = request.getURIConverter();
594: try {
595: if (conv == null) {
596: conv = new B2CConverter(enc);
597: request.setURIConverter(conv);
598: } else {
599: conv.recycle();
600: }
601: } catch (IOException e) {
602: // Ignore
603: log.error("Invalid URI encoding; using HTTP default");
604: connector.setURIEncoding(null);
605: }
606: if (conv != null) {
607: try {
608: conv.convert(bc, cc);
609: uri.setChars(cc.getBuffer(), cc.getStart(), cc
610: .getLength());
611: return;
612: } catch (IOException e) {
613: log
614: .error("Invalid URI character encoding; trying ascii");
615: cc.recycle();
616: }
617: }
618: }
619:
620: // Default encoding: fast conversion
621: byte[] bbuf = bc.getBuffer();
622: char[] cbuf = cc.getBuffer();
623: int start = bc.getStart();
624: for (int i = 0; i < length; i++) {
625: cbuf[i] = (char) (bbuf[i + start] & 0xff);
626: }
627: uri.setChars(cbuf, 0, length);
628:
629: }
630:
631: /**
632: * Character conversion of the a US-ASCII MessageBytes.
633: */
634: protected void convertMB(MessageBytes mb) {
635:
636: // This is of course only meaningful for bytes
637: if (mb.getType() != MessageBytes.T_BYTES)
638: return;
639:
640: ByteChunk bc = mb.getByteChunk();
641: CharChunk cc = mb.getCharChunk();
642: int length = bc.getLength();
643: cc.allocate(length, -1);
644:
645: // Default encoding: fast conversion
646: byte[] bbuf = bc.getBuffer();
647: char[] cbuf = cc.getBuffer();
648: int start = bc.getStart();
649: for (int i = 0; i < length; i++) {
650: cbuf[i] = (char) (bbuf[i + start] & 0xff);
651: }
652: mb.setChars(cbuf, 0, length);
653:
654: }
655:
656: /**
657: * Normalize URI.
658: * <p>
659: * This method normalizes "\", "//", "/./" and "/../". This method will
660: * return false when trying to go above the root, or if the URI contains
661: * a null byte.
662: *
663: * @param uriMB URI to be normalized
664: */
665: public static boolean normalize(MessageBytes uriMB) {
666:
667: ByteChunk uriBC = uriMB.getByteChunk();
668: byte[] b = uriBC.getBytes();
669: int start = uriBC.getStart();
670: int end = uriBC.getEnd();
671:
672: // URL * is acceptable
673: if ((end - start == 1) && b[start] == (byte) '*')
674: return true;
675:
676: int pos = 0;
677: int index = 0;
678:
679: // Replace '\' with '/'
680: // Check for null byte
681: for (pos = start; pos < end; pos++) {
682: if (b[pos] == (byte) '\\') {
683: if (ALLOW_BACKSLASH) {
684: b[pos] = (byte) '/';
685: } else {
686: return false;
687: }
688: }
689: if (b[pos] == (byte) 0) {
690: return false;
691: }
692: }
693:
694: // The URL must start with '/'
695: if (b[start] != (byte) '/') {
696: return false;
697: }
698:
699: // Replace "//" with "/"
700: for (pos = start; pos < (end - 1); pos++) {
701: if (b[pos] == (byte) '/') {
702: while ((pos + 1 < end) && (b[pos + 1] == (byte) '/')) {
703: copyBytes(b, pos, pos + 1, end - pos - 1);
704: end--;
705: }
706: }
707: }
708:
709: // If the URI ends with "/." or "/..", then we append an extra "/"
710: // Note: It is possible to extend the URI by 1 without any side effect
711: // as the next character is a non-significant WS.
712: if (((end - start) >= 2) && (b[end - 1] == (byte) '.')) {
713: if ((b[end - 2] == (byte) '/')
714: || ((b[end - 2] == (byte) '.') && (b[end - 3] == (byte) '/'))) {
715: b[end] = (byte) '/';
716: end++;
717: }
718: }
719:
720: uriBC.setEnd(end);
721:
722: index = 0;
723:
724: // Resolve occurrences of "/./" in the normalized path
725: while (true) {
726: index = uriBC.indexOf("/./", 0, 3, index);
727: if (index < 0)
728: break;
729: copyBytes(b, start + index, start + index + 2, end - start
730: - index - 2);
731: end = end - 2;
732: uriBC.setEnd(end);
733: }
734:
735: index = 0;
736:
737: // Resolve occurrences of "/../" in the normalized path
738: while (true) {
739: index = uriBC.indexOf("/../", 0, 4, index);
740: if (index < 0)
741: break;
742: // Prevent from going outside our context
743: if (index == 0)
744: return false;
745: int index2 = -1;
746: for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos--) {
747: if (b[pos] == (byte) '/') {
748: index2 = pos;
749: }
750: }
751: copyBytes(b, start + index2, start + index + 3, end - start
752: - index - 3);
753: end = end + index2 - index - 3;
754: uriBC.setEnd(end);
755: index = index2;
756: }
757:
758: uriBC.setBytes(b, start, end);
759:
760: return true;
761:
762: }
763:
764: // ------------------------------------------------------ Protected Methods
765:
766: /**
767: * Copy an array of bytes to a different position. Used during
768: * normalization.
769: */
770: protected static void copyBytes(byte[] b, int dest, int src, int len) {
771: for (int pos = 0; pos < len; pos++) {
772: b[pos + dest] = b[pos + src];
773: }
774: }
775:
776: }
|