KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > catalina > connector > CoyoteAdapter


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */

17
18
19 package org.apache.catalina.connector;
20
21 import java.io.IOException JavaDoc;
22
23 import org.apache.catalina.CometEvent;
24 import org.apache.catalina.Context;
25 import org.apache.catalina.Globals;
26 import org.apache.catalina.Wrapper;
27 import org.apache.catalina.util.StringManager;
28 import org.apache.commons.logging.Log;
29 import org.apache.commons.logging.LogFactory;
30 import org.apache.coyote.ActionCode;
31 import org.apache.coyote.Adapter;
32 import org.apache.tomcat.util.buf.B2CConverter;
33 import org.apache.tomcat.util.buf.ByteChunk;
34 import org.apache.tomcat.util.buf.CharChunk;
35 import org.apache.tomcat.util.buf.MessageBytes;
36 import org.apache.tomcat.util.http.Cookies;
37 import org.apache.tomcat.util.http.ServerCookie;
38 import org.apache.tomcat.util.net.SocketStatus;
39
40
41 /**
42  * Implementation of a request processor which delegates the processing to a
43  * Coyote processor.
44  *
45  * @author Craig R. McClanahan
46  * @author Remy Maucherat
47  * @version $Revision: 467222 $ $Date: 2006-10-24 05:17:11 +0200 (mar., 24 oct. 2006) $
48  */

