KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > coyote > tomcat4 > CoyoteAdapter


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

16
17 package org.apache.coyote.tomcat4;
18
19
20 import java.io.IOException JavaDoc;
21 import javax.servlet.http.Cookie JavaDoc;
22 import javax.servlet.http.HttpServletRequest JavaDoc;
23
24 import org.apache.tomcat.util.buf.B2CConverter;
25 import org.apache.tomcat.util.buf.ByteChunk;
26 import org.apache.tomcat.util.buf.CharChunk;
27 import org.apache.tomcat.util.buf.MessageBytes;
28 import org.apache.tomcat.util.http.Cookies;
29 import org.apache.tomcat.util.http.ServerCookie;
30
31 import org.apache.coyote.ActionCode;
32 import org.apache.coyote.Adapter;
33 import org.apache.coyote.Request;
34 import org.apache.coyote.Response;
35
36 import org.apache.catalina.Globals;
37 import org.apache.catalina.Logger;
38 import org.apache.catalina.util.StringManager;
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: 1.28 $ $Date: 2004/04/04 19:09:38 $
48  */

49
50 final class CoyoteAdapter
51     implements Adapter {
52
53
54     // -------------------------------------------------------------- Constants
55

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

62
63     /**
64      * Construct a new CoyoteProcessor associated with the specified connector.
65      *
66      * @param connector CoyoteConnector that owns this processor
67      * @param id Identifier of this CoyoteProcessor (unique per connector)
68      */

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

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

84     private CoyoteConnector connector = null;
85
86
87     /**
88      * The debugging detail level for this component.
89      */

90     private int debug = 0;
91
92
93     /**
94      * The match string for identifying a session ID parameter.
95      */

96     private static final String JavaDoc match =
97         ";" + Globals.SESSION_PARAMETER_NAME + "=";
98
99
100     /**
101      * The match string for identifying a session ID parameter.
102      */

103     private static final char[] SESSION_ID = match.toCharArray();
104
105
106     /**
107      * The string manager for this package.
108      */

109     protected StringManager sm =
110         StringManager.getManager(Constants.Package);
111
112
113     // -------------------------------------------------------- Adapter Methods
114

115
116     /**
117      * Service method.
118      */

119     public void service(Request JavaDoc req, Response res)
120         throws Exception JavaDoc {
121
122         CoyoteRequest request = (CoyoteRequest) req.getNote(ADAPTER_NOTES);
123         CoyoteResponse response = (CoyoteResponse) res.getNote(ADAPTER_NOTES);
124
125         if (request == null) {
126
127             // Create objects
128
request = (CoyoteRequest) connector.createRequest();
129             request.setCoyoteRequest(req);
130             response = (CoyoteResponse) connector.createResponse();
131             response.setCoyoteResponse(res);
132
133             // Link objects
134
request.setResponse(response);
135             response.setRequest(request);
136
137             // Set as notes
138
req.setNote(ADAPTER_NOTES, request);
139             res.setNote(ADAPTER_NOTES, response);
140
141             // Set query string encoding
142
req.getParameters().setQueryStringEncoding
143                 (connector.getURIEncoding());
144
145         }
146
147         try {
148             // Parse and set Catalina and configuration specific
149
// request parameters
150
postParseRequest(req, request, res, response);
151             // Calling the container
152
connector.getContainer().invoke(request, response);
153             response.finishResponse();
154
155             req.action( ActionCode.ACTION_POST_REQUEST , null);
156         } catch (IOException JavaDoc e) {
157             ;
158         } catch (Throwable JavaDoc t) {
159             log(sm.getString("coyoteAdapter.service"), t);
160         } finally {
161             // Recycle the wrapper request and response
162
request.recycle();
163             response.recycle();
164         }
165
166     }
167
168
169     // ------------------------------------------------------ Protected Methods
170

171
172     /**
173      * Parse additional request parameters.
174      */

175     protected void postParseRequest(Request JavaDoc req, CoyoteRequest request,
176                                     Response res, CoyoteResponse response)
177         throws Exception JavaDoc {
178         // XXX the processor needs to set a correct scheme and port prior to this point,
179
// in ajp13 protocols dont make sense to get the port from the connector..
180
// XXX the processor may have set a correct scheme and port prior to this point,
181
// in ajp13 protocols dont make sense to get the port from the connector...
182
// otherwise, use connector configuration
183
if (! req.scheme().isNull()) {
184             // use processor specified scheme to determine secure state
185
request.setSecure(req.scheme().equals("https"));
186         } else {
187             // use connector scheme and secure configuration, (defaults to
188
// "http" and false respectively)
189
req.scheme().setString(connector.getScheme());
190             request.setSecure(connector.getSecure());
191         }
192  
193         // Filter trace method
194
if (!connector.getAllowTrace()
195             && req.method().equalsIgnoreCase("TRACE")) {
196             res.setStatus(403);
197             res.setMessage("TRACE method is not allowed");
198             throw new IOException JavaDoc("TRACE method is not allowed");
199         }
200
201         request.setAuthorization
202             (req.getHeader(Constants.AUTHORIZATION_HEADER));
203         // FIXME: the code below doesnt belongs to here, this is only have sense
204
// in Http11, not in ajp13..
205
// At this point the Host header has been processed.
206
// Override if the proxyPort/proxyHost are set
207
String JavaDoc proxyName = connector.getProxyName();
208         int proxyPort = connector.getProxyPort();
209         if (proxyPort != 0) {
210             request.setServerPort(proxyPort);
211             req.setServerPort(proxyPort);
212         } else {
213             request.setServerPort(req.getServerPort());
214         }
215         if (proxyName != null) {
216             request.setServerName(proxyName);
217             req.serverName().setString(proxyName);
218         } else {
219             request.setServerName(req.serverName().toString());
220         }
221
222         // URI decoding
223
req.decodedURI().duplicate(req.requestURI());
224         try {
225           req.getURLDecoder().convert(req.decodedURI(), false);
226         } catch (IOException JavaDoc ioe) {
227             res.setStatus(400);
228             res.setMessage("Invalid URI");
229             throw ioe;
230         }
231
232         // Normalize decoded URI
233
if (!normalize(req.decodedURI())) {
234             res.setStatus(400);
235             res.setMessage("Invalid URI");
236             throw new IOException JavaDoc("Invalid URI");
237         }
238
239         // URI character decoding
240
convertURI(req.decodedURI(), request);
241
242         // Parse session Id
243
parseSessionId(req, request);
244
245         // Additional URI normalization and validation is needed for security
246
// reasons on Tomcat 4.0.x
247
if (connector.getUseURIValidationHack()) {
248             String JavaDoc uri = validate(request.getRequestURI());
249             if (uri == null) {
250                 res.setStatus(400);
251                 res.setMessage("Invalid URI");
252                 throw new IOException JavaDoc("Invalid URI");
253             } else {
254                 req.requestURI().setString(uri);
255                 // Redoing the URI decoding
256
req.decodedURI().duplicate(req.requestURI());
257                 req.getURLDecoder().convert(req.decodedURI(), true);
258                 convertURI(req.decodedURI(), request);
259             }
260         }
261
262         // Parse cookies
263
parseCookies(req, request);
264
265         // Set the SSL properties
266
if( request.isSecure() ) {
267             res.action(ActionCode.ACTION_REQ_SSL_ATTRIBUTE,
268                        request.getCoyoteRequest());
269             //Set up for getAttributeNames
270
request.getAttribute(Globals.CERTIFICATES_ATTR);
271             request.getAttribute(Globals.CIPHER_SUITE_ATTR);
272             request.getAttribute(Globals.KEY_SIZE_ATTR);
273         }
274
275         // Set the remote principal
276
String JavaDoc principal = req.getRemoteUser().toString();
277         if (principal != null) {
278             request.setUserPrincipal(new CoyotePrincipal(principal));
279         }
280
281         // Set the authorization type
282
String JavaDoc authtype = req.getAuthType().toString();
283         if (authtype != null) {
284             request.setAuthType(authtype);
285         }
286
287     }
288
289     /**
290      * Parse session id in URL.
291      * FIXME: Optimize this.
292      */

293     protected void parseSessionId(Request JavaDoc req, CoyoteRequest request) {
294
295         req.decodedURI().toChars();
296         CharChunk uriCC = req.decodedURI().getCharChunk();
297         int semicolon = uriCC.indexOf(match, 0, match.length(), 0);
298
299         if (semicolon > 0) {
300
301             // Parse session ID, and extract it from the decoded request URI
302
int start = uriCC.getStart();
303             int end = uriCC.getEnd();
304
305             int sessionIdStart = start + semicolon + match.length();
306             int semicolon2 = uriCC.indexOf(';', sessionIdStart);
307             if (semicolon2 >= 0) {
308                 request.setRequestedSessionId
309                     (new String JavaDoc(uriCC.getBuffer(), sessionIdStart,
310                                 semicolon2 - semicolon - match.length()));
311                 req.decodedURI().setString
312                     (new String JavaDoc(uriCC.getBuffer(), start, semicolon) +
313                             new String JavaDoc(uriCC.getBuffer(),
314                                         semicolon2,
315                                         end-semicolon2));
316             } else {
317                 request.setRequestedSessionId
318                     (new String JavaDoc(uriCC.getBuffer(), sessionIdStart,
319                                 end - sessionIdStart));
320                 req.decodedURI().setString
321                     (new String JavaDoc(uriCC.getBuffer(), start, semicolon));
322             }
323             request.setRequestedSessionURL(true);
324
325             // Extract session ID from request URI
326
String JavaDoc uri = req.requestURI().toString();
327             semicolon = uri.indexOf(match);
328
329             if (semicolon > 0) {
330                 String JavaDoc rest = uri.substring(semicolon + match.length());
331                 semicolon2 = rest.indexOf(';');
332                 if (semicolon2 >= 0) {
333                     rest = rest.substring(semicolon2);
334                 } else {
335                     rest = "";
336                 }
337                 req.requestURI().setString(uri.substring(0, semicolon) + rest);
338             }
339
340         } else {
341             request.setRequestedSessionId(null);
342             request.setRequestedSessionURL(false);
343         }
344
345     }
346
347
348     /**
349      * Parse cookies.
350      */

351     protected void parseCookies(Request JavaDoc req, CoyoteRequest request) {
352
353         Cookies serverCookies = req.getCookies();
354         int count = serverCookies.getCookieCount();
355         if (count <= 0)
356             return;
357
358         Cookie JavaDoc[] cookies = new Cookie JavaDoc[count];
359
360         int idx=0;
361         for (int i = 0; i < count; i++) {
362             ServerCookie scookie = serverCookies.getCookie(i);
363             if (scookie.getName().equals(Globals.SESSION_COOKIE_NAME)) {
364                 // Override anything requested in the URL
365
if (!request.isRequestedSessionIdFromCookie()) {
366                     // Accept only the first session id cookie
367
request.setRequestedSessionId
368                         (scookie.getValue().toString());
369                     request.setRequestedSessionCookie(true);
370                     request.setRequestedSessionURL(false);
371                     if (debug >= 1)
372                         log(" Requested cookie session id is " +
373                             ((HttpServletRequest JavaDoc) request.getRequest())
374                             .getRequestedSessionId());
375                 }
376             }
377             try {
378                 Cookie JavaDoc cookie = new Cookie JavaDoc(scookie.getName().toString(),
379                                            scookie.getValue().toString());
380                 cookie.setPath(scookie.getPath().toString());
381                 cookie.setVersion(scookie.getVersion());
382                 String JavaDoc domain = scookie.getDomain().toString();
383                 if (domain != null) {
384                     cookie.setDomain(scookie.getDomain().toString());
385                 }
386                 cookies[idx++] = cookie;
387             } catch(Exception JavaDoc ex) {
388                 log("Bad Cookie Name: " + scookie.getName() +
389                     " /Value: " + scookie.getValue(),ex);
390             }
391         }
392         if( idx < count ) {
393             Cookie JavaDoc [] ncookies = new Cookie JavaDoc[idx];
394             System.arraycopy(cookies, 0, ncookies, 0, idx);
395             cookies = ncookies;
396         }
397
398         request.setCookies(cookies);
399
400     }
401
402
403     /**
404      * Return a context-relative path, beginning with a "/", that represents
405      * the canonical version of the specified path after ".." and "." elements
406      * are resolved out. If the specified path attempts to go outside the
407      * boundaries of the current context (i.e. too many ".." path elements
408      * are present), return <code>null</code> instead.
409      * This code is not optimized, and is only needed for Tomcat 4.0.x.
410      *
411      * @param path Path to be validated
412      */

413     protected static String JavaDoc validate(String JavaDoc path) {
414
415         if (path == null)
416             return null;
417
418         // Create a place for the normalized path
419
String JavaDoc normalized = path;
420
421         // Normalize "/%7E" and "/%7e" at the beginning to "/~"
422
if (normalized.startsWith("/%7E") ||
423             normalized.startsWith("/%7e"))
424             normalized = "/~" + normalized.substring(4);
425
426         // Prevent encoding '%', '/', '.' and '\', which are special reserved
427
// characters
428
if ((normalized.indexOf("%25") >= 0)
429             || (normalized.indexOf("%2F") >= 0)
430             || (normalized.indexOf("%2E") >= 0)
431             || (normalized.indexOf("%5C") >= 0)
432             || (normalized.indexOf("%2f") >= 0)
433             || (normalized.indexOf("%2e") >= 0)
434             || (normalized.indexOf("%5c") >= 0)) {
435             return null;
436         }
437
438         if (normalized.equals("/."))
439             return "/";
440
441         // Normalize the slashes and add leading slash if necessary
442
if (normalized.indexOf('\\') >= 0)
443             normalized = normalized.replace('\\', '/');
444         if (!normalized.startsWith("/"))
445             normalized = "/" + normalized;
446
447         // Resolve occurrences of "//" in the normalized path
448
while (true) {
449             int index = normalized.indexOf("//");
450             if (index < 0)
451                 break;
452             normalized = normalized.substring(0, index) +
453                 normalized.substring(index + 1);
454         }
455
456         // Resolve occurrences of "/./" in the normalized path
457
while (true) {
458             int index = normalized.indexOf("/./");
459             if (index < 0)
460                 break;
461             normalized = normalized.substring(0, index) +
462                 normalized.substring(index + 2);
463         }
464
465         // Resolve occurrences of "/../" in the normalized path
466
while (true) {
467             int index = normalized.indexOf("/../");
468             if (index < 0)
469                 break;
470             if (index == 0)
471                 return (null); // Trying to go outside our context
472
int index2 = normalized.lastIndexOf('/', index - 1);
473             normalized = normalized.substring(0, index2) +
474                 normalized.substring(index + 3);
475         }
476
477         // Declare occurrences of "/..." (three or more dots) to be invalid
478
// (on some Windows platforms this walks the directory tree!!!)
479
if (normalized.indexOf("/...") >= 0)
480             return (null);
481
482         // Return the normalized path that we have completed
483
return (normalized);
484
485     }
486
487
488     /**
489      * Character conversion of the URI.
490      */

491     protected void convertURI(MessageBytes uri, CoyoteRequest request)
492         throws Exception JavaDoc {
493
494         ByteChunk bc = uri.getByteChunk();
495         CharChunk cc = uri.getCharChunk();
496         cc.allocate(bc.getLength(), -1);
497
498         String JavaDoc enc = connector.getURIEncoding();
499         if (enc != null) {
500             B2CConverter conv = request.getURIConverter();
501             try {
502                 if (conv == null) {
503                     conv = new B2CConverter(enc);
504                     request.setURIConverter(conv);
505                 } else {
506                     conv.recycle();
507                 }
508             } catch (IOException JavaDoc e) {
509                 // Ignore
510
log("Invalid URI encoding; using HTTP default", e);
511                 connector.setURIEncoding(null);
512             }
513             if (conv != null) {
514                 try {
515                     conv.convert(bc, cc);
516                     uri.setChars(cc.getBuffer(), cc.getStart(),
517                                  cc.getLength());
518                     return;
519                 } catch (IOException JavaDoc e) {
520                     if (debug >= 1) {
521                         log("Invalid URI character encoding; trying ascii", e);
522                     }
523                     cc.recycle();
524                 }
525             }
526         }
527
528         // Default encoding: fast conversion
529
byte[] bbuf = bc.getBuffer();
530         char[] cbuf = cc.getBuffer();
531         int start = bc.getStart();
532         for (int i = 0; i < bc.getLength(); i++) {
533             cbuf[i] = (char) (bbuf[i + start] & 0xff);
534         }
535         uri.setChars(cbuf, 0, bc.getLength());
536
537     }
538
539
540     /**
541      * Normalize URI.
542      * <p>
543      * This method normalizes "\", "//", "/./" and "/../". This method will
544      * return false when trying to go above the root, or if the URI contains
545      * a null byte.
546      *
547      * @param uriMB URI to be normalized
548      */

549     public static boolean normalize(MessageBytes uriMB) {
550
551         ByteChunk uriBC = uriMB.getByteChunk();
552         byte[] b = uriBC.getBytes();
553         int start = uriBC.getStart();
554         int end = uriBC.getEnd();
555
556         // URL * is acceptable
557
if ((end - start == 1) && b[start] == (byte) '*')
558           return true;
559
560         int pos = 0;
561         int index = 0;
562
563         // Replace '\' with '/'
564
// Check for null byte
565
for (pos = start; pos < end; pos++) {
566             if (b[pos] == (byte) '\\')
567                 b[pos] = (byte) '/';
568             if (b[pos] == (byte) 0)
569                 return false;
570         }
571
572         // The URL must start with '/'
573
if (b[start] != (byte) '/') {
574             return false;
575         }
576
577         // Replace "//" with "/"
578
for (pos = start; pos < (end - 1); pos++) {
579             if (b[pos] == (byte) '/') {
580                 while ((pos + 1 < end) && (b[pos + 1] == (byte) '/')) {
581                     copyBytes(b, pos, pos + 1, end - pos - 1);
582                     end--;
583                 }
584             }
585         }
586
587         // If the URI ends with "/." or "/..", then we append an extra "/"
588
// Note: It is possible to extend the URI by 1 without any side effect
589
// as the next character is a non-significant WS.
590
if (((end - start) > 2) && (b[end - 1] == (byte) '.')) {
591             if ((b[end - 2] == (byte) '/')
592                 || ((b[end - 2] == (byte) '.')
593                     && (b[end - 3] == (byte) '/'))) {
594                 b[end] = (byte) '/';
595                 end++;
596             }
597         }
598
599         uriBC.setEnd(end);
600
601         index = 0;
602
603         // Resolve occurrences of "/./" in the normalized path
604
while (true) {
605             index = uriBC.indexOf("/./", 0, 3, index);
606             if (index < 0)
607                 break;
608             copyBytes(b, start + index, start + index + 2,
609                       end - start - index - 2);
610             end = end - 2;
611             uriBC.setEnd(end);
612         }
613
614         index = 0;
615
616         // Resolve occurrences of "/../" in the normalized path
617
while (true) {
618             index = uriBC.indexOf("/../", 0, 4, index);
619             if (index < 0)
620                 break;
621             // Prevent from going outside our context
622
if (index == 0)
623                 return false;
624             int index2 = -1;
625             for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos --) {
626                 if (b[pos] == (byte) '/') {
627                     index2 = pos;
628                 }
629             }
630             copyBytes(b, start + index2, start + index + 3,
631                       end - start - index - 3);
632             end = end + index2 - index - 3;
633             uriBC.setEnd(end);
634             index = index2;
635         }
636
637         uriBC.setBytes(b, start, end);
638
639         return true;
640
641     }
642
643
644     // ------------------------------------------------------ Protected Methods
645

646
647     /**
648      * Copy an array of bytes to a different position. Used during
649      * normalization.
650      */

651     protected static void copyBytes(byte[] b, int dest, int src, int len) {
652         for (int pos = 0; pos < len; pos++) {
653             b[pos + dest] = b[pos + src];
654         }
655     }
656
657
658     /**
659      * Log a message on the Logger associated with our Container (if any)
660      *
661      * @param message Message to be logged
662      */

663     protected void log(String JavaDoc message) {
664
665         Logger logger = connector.getContainer().getLogger();
666         if (logger != null)
667             logger.log("CoyoteAdapter " + message);
668
669     }
670
671
672     /**
673      * Log a message on the Logger associated with our Container (if any)
674      *
675      * @param message Message to be logged
676      * @param throwable Associated exception
677      */

678     protected void log(String JavaDoc message, Throwable JavaDoc throwable) {
679
680         Logger logger = connector.getContainer().getLogger();
681         if (logger != null)
682             logger.log("CoyoteAdapter " + message, throwable);
683
684     }
685
686
687 }
688
Popular Tags