1 /*
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3 *
4 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
5 *
6 * Portions Copyright Apache Software Foundation.
7 *
8 * The contents of this file are subject to the terms of either the GNU
9 * General Public License Version 2 only ("GPL") or the Common Development
10 * and Distribution License("CDDL") (collectively, the "License"). You
11 * may not use this file except in compliance with the License. You can obtain
12 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
13 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
14 * language governing permissions and limitations under the License.
15 *
16 * When distributing the software, include this License Header Notice in each
17 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
18 * Sun designates this particular file as subject to the "Classpath" exception
19 * as provided by Sun in the GPL Version 2 section of the License file that
20 * accompanied this code. If applicable, add the following below the License
21 * Header, with the fields enclosed by brackets [] replaced by your own
22 * identifying information: "Portions Copyrighted [year]
23 * [name of copyright owner]"
24 *
25 * Contributor(s):
26 *
27 * If you wish your version of this file to be governed by only the CDDL or
28 * only the GPL Version 2, indicate your decision by adding "[Contributor]
29 * elects to include this software in this distribution under the [CDDL or GPL
30 * Version 2] license." If you don't indicate a single choice of license, a
31 * recipient has the option to distribute your version of this file under
32 * either the CDDL, the GPL Version 2 or to extend the choice of license to
33 * its licensees as provided above. However, if you add GPL Version 2 code
34 * and therefore, elected the GPL Version 2 license, then the option applies
35 * only if the new code is made subject to such option by the copyright
36 * holder.
37 */
38
39
40 package org.apache.coyote.tomcat5;
41
42 import java.io.IOException;
43
44 // START S1AS 6188932
45 import java.security.cert.X509Certificate;
46 import java.security.cert.CertificateException;
47 // END S1AS 6188932
48
49 import javax.servlet.http.Cookie;
50 import javax.servlet.http.HttpServletRequest;
51 import javax.servlet.http.HttpServletResponse;
52 import org.apache.catalina.ContainerEvent;
53 import org.apache.catalina.ContainerListener;
54
55 import org.apache.catalina.Context;
56 import org.apache.catalina.Globals;
57 import org.apache.catalina.Wrapper;
58 import org.apache.catalina.core.ContainerBase;
59 import org.apache.catalina.util.StringManager;
60 import com.sun.org.apache.commons.logging.Log;
61 import com.sun.org.apache.commons.logging.LogFactory;
62 import org.apache.coyote.ActionCode;
63 import org.apache.coyote.Adapter;
64 import org.apache.coyote.Request;
65 import org.apache.coyote.Response;
66 /* CR 6309511
67 import org.apache.tomcat.util.buf.B2CConverter;
68 */
69 import org.apache.tomcat.util.buf.ByteChunk;
70 import org.apache.tomcat.util.buf.CharChunk;
71 import org.apache.tomcat.util.buf.MessageBytes;
72 // START GlassFish 936
73 import org.apache.tomcat.util.buf.UEncoder;
74 // END GlassFish 936
75 /* CR 6309511
76 import org.apache.tomcat.util.http.Cookies;
77 import org.apache.tomcat.util.http.ServerCookie;
78 */
79 // START S1AS 6188932
80 import com.sun.appserv.ProxyHandler;
81 // END S1AS 6188932
82
83
84 /**
85 * Implementation of a request processor which delegates the processing to a
86 * Coyote processor.
87 *
88 * @author Craig R. McClanahan
89 * @author Remy Maucherat
90 * @version $Revision: 1.32.2.1.2.2 $ $Date: 2007/11/27 01:53:48 $
91 */
92
93 public class CoyoteAdapter
94 implements Adapter
95 {
96 private static Log log = LogFactory.getLog(CoyoteAdapter.class);
97
98 // -------------------------------------------------------------- Constants
99
100
101 public static final int ADAPTER_NOTES = 1;
102
103 static final String JVM_ROUTE = System.getProperty("jvmRoute");
104
105 protected static final boolean ALLOW_BACKSLASH =
106 Boolean.valueOf(System.getProperty("org.apache.coyote.tomcat5.CoyoteAdapter.ALLOW_BACKSLASH", "false")).booleanValue();
107
108 private static final boolean COLLAPSE_ADJACENT_SLASHES =
109 Boolean.valueOf(System.getProperty(
110 "com.sun.enterprise.web.collapseAdjacentSlashes", "true")).booleanValue();
111
112 /**
113 * When mod_jk is used, the adapter must be invoked the same way
114 * Tomcat does by invoking service(...) and the afterService(...). This
115 * is a hack to make it compatible with Tomcat 5|6.
116 */
117 private boolean compatWithTomcat = false;
118
119 private String serverName = System.getProperty("product.name");
120
121 // ----------------------------------------------------------- Constructors
122
123
124 /**
125 * Construct a new CoyoteProcessor associated with the specified connector.
126 *
127 * @param connector CoyoteConnector that owns this processor
128 * @param id Identifier of this CoyoteProcessor (unique per connector)
129 */
130 public CoyoteAdapter(CoyoteConnector connector) {
131
132 super();
133 this.connector = connector;
134 this.debug = connector.getDebug();
135 // START GlassFish 936
136 urlEncoder.addSafeCharacter('/');
137 // END GlassFish 936
138 }
139
140
141 // ----------------------------------------------------- Instance Variables
142
143
144 /**
145 * The CoyoteConnector with which this processor is associated.
146 */
147 private CoyoteConnector connector = null;
148
149
150 /**
151 * The debugging detail level for this component.
152 */
153 private int debug = 0;
154
155 // START GlassFish 936
156 private UEncoder urlEncoder = new UEncoder();
157 // END GlassFish 936
158
159 /**
160 * The match string for identifying a session ID parameter.
161 */
162 /* CR 6309511
163 private static final String match =
164 ";" + Globals.SESSION_PARAMETER_NAME + "=";
165 */
166
167
168 /**
169 * The match string for identifying a session ID parameter.
170 */
171 /* CR 6309511
172 private static final char[] SESSION_ID = match.toCharArray();
173 */
174
175
176 /**
177 * The string manager for this package.
178 */
179 protected StringManager sm =
180 StringManager.getManager(Constants.Package);
181
182
183 // -------------------------------------------------------- Adapter Methods
184
185
186 /**
187 * Service method.
188 */
189 public void service(Request req, Response res)
190 throws Exception {
191
192 CoyoteRequest request = (CoyoteRequest) req.getNote(ADAPTER_NOTES);
193 CoyoteResponse response = (CoyoteResponse) res.getNote(ADAPTER_NOTES);
194
195 if (request == null) {
196
197 // Create objects
198 request = (CoyoteRequest) connector.createRequest();
199 request.setCoyoteRequest(req);
200 response = (CoyoteResponse) connector.createResponse();
201 response.setCoyoteResponse(res);
202
203 // Link objects
204 request.setResponse(response);
205 response.setRequest(request);
206
207 // Set as notes
208 req.setNote(ADAPTER_NOTES, request);
209 res.setNote(ADAPTER_NOTES, response);
210
211 // Set query string encoding
212 req.getParameters().setQueryStringEncoding
213 (connector.getURIEncoding());
214 }
215
216 // START SJSAS 6331392
217 // Check connector for disabled state
218 if (!connector.isEnabled()) {
219 String msg = sm.getString("coyoteAdapter.listenerOff",
220 String.valueOf(connector.getPort()));
221 if (log.isDebugEnabled()) {
222 log.debug(msg);
223 }
224 response.sendError(HttpServletResponse.SC_NOT_FOUND, msg);
225 return;
226 }
227 // END SJSAS 6331392
228
229 if (connector.isXpoweredBy()) {
230 response.addHeader("X-Powered-By", "Servlet/2.5");
231 }
232
233 try {
234
235 // Parse and set Catalina and configuration specific
236 // request parameters
237 if ( postParseRequest(req, request, res, response) ) {
238
239 // START S1AS 6188932
240 boolean authPassthroughEnabled =
241 connector.getAuthPassthroughEnabled();
242 ProxyHandler proxyHandler = connector.getProxyHandler();
243 if (authPassthroughEnabled && proxyHandler != null) {
244
245 // START SJSAS 6397218
246 if (proxyHandler.getSSLKeysize(
247 (HttpServletRequest)request.getRequest()) > 0) {
248 request.setSecure(true);
249 }
250 // END SJSAS 6397218
251
252 X509Certificate[] certs = null;
253 try {
254 certs = proxyHandler.getSSLClientCertificateChain(
255 request.getRequest());
256 } catch (CertificateException ce) {
257 log.error(sm.getString(
258 "coyoteAdapter.proxyAuthCertError"),
259 ce);
260 }
261 if (certs != null) {
262 request.setAttribute(Globals.CERTIFICATES_ATTR,
263 certs);
264 }
265
266 }
267 // END S1AS 6188932
268
269 response.addHeader("Server",serverName);
270
271 // Calling the container
272 connector.getContainer().invoke(request, response);
273 }
274 /* GlassFish Issue 79
275 response.finishResponse();
276 req.action( ActionCode.ACTION_POST_REQUEST , null);
277
278 } catch (IOException e) {
279 ;
280 } catch (Throwable t) {
281 log.error(sm.getString("coyoteAdapter.service"), t);
282 } finally {
283 // Recycle the wrapper request and response
284 request.recycle();
285 response.recycle();
286 }*/
287 // START GlassFish Issue 798
288 } catch (IOException e) {
289 // Recycle the wrapper request and response
290 request.recycle();
291 response.recycle();
292 } catch (Throwable t) {
293 log.error(sm.getString("coyoteAdapter.service"), t);
294 // Recycle the wrapper request and response
295 request.recycle();
296 response.recycle();
297 }
298 // END GlassFish Issue 798
299
300 if ( compatWithTomcat ) {
301 afterService(req,res);
302 }
303
304 }
305
306 // START GlassFish Issue 798
307 /**
308 * Finish the response and close the connection based on the connection
309 * header.
310 */
311 public void afterService(Request req,Response res) throws Exception{
312 CoyoteRequest request = (CoyoteRequest) req.getNote(ADAPTER_NOTES);
313 CoyoteResponse response = (CoyoteResponse) res.getNote(ADAPTER_NOTES);
314
315 if ( request == null || response == null) return;
316
317 try{
318 response.finishResponse();
319 req.action( ActionCode.ACTION_POST_REQUEST , null);
320 }catch (Throwable t) {
321 log.error(sm.getString("coyoteAdapter.service"), t);
322 } finally {
323 // Recycle the wrapper request and response
324 request.recycle();
325 response.recycle();
326 }
327 }
328 // END GlassFish Issue 798
329 // ------------------------------------------------------ Protected Methods
330
331
332 /**
333 * Parse additional request parameters.
334 */
335 protected boolean postParseRequest(Request req, CoyoteRequest request,
336 Response res, CoyoteResponse response)
337 throws Exception {
338 // XXX the processor needs to set a correct scheme and port prior to this point,
339 // in ajp13 protocols dont make sense to get the port from the connector..
340 // XXX the processor may have set a correct scheme and port prior to this point,
341 // in ajp13 protocols dont make sense to get the port from the connector...
342 // otherwise, use connector configuration
343 if (! req.scheme().isNull()) {
344 // use processor specified scheme to determine secure state
345 request.setSecure(req.scheme().equals("https"));
346 } else {
347 // use connector scheme and secure configuration, (defaults to
348 // "http" and false respectively)
349 req.scheme().setString(connector.getScheme());
350 request.setSecure(connector.getSecure());
351 }
352
353 // FIXME: the code below doesnt belongs to here,
354 // this is only have sense
355 // in Http11, not in ajp13..
356 // At this point the Host header has been processed.
357 // Override if the proxyPort/proxyHost are set
358 String proxyName = connector.getProxyName();
359 int proxyPort = connector.getProxyPort();
360 if (proxyPort != 0) {
361 req.setServerPort(proxyPort);
362 }
363 if (proxyName != null) {
364 req.serverName().setString(proxyName);
365 }
366
367 // URI decoding
368 MessageBytes decodedURI = req.decodedURI();
369 decodedURI.duplicate(req.requestURI());
370 try {
371 req.getURLDecoder().convert(decodedURI, false);
372 } catch (IOException ioe) {
373 res.setStatus(400);
374 res.setMessage("Invalid URI: " + ioe.getMessage());
375 return false;
376 }
377
378 /* GlassFish Issue 2339
379 // Normalize decoded URI
380 if (!normalize(req.decodedURI())) {
381 res.setStatus(400);
382 res.setMessage("Invalid URI");
383 return false;
384 }
385 */
386
387 // Set the remote principal
388 String principal = req.getRemoteUser().toString();
389 if (principal != null) {
390 request.setUserPrincipal(new CoyotePrincipal(principal));
391 }
392
393 // Set the authorization type
394 String authtype = req.getAuthType().toString();
395 if (authtype != null) {
396 request.setAuthType(authtype);
397 }
398
399 /* CR 6309511
400 // URI character decoding
401 convertURI(decodedURI, request);
402
403 // Parse session Id
404 parseSessionId(req, request);
405 */
406 // START CR 6309511
407 // URI character decoding
408 request.convertURI(decodedURI);
409
410 // START GlassFish Issue 2339
411 // Normalize decoded URI
412 if (!normalize(decodedURI)) {
413 res.setStatus(400);
414 res.setMessage("Invalid URI");
415 return false;
416 }
417 // END GlassFish Issue 2339
418
419 // Parse session Id
420 request.parseSessionId();
421 // END CR 6309511
422
423 // Remove any remaining parameters (other than session id, which has
424 // already been removed in parseSessionId()) from the URI, so they
425 // won't be considered by the mapping algorithm.
426 CharChunk uriCC = decodedURI.getCharChunk();
427 int semicolon = uriCC.indexOf(';');
428 String sessionVersionString = null;
429 if (semicolon > 0) {
430 sessionVersionString = request.parseSessionVersion();
431 decodedURI.setChars
432 (uriCC.getBuffer(), uriCC.getStart(), semicolon);
433 }
434
435 // Request mapping.
436 connector.getMapper().map(req.serverName(), decodedURI,
437 request.getMappingData());
438 // START GlassFish 1024
439 request.setDefaultContext(request.getMappingData().isDefaultContext);
440 // END GlassFish 1024
441
442 // START SJSAS 6253524
443 // request.setContext((Context) request.getMappingData().context);
444 // END SJSAS 6253524
445 // START SJSAS 6253524
446 Context ctx = (Context) request.getMappingData().context;
447 request.setContext(ctx);
448 // END SJSAS 6253524
449
450 request.setWrapper((Wrapper) request.getMappingData().wrapper);
451
452 // Filter trace method
453 if (!connector.getAllowTrace()
454 && req.method().equalsIgnoreCase("TRACE")) {
455 Wrapper wrapper = request.getWrapper();
456 String header = null;
457 if (wrapper != null) {
458 String[] methods = wrapper.getServletMethods();
459 if (methods != null) {
460 for (int i=0; i<methods.length; i++) {
461 // Exclude TRACE from methods returned in Allow header
462 if ("TRACE".equals(methods[i])) {
463 continue;
464 }
465 if (header == null) {
466 header = methods[i];
467 } else {
468 header += ", " + methods[i];
469 }
470 }
471 }
472 }
473 res.setStatus(405);
474 res.addHeader("Allow", header);
475 res.setMessage("TRACE method is not allowed");
476 return false;
477 }
478
479 // Possible redirect
480 MessageBytes redirectPathMB = request.getMappingData().redirectPath;
481 // START SJSAS 6253524
482 // if (!redirectPathMB.isNull()) {
483 // END SJSAS 6253524
484 // START SJSAS 6253524
485 if (!redirectPathMB.isNull()
486 && (!ctx.hasAdHocPaths()
487 || (ctx.getAdHocServletName(((HttpServletRequest)
488 request.getRequest()).getServletPath()) == null))) {
489 // END SJSAS 6253524
490 String redirectPath = redirectPathMB.toString();
491 String query = request.getQueryString();
492 if (request.isRequestedSessionIdFromURL()) {
493 // This is not optimal, but as this is not very common, it
494 // shouldn't matter
495 redirectPath = redirectPath + ";jsessionid="
496 + request.getRequestedSessionId();
497 }
498 // START GlassFish 936
499 redirectPath = urlEncoder.encodeURL(redirectPath);
500 // END GlassFish 936
501 if (query != null) {
502 // This is not optimal, but as this is not very common, it
503 // shouldn't matter
504 redirectPath = redirectPath + "?" + query;
505 }
506
507 // START CR 6590921
508 boolean authPassthroughEnabled =
509 connector.getAuthPassthroughEnabled();
510 ProxyHandler proxyHandler = connector.getProxyHandler();
511 if (authPassthroughEnabled && proxyHandler != null) {
512
513 if (proxyHandler.getSSLKeysize(
514 (HttpServletRequest)request.getRequest()) > 0) {
515 request.setSecure(true);
516 }
517 }
518 // END CR 6590921
519
520 response.sendRedirect(redirectPath);
521 return false;
522 }
523
524 // Parse session Id
525 /* CR 6309511
526 parseSessionCookiesId(req, request);
527 */
528 // START CR 6309511
529 request.parseSessionCookiesId();
530 // END CR 6309511
531
532 // START SJSAS 6346226
533 request.parseJrouteCookie();
534 // END SJSAS 6346226
535
536 if (sessionVersionString != null) {
537 request.parseSessionVersionString(sessionVersionString);
538 }
539
540 return true;
541 }
542
543
544 /**
545 * Parse session id in URL.
546 */
547 /* CR 6309511
548 protected void parseSessionId(Request req, CoyoteRequest request) {
549
550 CharChunk uriCC = req.decodedURI().getCharChunk();
551 int semicolon = uriCC.indexOf(match, 0, match.length(), 0);
552
553 if (semicolon > 0) {
554
555 // Parse session ID, and extract it from the decoded request URI
556 int start = uriCC.getStart();
557 int end = uriCC.getEnd();
558
559 int sessionIdStart = start + semicolon + match.length();
560 int semicolon2 = uriCC.indexOf(';', sessionIdStart);
561 if (semicolon2 >= 0) {
562 request.setRequestedSessionId
563 (new String(uriCC.getBuffer(), sessionIdStart,
564 semicolon2 - semicolon - match.length()));
565 } else {
566 request.setRequestedSessionId
567 (new String(uriCC.getBuffer(), sessionIdStart,
568 end - sessionIdStart));
569 }
570 request.setRequestedSessionURL(true);
571
572 // Extract session ID from request URI
573 ByteChunk uriBC = req.requestURI().getByteChunk();
574 start = uriBC.getStart();
575 end = uriBC.getEnd();
576 semicolon = uriBC.indexOf(match, 0, match.length(), 0);
577
578 if (semicolon > 0) {
579 sessionIdStart = start + semicolon;
580 semicolon2 = uriCC.indexOf
581 (';', start + semicolon + match.length());
582 uriBC.setEnd(start + semicolon);
583 byte[] buf = uriBC.getBuffer();
584 if (semicolon2 >= 0) {
585 for (int i = 0; i < end - start - semicolon2; i++) {
586 buf[start + semicolon + i]
587 = buf[start + i + semicolon2];
588 }
589 uriBC.setBytes(buf, start, semicolon
590 + (end - start - semicolon2));
591 }
592 }
593
594 } else {
595 request.setRequestedSessionId(null);
596 request.setRequestedSessionURL(false);
597 }
598
599 }
600 */
601
602
603 /**
604 * Parse session id in URL.
605 */
606 /* CR 6309511
607 protected void parseSessionCookiesId(Request req, CoyoteRequest request) {
608
609 // Parse session id from cookies
610 Cookies serverCookies = req.getCookies();
611 int count = serverCookies.getCookieCount();
612 if (count <= 0)
613 return;
614
615 for (int i = 0; i < count; i++) {
616 ServerCookie scookie = serverCookies.getCookie(i);
617 if (scookie.getName().equals(Globals.SESSION_COOKIE_NAME)) {
618 // Override anything requested in the URL
619 if (!request.isRequestedSessionIdFromCookie()) {
620 // Accept only the first session id cookie
621 convertMB(scookie.getValue());
622 request.setRequestedSessionId
623 (scookie.getValue().toString());
624 request.setRequestedSessionCookie(true);
625 request.setRequestedSessionURL(false);
626 if (log.isDebugEnabled())
627 log.debug(" Requested cookie session id is " +
628 ((HttpServletRequest) request.getRequest())
629 .getRequestedSessionId());
630 } else {
631 if (!request.isRequestedSessionIdValid()) {
632 // Replace the session id until one is valid
633 convertMB(scookie.getValue());
634 request.setRequestedSessionId
635 (scookie.getValue().toString());
636 }
637 }
638 }
639 }
640
641 }
642 */
643
644
645 /**
646 * Character conversion of the URI.
647 */
648 /* CR 6309511
649 protected void convertURI(MessageBytes uri, CoyoteRequest request)
650 throws Exception {
651
652 ByteChunk bc = uri.getByteChunk();
653 CharChunk cc = uri.getCharChunk();
654 cc.allocate(bc.getLength(), -1);
655
656 String enc = connector.getURIEncoding();
657 if (enc != null) {
658 B2CConverter conv = request.getURIConverter();
659 try {
660 if (conv == null) {
661 conv = new B2CConverter(enc);
662 request.setURIConverter(conv);
663 } else {
664 conv.recycle();
665 }
666 } catch (IOException e) {
667 // Ignore
668 log.error("Invalid URI encoding; using HTTP default");
669 connector.setURIEncoding(null);
670 }
671 if (conv != null) {
672 try {
673 conv.convert(bc, cc);
674 uri.setChars(cc.getBuffer(), cc.getStart(),
675 cc.getLength());
676 return;
677 } catch (IOException e) {
678 log.error("Invalid URI character encoding; trying ascii");
679 cc.recycle();
680 }
681 }
682 }
683
684 // Default encoding: fast conversion
685 byte[] bbuf = bc.getBuffer();
686 char[] cbuf = cc.getBuffer();
687 int start = bc.getStart();
688 for (int i = 0; i < bc.getLength(); i++) {
689 cbuf[i] = (char) (bbuf[i + start] & 0xff);
690 }
691 uri.setChars(cbuf, 0, bc.getLength());
692
693 }
694 */
695
696
697 /**
698 * Normalize URI.
699 * <p>
700 * This method normalizes "\", "//", "/./" and "/../". This method will
701 * return false when trying to go above the root, or if the URI contains
702 * a null byte.
703 *
704 * @param uriMB URI to be normalized
705 */
706 public static boolean normalize(MessageBytes uriMB) {
707
708 int type = uriMB.getType();
709 if (type == MessageBytes.T_CHARS) {
710 return normalizeChars(uriMB);
711 } else {
712 return normalizeBytes(uriMB);
713 }
714 }
715
716
717 private static boolean normalizeBytes(MessageBytes uriMB) {
718
719 ByteChunk uriBC = uriMB.getByteChunk();
720 byte[] b = uriBC.getBytes();
721 int start = uriBC.getStart();
722 int end = uriBC.getEnd();
723
724 // URL * is acceptable
725 if ((end - start == 1) && b[start] == (byte) '*')
726 return true;
727
728 int pos = 0;
729 int index = 0;
730
731 // Replace '\' with '/'
732 // Check for null byte
733 for (pos = start; pos < end; pos++) {
734 if (b[pos] == (byte) '\\') {
735 if (ALLOW_BACKSLASH) {
736 b[pos] = (byte) '/';
737 } else {
738 return false;
739 }
740 }
741 if (b[pos] == (byte) 0) {
742 return false;
743 }
744 }
745
746 // The URL must start with '/'
747 if (b[start] != (byte) '/') {
748 return false;
749 }
750
751 // Replace "//" with "/"
752 if (COLLAPSE_ADJACENT_SLASHES) {
753 for (pos = start; pos < (end - 1); pos++) {
754 if (b[pos] == (byte) '/') {
755 while ((pos + 1 < end) && (b[pos + 1] == (byte) '/')) {
756 copyBytes(b, pos, pos + 1, end - pos - 1);
757 end--;
758 }
759 }
760 }
761 }
762
763 // If the URI ends with "/." or "/..", then we append an extra "/"
764 // Note: It is possible to extend the URI by 1 without any side effect
765 // as the next character is a non-significant WS.
766 if (((end - start) > 2) && (b[end - 1] == (byte) '.')) {
767 if ((b[end - 2] == (byte) '/')
768 || ((b[end - 2] == (byte) '.')
769 && (b[end - 3] == (byte) '/'))) {
770 b[end] = (byte) '/';
771 end++;
772 }
773 }
774
775 uriBC.setEnd(end);
776
777 index = 0;
778
779 // Resolve occurrences of "/./" in the normalized path
780 while (true) {
781 index = uriBC.indexOf("/./", 0, 3, index);
782 if (index < 0)
783 break;
784 copyBytes(b, start + index, start + index + 2,
785 end - start - index - 2);
786 end = end - 2;
787 uriBC.setEnd(end);
788 }
789
790 index = 0;
791
792 // Resolve occurrences of "/../" in the normalized path
793 while (true) {
794 index = uriBC.indexOf("/../", 0, 4, index);
795 if (index < 0)
796 break;
797 // Prevent from going outside our context
798 if (index == 0)
799 return false;
800 int index2 = -1;
801 for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos --) {
802 if (b[pos] == (byte) '/') {
803 index2 = pos;
804 }
805 }
806 copyBytes(b, start + index2, start + index + 3,
807 end - start - index - 3);
808 end = end + index2 - index - 3;
809 uriBC.setEnd(end);
810 index = index2;
811 }
812
813 uriBC.setBytes(b, start, end);
814
815 return true;
816
817 }
818
819
820 private static boolean normalizeChars(MessageBytes uriMB) {
821
822 CharChunk uriCC = uriMB.getCharChunk();
823 char[] c = uriCC.getChars();
824 int start = uriCC.getStart();
825 int end = uriCC.getEnd();
826
827 // URL * is acceptable
828 if ((end - start == 1) && c[start] == (char) '*')
829 return true;
830
831 int pos = 0;
832 int index = 0;
833
834 // Replace '\' with '/'
835 // Check for null char
836 for (pos = start; pos < end; pos++) {
837 if (c[pos] == (char) '\\') {
838 if (ALLOW_BACKSLASH) {
839 c[pos] = (char) '/';
840 } else {
841 return false;
842 }
843 }
844 if (c[pos] == (char) 0) {
845 return false;
846 }
847 }
848
849 // The URL must start with '/'
850 if (c[start] != (char) '/') {
851 return false;
852 }
853
854 // Replace "//" with "/"
855 if (COLLAPSE_ADJACENT_SLASHES) {
856 for (pos = start; pos < (end - 1); pos++) {
857 if (c[pos] == (char) '/') {
858 while ((pos + 1 < end) && (c[pos + 1] == (char) '/')) {
859 copyChars(c, pos, pos + 1, end - pos - 1);
860 end--;
861 }
862 }
863 }
864 }
865
866 // If the URI ends with "/." or "/..", then we append an extra "/"
867 // Note: It is possible to extend the URI by 1 without any side effect
868 // as the next character is a non-significant WS.
869 if (((end - start) > 2) && (c[end - 1] == (char) '.')) {
870 if ((c[end - 2] == (char) '/')
871 || ((c[end - 2] == (char) '.')
872 && (c[end - 3] == (char) '/'))) {
873 c[end] = (char) '/';
874 end++;
875 }
876 }
877
878 uriCC.setEnd(end);
879
880 index = 0;
881
882 // Resolve occurrences of "/./" in the normalized path
883 while (true) {
884 index = uriCC.indexOf("/./", 0, 3, index);
885 if (index < 0)
886 break;
887 copyChars(c, start + index, start + index + 2,
888 end - start - index - 2);
889 end = end - 2;
890 uriCC.setEnd(end);
891 }
892
893 index = 0;
894
895 // Resolve occurrences of "/../" in the normalized path
896 while (true) {
897 index = uriCC.indexOf("/../", 0, 4, index);
898 if (index < 0)
899 break;
900 // Prevent from going outside our context
901 if (index == 0)
902 return false;
903 int index2 = -1;
904 for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos --) {
905 if (c[pos] == (char) '/') {
906 index2 = pos;
907 }
908 }
909 copyChars(c, start + index2, start + index + 3,
910 end - start - index - 3);
911 end = end + index2 - index - 3;
912 uriCC.setEnd(end);
913 index = index2;
914 }
915
916 uriCC.setChars(c, start, end);
917
918 return true;
919
920 }
921
922
923 // ------------------------------------------------------ Protected Methods
924
925
926 /**
927 * Copy an array of bytes to a different position. Used during
928 * normalization.
929 */
930 protected static void copyBytes(byte[] b, int dest, int src, int len) {
931 for (int pos = 0; pos < len; pos++) {
932 b[pos + dest] = b[pos + src];
933 }
934 }
935
936
937 /**
938 * Copy an array of chars to a different position. Used during
939 * normalization.
940 */
941 private static void copyChars(char[] c, int dest, int src, int len) {
942 for (int pos = 0; pos < len; pos++) {
943 c[pos + dest] = c[pos + src];
944 }
945 }
946
947
948 /**
949 * Log a message on the Logger associated with our Container (if any)
950 *
951 * @param message Message to be logged
952 */
953 protected void log(String message) {
954 log.info( message );
955 }
956
957
958 /**
959 * Log a message on the Logger associated with our Container (if any)
960 *
961 * @param message Message to be logged
962 * @param throwable Associated exception
963 */
964 protected void log(String message, Throwable throwable) {
965 log.error( message, throwable);
966 }
967
968
969 /**
970 * Character conversion of the a US-ASCII MessageBytes.
971 */
972 /* CR 6309511
973 protected void convertMB(MessageBytes mb) {
974
975 // This is of course only meaningful for bytes
976 if (mb.getType() != MessageBytes.T_BYTES)
977 return;
978
979 ByteChunk bc = mb.getByteChunk();
980 CharChunk cc = mb.getCharChunk();
981 cc.allocate(bc.getLength(), -1);
982
983 // Default encoding: fast conversion
984 byte[] bbuf = bc.getBuffer();
985 char[] cbuf = cc.getBuffer();
986 int start = bc.getStart();
987 for (int i = 0; i < bc.getLength(); i++) {
988 cbuf[i] = (char) (bbuf[i + start] & 0xff);
989 }
990 mb.setChars(cbuf, 0, bc.getLength());
991
992 }
993 */
994
995
996 // START SJSAS 6349248
997 /**
998 * Notify all container event listeners that a particular event has
999 * occurred for this Adapter. The default implementation performs
1000 * this notification synchronously using the calling thread.
1001 *
1002 * @param type Event type
1003 * @param data Event data
1004 */
1005 public void fireAdapterEvent(String type, Object data) {
1006 if ( connector != null && connector.getContainer() != null) {
1007 try{
1008 ((ContainerBase)connector.getContainer())
1009 .fireContainerEvent(type,data);
1010 } catch (Throwable t){
1011 log.error(sm.getString("coyoteAdapter.service"), t);
1012 }
1013 }
1014 }
1015 // END SJSAS 6349248
1016
1017
1018 /**
1019 * Return true when an instance is executed the same way it does in Tomcat.
1020 */
1021 public boolean isCompatWithTomcat() {
1022 return compatWithTomcat;
1023 }
1024
1025
1026 /**
1027 * <tt>true</tt> if this class needs to be compatible with Tomcat
1028 * Adapter class. Since Tomcat Adapter implementation doesn't support
1029 * the afterService method, the afterService method must be invoked
1030 * inside the service method.
1031 */
1032 public void setCompatWithTomcat(boolean compatWithTomcat) {
1033 this.compatWithTomcat = compatWithTomcat;
1034
1035 // Add server header
1036 if (compatWithTomcat){
1037 serverName = "Apache/" + serverName;
1038 } else {
1039 // Recalculate.
1040 serverName = System.getProperty("product.name");
1041 }
1042 }
1043
1044 }