49
50 public class CoyoteAdapter
51     implements Adapter
52  {
53     private static Log log = LogFactory.getLog(CoyoteAdapter.class);
54
55     // -------------------------------------------------------------- Constants
56

57
58     public static final int ADAPTER_NOTES = 1;
59
60
61     // ----------------------------------------------------------- Constructors
62

63
64     /**
65      * Construct a new CoyoteProcessor associated with the specified connector.
66      *
67      * @param connector CoyoteConnector that owns this processor
68      */

69     public CoyoteAdapter(Connector connector) {
70
71         super();
72         this.connector = connector;
73
74     }
75
76
77     // ----------------------------------------------------- Instance Variables
78

79
80     /**
81      * The CoyoteConnector with which this processor is associated.
82      */

83     private Connector connector = null;
84
85
86     /**
87      * The match string for identifying a session ID parameter.
88      */

89     private static final String JavaDoc match =
90         ";" + Globals.SESSION_PARAMETER_NAME + "=";
91
92
93     /**
94      * The string manager for this package.
95      */

96     protected StringManager sm =
97         StringManager.getManager(Constants.Package);
98
99
100     // -------------------------------------------------------- Adapter Methods
101

102     
103     /**
104      * Event method.
105      *
106      * @return false to indicate an error, expected or not
107      */

108     public boolean event(org.apache.coyote.Request req,
109             org.apache.coyote.Response res, SocketStatus status) {
110
111         Request request = (Request) req.getNote(ADAPTER_NOTES);
112         Response response = (Response) res.getNote(ADAPTER_NOTES);
113
114         if (request.getWrapper() != null) {
115             
116             boolean error = false;
117             try {
118                 if (status == SocketStatus.OPEN) {
119                     request.getEvent().setEventType(CometEvent.EventType.READ);
120                     request.getEvent().setEventSubType(null);
121                 } else if (status == SocketStatus.DISCONNECT) {
122                     request.getEvent().setEventType(CometEvent.EventType.ERROR);
123                     request.getEvent().setEventSubType(CometEvent.EventSubType.CLIENT_DISCONNECT);
124                     error = true;
125                 } else if (status == SocketStatus.ERROR) {
126                     request.getEvent().setEventType(CometEvent.EventType.ERROR);
127                     request.getEvent().setEventSubType(CometEvent.EventSubType.IOEXCEPTION);
128                     error = true;
129                 } else if (status == SocketStatus.STOP) {
130                     request.getEvent().setEventType(CometEvent.EventType.END);
131                     request.getEvent().setEventSubType(CometEvent.EventSubType.SERVER_SHUTDOWN);
132                 } else if (status == SocketStatus.TIMEOUT) {
133                     request.getEvent().setEventType(CometEvent.EventType.ERROR);
134                     request.getEvent().setEventSubType(CometEvent.EventSubType.TIMEOUT);
135                 }
136                 
137                 // Calling the container
138
connector.getContainer().getPipeline().getFirst().event(request, response, request.getEvent());
139
140                 if (response.isClosed() || !request.isComet()) {
141                     res.action(ActionCode.ACTION_COMET_END, null);
142                 }
143                 return (!error);
144             } catch (Throwable JavaDoc t) {
145                 if (!(t instanceof IOException JavaDoc)) {
146                     log.error(sm.getString("coyoteAdapter.service"), t);
147                 }
148                 error = true;
149                 // FIXME: Since there's likely some structures kept in the servlet or elsewhere,
150
// a cleanup event of some sort could be needed ?
151
return false;
152             } finally {
153                 // Recycle the wrapper request and response
154
if (error || response.isClosed() || !request.isComet()) {
155                     request.recycle();
156                     request.setFilterChain(null);
157                     response.recycle();
158                 }
159             }
160             
161         } else {
162             return false;
163         }
164     }
165     
166
167     /**
168      * Service method.
169      */

170     public void service(org.apache.coyote.Request req,
171                         org.apache.coyote.Response res)
172         throws Exception JavaDoc {
173
174         Request request = (Request) req.getNote(ADAPTER_NOTES);
175         Response response = (Response) res.getNote(ADAPTER_NOTES);
176
177         if (request == null) {
178
179             // Create objects
180
request = (Request) connector.createRequest();
181             request.setCoyoteRequest(req);
182             response = (Response) connector.createResponse();
183             response.setCoyoteResponse(res);
184
185             // Link objects
186
request.setResponse(response);
187             response.setRequest(request);
188
189             // Set as notes
190
req.setNote(ADAPTER_NOTES, request);
191             res.setNote(ADAPTER_NOTES, response);
192
193             // Set query string encoding
194
req.getParameters().setQueryStringEncoding
195                 (connector.getURIEncoding());
196
197         }
198
199         if (connector.getXpoweredBy()) {
200             response.addHeader("X-Powered-By", "Servlet/2.5");
201         }
202
203         boolean comet = false;
204         
205         try {
206
207             // Parse and set Catalina and configuration specific
208
// request parameters
209
if (postParseRequest(req, request, res, response)) {
210
211                 // Calling the container
212
connector.getContainer().getPipeline().getFirst().invoke(request, response);
213
214                 if (request.isComet()) {
215                     if (!response.isClosed()) {
216                         comet = true;
217                         res.action(ActionCode.ACTION_COMET_BEGIN, null);
218                     } else {
219                         // Clear the filter chain, as otherwise it will not be reset elsewhere
220
// since this is a Comet request
221
request.setFilterChain(null);
222                     }
223                 }
224
225             }
226
227             if (!comet) {
228                 response.finishResponse();
229                 req.action(ActionCode.ACTION_POST_REQUEST , null);
230             }
231
232         } catch (IOException JavaDoc e) {
233             ;
234         } catch (Throwable JavaDoc t) {
235             log.error(sm.getString("coyoteAdapter.service"), t);
236         } finally {
237             // Recycle the wrapper request and response
238
if (!comet) {
239                 request.recycle();
240                 response.recycle();
241             } else {
242                 // Clear converters so that the minimum amount of memory
243
// is used by this processor
244
request.clearEncoders();
245                 response.clearEncoders();
246             }
247         }
248
249     }
250
251
252     // ------------------------------------------------------ Protected Methods
253

254
255     /**
256      * Parse additional request parameters.
257      */

258     protected boolean postParseRequest(org.apache.coyote.Request req,
259                                        Request request,
260                                    org.apache.coyote.Response res,
261                                        Response response)
262             throws Exception JavaDoc {
263
264         // XXX the processor needs to set a correct scheme and port prior to this point,
265
// in ajp13 protocols dont make sense to get the port from the connector..
266
// XXX the processor may have set a correct scheme and port prior to this point,
267
// in ajp13 protocols dont make sense to get the port from the connector...
268
// otherwise, use connector configuration
269
if (! req.scheme().isNull()) {
270             // use processor specified scheme to determine secure state
271
request.setSecure(req.scheme().equals("https"));
272         } else {
273             // use connector scheme and secure configuration, (defaults to
274
// "http" and false respectively)
275
req.scheme().setString(connector.getScheme());
276             request.setSecure(connector.getSecure());
277         }
278
279         // FIXME: the code below doesnt belongs to here,
280
// this is only have sense
281
// in Http11, not in ajp13..
282
// At this point the Host header has been processed.
283
// Override if the proxyPort/proxyHost are set
284
String JavaDoc proxyName = connector.getProxyName();
285         int proxyPort = connector.getProxyPort();
286         if (proxyPort != 0) {
287             req.setServerPort(proxyPort);
288         }
289         if (proxyName != null) {
290             req.serverName().setString(proxyName);
291         }
292
293         // Parse session Id
294
parseSessionId(req, request);
295
296         // URI decoding
297
MessageBytes decodedURI = req.decodedURI();
298         decodedURI.duplicate(req.requestURI());
299
300         if (decodedURI.getType() == MessageBytes.T_BYTES) {
301             // Remove any path parameters
302
ByteChunk uriBB = decodedURI.getByteChunk();
303             int semicolon = uriBB.indexOf(';', 0);
304             if (semicolon > 0) {
305                 decodedURI.setBytes
306                     (uriBB.getBuffer(), uriBB.getStart(), semicolon);
307             }
308             // %xx decoding of the URL
309
try {
310                 req.getURLDecoder().convert(decodedURI, false);
311             } catch (IOException JavaDoc ioe) {
312                 res.setStatus(400);
313                 res.setMessage("Invalid URI");
314                 throw ioe;
315             }
316             // Normalization
317
if (!normalize(req.decodedURI())) {
318                 res.setStatus(400);
319                 res.setMessage("Invalid URI");
320                 return false;
321             }
322             // Character decoding
323
convertURI(decodedURI, request);
324         } else {
325             // The URL is chars or String, and has been sent using an in-memory
326
// protocol handler, we have to assume the URL has been properly
327
// decoded already
328
decodedURI.toChars();
329             // Remove any path parameters
330
CharChunk uriCC = decodedURI.getCharChunk();
331             int semicolon = uriCC.indexOf(';');
332             if (semicolon > 0) {
333                 decodedURI.setChars
334                     (uriCC.getBuffer(), uriCC.getStart(), semicolon);
335             }
336         }
337
338         // Set the remote principal
339
String JavaDoc principal = req.getRemoteUser().toString();
340         if (principal != null) {
341             request.setUserPrincipal(new CoyotePrincipal(principal));
342         }
343
344         // Set the authorization type
345
String JavaDoc authtype = req.getAuthType().toString();
346         if (authtype != null) {
347             request.setAuthType(authtype);
348         }
349
350         // Request mapping.
351
MessageBytes serverName;
352         if (connector.getUseIPVHosts()) {
353             serverName = req.localName();
354             if (serverName.isNull()) {
355                 // well, they did ask for it
356
res.action(ActionCode.ACTION_REQ_LOCAL_NAME_ATTRIBUTE, null);
357             }
358         } else {
359             serverName = req.serverName();
360         }
361         connector.getMapper().map(serverName, decodedURI,
362                                   request.getMappingData());
363         request.setContext((Context) request.getMappingData().context);
364         request.setWrapper((Wrapper) request.getMappingData().wrapper);
365
366         // Filter trace method
367
if (!connector.getAllowTrace()
368                 && req.method().equalsIgnoreCase("TRACE")) {
369             Wrapper wrapper = request.getWrapper();
370             String JavaDoc header = null;
371             if (wrapper != null) {
372                 String JavaDoc[] methods = wrapper.getServletMethods();
373                 if (methods != null) {
374                     for (int i=0; i<methods.length; i++) {
375                         if ("TRACE".equals(methods[i])) {
376                             continue;
377                         }
378                         if (header == null) {
379                             header = methods[i];
380                         } else {
381                             header += ", " + methods[i];
382                         }
383                     }
384                 }
385             }
386             res.setStatus(405);
387             res.addHeader("Allow", header);
388             res.setMessage("TRACE method is not allowed");
389             return false;
390         }
391
392         // Possible redirect
393
MessageBytes redirectPathMB = request.getMappingData().redirectPath;
394         if (!redirectPathMB.isNull()) {
395             String JavaDoc redirectPath = redirectPathMB.toString();
396             String JavaDoc query = request.getQueryString();
397             if (request.isRequestedSessionIdFromURL()) {
398                 // This is not optimal, but as this is not very common, it
399
// shouldn't matter
400
redirectPath = redirectPath + ";jsessionid="
401                     + request.getRequestedSessionId();
402             }
403             if (query != null) {
404                 // This is not optimal, but as this is not very common, it
405
// shouldn't matter
406
redirectPath = redirectPath + "?" + query;
407             }
408             response.sendRedirect(redirectPath);
409             return false;
410         }
411
412         // Parse session Id
413
parseSessionCookiesId(req, request);
414
415         return true;
416     }
417
418
419     /**
420      * Parse session id in URL.
421      */

422     protected void parseSessionId(org.apache.coyote.Request req, Request request) {
423
424         ByteChunk uriBC = req.requestURI().getByteChunk();
425         int semicolon = uriBC.indexOf(match, 0, match.length(), 0);
426
427         if (semicolon > 0) {
428
429             // Parse session ID, and extract it from the decoded request URI
430
int start = uriBC.getStart();
431             int end = uriBC.getEnd();
432
433             int sessionIdStart = semicolon + match.length();
434             int semicolon2 = uriBC.indexOf(';', sessionIdStart);
435             if (semicolon2 >= 0) {
436                 request.setRequestedSessionId
437                     (new String JavaDoc(uriBC.getBuffer(), start + sessionIdStart,
438                             semicolon2 - sessionIdStart));
439                 // Extract session ID from request URI
440
byte[] buf = uriBC.getBuffer();
441                 for (int i = 0; i < end - start - semicolon2; i++) {
442                     buf[start + semicolon + i]
443                         = buf[start + i + semicolon2];
444                 }
445                 uriBC.setBytes(buf, start, end - start - semicolon2 + semicolon);
446             } else {
447                 request.setRequestedSessionId
448                     (new String JavaDoc(uriBC.getBuffer(), start + sessionIdStart,
449                             (end - start) - sessionIdStart));
450                 uriBC.setEnd(start + semicolon);
451             }
452             request.setRequestedSessionURL(true);
453
454         } else {
455             request.setRequestedSessionId(null);
456             request.setRequestedSessionURL(false);
457         }
458
459     }
460
461
462     /**
463      * Parse session id in URL.
464      */

465     protected void parseSessionCookiesId(org.apache.coyote.Request req, Request request) {
466
467         // Parse session id from cookies
468
Cookies serverCookies = req.getCookies();
469         int count = serverCookies.getCookieCount();
470         if (count <= 0)
471             return;
472
473         for (int i = 0; i < count; i++) {
474             ServerCookie scookie = serverCookies.getCookie(i);
475             if (scookie.getName().equals(Globals.SESSION_COOKIE_NAME)) {
476                 // Override anything requested in the URL
477
if (!request.isRequestedSessionIdFromCookie()) {
478                     // Accept only the first session id cookie
479
convertMB(scookie.getValue());
480                     request.setRequestedSessionId
481                         (scookie.getValue().toString());
482                     request.setRequestedSessionCookie(true);
483                     request.setRequestedSessionURL(false);
484                     if (log.isDebugEnabled())
485                         log.debug(" Requested cookie session id is " +
486                             request.getRequestedSessionId());
487                 } else {
488                     if (!request.isRequestedSessionIdValid()) {
489                         // Replace the session id until one is valid
490
convertMB(scookie.getValue());
491                         request.setRequestedSessionId
492                             (scookie.getValue().toString());
493                     }
494                 }
495             }
496         }
497
498     }
499
500
501     /**
502      * Character conversion of the URI.
503      */

504     protected void convertURI(MessageBytes uri, Request request)
505         throws Exception JavaDoc {
506
507         ByteChunk bc = uri.getByteChunk();
508         CharChunk cc = uri.getCharChunk();
509         cc.allocate(bc.getLength(), -1);
510
511         String JavaDoc enc = connector.getURIEncoding();
512         if (enc != null) {
513             B2CConverter conv = request.getURIConverter();
514             try {
515                 if (conv == null) {
516                     conv = new B2CConverter(enc);
517                     request.setURIConverter(conv);
518                 } else {
519                     conv.recycle();
520                 }
521             } catch (IOException JavaDoc e) {
522                 // Ignore
523
log.error("Invalid URI encoding; using HTTP default");
524                 connector.setURIEncoding(null);
525             }
526             if (conv != null) {
527                 try {
528                     conv.convert(bc, cc);
529                     uri.setChars(cc.getBuffer(), cc.getStart(),
530                                  cc.getLength());
531                     return;
532                 } catch (IOException JavaDoc e) {
533                     log.error("Invalid URI character encoding; trying ascii");
534                     cc.recycle();
535                 }
536             }
537         }
538
539         // Default encoding: fast conversion
540
byte[] bbuf = bc.getBuffer();
541         char[] cbuf = cc.getBuffer();
542         int start = bc.getStart();
543         for (int i = 0; i < bc.getLength(); i++) {
544             cbuf[i] = (char) (bbuf[i + start] & 0xff);
545         }
546         uri.setChars(cbuf, 0, bc.getLength());
547
548     }
549
550
551     /**
552      * Character conversion of the a US-ASCII MessageBytes.
553      */

554     protected void convertMB(MessageBytes mb) {
555
556         // This is of course only meaningful for bytes
557
if (mb.getType() != MessageBytes.T_BYTES)
558             return;
559         
560         ByteChunk bc = mb.getByteChunk();
561         CharChunk cc = mb.getCharChunk();
562         cc.allocate(bc.getLength(), -1);
563
564         // Default encoding: fast conversion
565
byte[] bbuf = bc.getBuffer();
566         char[] cbuf = cc.getBuffer();
567         int start = bc.getStart();
568         for (int i = 0; i < bc.getLength(); i++) {
569             cbuf[i] = (char) (bbuf[i + start] & 0xff);
570         }
571         mb.setChars(cbuf, 0, bc.getLength());
572
573     }
574
575
576     /**
577      * Normalize URI.
578      * <p>
579      * This method normalizes "\", "//", "/./" and "/../". This method will
580      * return false when trying to go above the root, or if the URI contains
581      * a null byte.
582      *
583      * @param uriMB URI to be normalized
584      */

585     public static boolean normalize(MessageBytes uriMB) {
586
587         ByteChunk uriBC = uriMB.getByteChunk();
588         byte[] b = uriBC.getBytes();
589         int start = uriBC.getStart();
590         int end = uriBC.getEnd();
591
592         // URL * is acceptable
593
if ((end - start == 1) && b[start] == (byte) '*')
594           return true;
595
596         int pos = 0;
597         int index = 0;
598
599         // Replace '\' with '/'
600
// Check for null byte
601
for (pos = start; pos < end; pos++) {
602             if (b[pos] == (byte) '\\')
603                 b[pos] = (byte) '/';
604             if (b[pos] == (byte) 0)
605                 return false;
606         }
607
608         // The URL must start with '/'
609
if (b[start] != (byte) '/') {
610             return false;
611         }
612
613         // Replace "//" with "/"
614
for (pos = start; pos < (end - 1); pos++) {
615             if (b[pos] == (byte) '/') {
616                 while ((pos + 1 < end) && (b[pos + 1] == (byte) '/')) {
617                     copyBytes(b, pos, pos + 1, end - pos - 1);
618                     end--;
619                 }
620             }
621         }
622
623         // If the URI ends with "/." or "/..", then we append an extra "/"
624
// Note: It is possible to extend the URI by 1 without any side effect
625
// as the next character is a non-significant WS.
626
if (((end - start) >= 2) && (b[end - 1] == (byte) '.')) {
627             if ((b[end - 2] == (byte) '/')
628                 || ((b[end - 2] == (byte) '.')
629                     && (b[end - 3] == (byte) '/'))) {
630                 b[end] = (byte) '/';
631                 end++;
632             }
633         }
634
635         uriBC.setEnd(end);
636
637         index = 0;
638
639         // Resolve occurrences of "/./" in the normalized path
640
while (true) {
641             index = uriBC.indexOf("/./", 0, 3, index);
642             if (index < 0)
643                 break;
644             copyBytes(b, start + index, start + index + 2,
645                       end - start - index - 2);
646             end = end - 2;
647             uriBC.setEnd(end);
648         }
649
650         index = 0;
651
652         // Resolve occurrences of "/../" in the normalized path
653
while (true) {
654             index = uriBC.indexOf("/../", 0, 4, index);
655             if (index < 0)
656                 break;
657             // Prevent from going outside our context
658
if (index == 0)
659                 return false;
660             int index2 = -1;
661             for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos --) {
662                 if (b[pos] == (byte) '/') {
663                     index2 = pos;
664                 }
665             }
666             copyBytes(b, start + index2, start + index + 3,
667                       end - start - index - 3);
668             end = end + index2 - index - 3;
669             uriBC.setEnd(end);
670             index = index2;
671         }
672
673         uriBC.setBytes(b, start, end);
674
675         return true;
676
677     }
678
679
680     // ------------------------------------------------------ Protected Methods
681

682
683     /**
684      * Copy an array of bytes to a different position. Used during
685      * normalization.
686      */

687     protected static void copyBytes(byte[] b, int dest, int src, int len) {
688         for (int pos = 0; pos < len; pos++) {
689             b[pos + dest] = b[pos + src];
690         }
691     }
692
693
694 }
695
Popular Tags