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 package org.apache.catalina.connector;
18
19 import java.io.IOException;
20 import java.nio.charset.Charset;
21 import java.nio.charset.StandardCharsets;
22 import java.util.EnumSet;
23 import java.util.concurrent.atomic.AtomicBoolean;
24
25 import javax.servlet.ReadListener;
26 import javax.servlet.RequestDispatcher;
27 import javax.servlet.ServletException;
28 import javax.servlet.SessionTrackingMode;
29 import javax.servlet.WriteListener;
30 import javax.servlet.http.HttpServletResponse;
31
32 import org.apache.catalina.Authenticator;
33 import org.apache.catalina.Context;
34 import org.apache.catalina.Host;
35 import org.apache.catalina.Wrapper;
36 import org.apache.catalina.authenticator.AuthenticatorBase;
37 import org.apache.catalina.core.AsyncContextImpl;
38 import org.apache.catalina.util.ServerInfo;
39 import org.apache.catalina.util.SessionConfig;
40 import org.apache.catalina.util.URLEncoder;
41 import org.apache.coyote.ActionCode;
42 import org.apache.coyote.Adapter;
43 import org.apache.juli.logging.Log;
44 import org.apache.juli.logging.LogFactory;
45 import org.apache.tomcat.util.ExceptionUtils;
46 import org.apache.tomcat.util.buf.B2CConverter;
47 import org.apache.tomcat.util.buf.ByteChunk;
48 import org.apache.tomcat.util.buf.CharChunk;
49 import org.apache.tomcat.util.buf.MessageBytes;
50 import org.apache.tomcat.util.http.ServerCookie;
51 import org.apache.tomcat.util.http.ServerCookies;
52 import org.apache.tomcat.util.net.SSLSupport;
53 import org.apache.tomcat.util.net.SocketEvent;
54 import org.apache.tomcat.util.res.StringManager;
55
56
57 /**
58 * Implementation of a request processor which delegates the processing to a
59 * Coyote processor.
60 *
61 * @author Craig R. McClanahan
62 * @author Remy Maucherat
63 */
64 public class CoyoteAdapter implements Adapter {
65
66 private static final Log log = LogFactory.getLog(CoyoteAdapter.class);
67
68 // -------------------------------------------------------------- Constants
69
70 private static final String POWERED_BY = "Servlet/4.0 JSP/2.3 " +
71 "(" + ServerInfo.getServerInfo() + " Java/" +
72 System.getProperty("java.vm.vendor") + "/" +
73 System.getProperty("java.runtime.version") + ")";
74
75 private static final EnumSet<SessionTrackingMode> SSL_ONLY =
76 EnumSet.of(SessionTrackingMode.SSL);
77
78 public static final int ADAPTER_NOTES = 1;
79
80
81 protected static final boolean ALLOW_BACKSLASH =
82 Boolean.parseBoolean(System.getProperty("org.apache.catalina.connector.CoyoteAdapter.ALLOW_BACKSLASH", "false"));
83
84
85 private static final ThreadLocal<String> THREAD_NAME =
86 new ThreadLocal<String>() {
87
88 @Override
89 protected String initialValue() {
90 return Thread.currentThread().getName();
91 }
92
93 };
94
95 // ----------------------------------------------------------- Constructors
96
97
98 /**
99 * Construct a new CoyoteProcessor associated with the specified connector.
100 *
101 * @param connector CoyoteConnector that owns this processor
102 */
103 public CoyoteAdapter(Connector connector) {
104
105 super();
106 this.connector = connector;
107
108 }
109
110
111 // ----------------------------------------------------- Instance Variables
112
113
114 /**
115 * The CoyoteConnector with which this processor is associated.
116 */
117 private final Connector connector;
118
119
120 /**
121 * The string manager for this package.
122 */
123 protected static final StringManager sm = StringManager.getManager(CoyoteAdapter.class);
124
125
126 // -------------------------------------------------------- Adapter Methods
127
128 @Override
129 public boolean asyncDispatch(org.apache.coyote.Request req, org.apache.coyote.Response res,
130 SocketEvent status) throws Exception {
131
132 Request request = (Request) req.getNote(ADAPTER_NOTES);
133 Response response = (Response) res.getNote(ADAPTER_NOTES);
134
135 if (request == null) {
136 throw new IllegalStateException(sm.getString("coyoteAdapter.nullRequest"));
137 }
138
139 boolean success = true;
140 AsyncContextImpl asyncConImpl = request.getAsyncContextInternal();
141
142 req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get());
143
144 try {
145 if (!request.isAsync()) {
146 // Error or timeout
147 // Lift any suspension (e.g. if sendError() was used by an async
148 // request) to allow the response to be written to the client
149 response.setSuspended(false);
150 }
151
152 if (status==SocketEvent.TIMEOUT) {
153 if (!asyncConImpl.timeout()) {
154 asyncConImpl.setErrorState(null, false);
155 }
156 } else if (status==SocketEvent.ERROR) {
157 // An I/O error occurred on a non-container thread which means
158 // that the socket needs to be closed so set success to false to
159 // trigger a close
160 success = false;
161 Throwable t = (Throwable)req.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
162 req.getAttributes().remove(RequestDispatcher.ERROR_EXCEPTION);
163 ClassLoader oldCL = null;
164 try {
165 oldCL = request.getContext().bind(false, null);
166 if (req.getReadListener() != null) {
167 req.getReadListener().onError(t);
168 }
169 if (res.getWriteListener() != null) {
170 res.getWriteListener().onError(t);
171 }
172 } finally {
173 request.getContext().unbind(false, oldCL);
174 }
175 if (t != null) {
176 asyncConImpl.setErrorState(t, true);
177 }
178 }
179
180 // Check to see if non-blocking writes or reads are being used
181 if (!request.isAsyncDispatching() && request.isAsync()) {
182 WriteListener writeListener = res.getWriteListener();
183 ReadListener readListener = req.getReadListener();
184 if (writeListener != null && status == SocketEvent.OPEN_WRITE) {
185 ClassLoader oldCL = null;
186 try {
187 oldCL = request.getContext().bind(false, null);
188 res.onWritePossible();
189 if (request.isFinished() && req.sendAllDataReadEvent() &&
190 readListener != null) {
191 readListener.onAllDataRead();
192 }
193 } catch (Throwable t) {
194 ExceptionUtils.handleThrowable(t);
195 writeListener.onError(t);
196 success = false;
197 } finally {
198 request.getContext().unbind(false, oldCL);
199 }
200 } else if (readListener != null && status == SocketEvent.OPEN_READ) {
201 ClassLoader oldCL = null;
202 try {
203 oldCL = request.getContext().bind(false, null);
204 // If data is being read on a non-container thread a
205 // dispatch with status OPEN_READ will be used to get
206 // execution back on a container thread for the
207 // onAllDataRead() event. Therefore, make sure
208 // onDataAvailable() is not called in this case.
209 if (!request.isFinished()) {
210 readListener.onDataAvailable();
211 }
212 if (request.isFinished() && req.sendAllDataReadEvent()) {
213 readListener.onAllDataRead();
214 }
215 } catch (Throwable t) {
216 ExceptionUtils.handleThrowable(t);
217 readListener.onError(t);
218 success = false;
219 } finally {
220 request.getContext().unbind(false, oldCL);
221 }
222 }
223 }
224
225 // Has an error occurred during async processing that needs to be
226 // processed by the application's error page mechanism (or Tomcat's
227 // if the application doesn't define one)?
228 if (!request.isAsyncDispatching() && request.isAsync() &&
229 response.isErrorReportRequired()) {
230 connector.getService().getContainer().getPipeline().getFirst().invoke(
231 request, response);
232 }
233
234 if (request.isAsyncDispatching()) {
235 connector.getService().getContainer().getPipeline().getFirst().invoke(
236 request, response);
237 Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
238 if (t != null) {
239 asyncConImpl.setErrorState(t, true);
240 }
241 }
242
243 if (!request.isAsync()) {
244 request.finishRequest();
245 response.finishResponse();
246 }
247
248 // Check to see if the processor is in an error state. If it is,
249 // bail out now.
250 AtomicBoolean error = new AtomicBoolean(false);
251 res.action(ActionCode.IS_ERROR, error);
252 if (error.get()) {
253 if (request.isAsyncCompleting()) {
254 // Connection will be forcibly closed which will prevent
255 // completion happening at the usual point. Need to trigger
256 // call to onComplete() here.
257 res.action(ActionCode.ASYNC_POST_PROCESS, null);
258 }
259 success = false;
260 }
261 } catch (IOException e) {
262 success = false;
263 // Ignore
264 } catch (Throwable t) {
265 ExceptionUtils.handleThrowable(t);
266 success = false;
267 log.error(sm.getString("coyoteAdapter.asyncDispatch"), t);
268 } finally {
269 if (!success) {
270 res.setStatus(500);
271 }
272
273 // Access logging
274 if (!success || !request.isAsync()) {
275 long time = 0;
276 if (req.getStartTime() != -1) {
277 time = System.currentTimeMillis() - req.getStartTime();
278 }
279 Context context = request.getContext();
280 if (context != null) {
281 context.logAccess(request, response, time, false);
282 } else {
283 log(req, res, time);
284 }
285 }
286
287 req.getRequestProcessor().setWorkerThreadName(null);
288 // Recycle the wrapper request and response
289 if (!success || !request.isAsync()) {
290 updateWrapperErrorCount(request, response);
291 request.recycle();
292 response.recycle();
293 }
294 }
295 return success;
296 }
297
298
299 @Override
300 public void service(org.apache.coyote.Request req, org.apache.coyote.Response res)
301 throws Exception {
302
303 Request request = (Request) req.getNote(ADAPTER_NOTES);
304 Response response = (Response) res.getNote(ADAPTER_NOTES);
305
306 if (request == null) {
307 // Create objects
308 request = connector.createRequest();
309 request.setCoyoteRequest(req);
310 response = connector.createResponse();
311 response.setCoyoteResponse(res);
312
313 // Link objects
314 request.setResponse(response);
315 response.setRequest(request);
316
317 // Set as notes
318 req.setNote(ADAPTER_NOTES, request);
319 res.setNote(ADAPTER_NOTES, response);
320
321 // Set query string encoding
322 req.getParameters().setQueryStringCharset(connector.getURICharset());
323 }
324
325 if (connector.getXpoweredBy()) {
326 response.addHeader("X-Powered-By", POWERED_BY);
327 }
328
329 boolean async = false;
330 boolean postParseSuccess = false;
331
332 req.getRequestProcessor().setWorkerThreadName(THREAD_NAME.get());
333
334 try {
335 // Parse and set Catalina and configuration specific
336 // request parameters
337 postParseSuccess = postParseRequest(req, request, res, response);
338 if (postParseSuccess) {
339 //check valves if we support async
340 request.setAsyncSupported(
341 connector.getService().getContainer().getPipeline().isAsyncSupported());
342 // Calling the container
343 connector.getService().getContainer().getPipeline().getFirst().invoke(
344 request, response);
345 }
346 if (request.isAsync()) {
347 async = true;
348 ReadListener readListener = req.getReadListener();
349 if (readListener != null && request.isFinished()) {
350 // Possible the all data may have been read during service()
351 // method so this needs to be checked here
352 ClassLoader oldCL = null;
353 try {
354 oldCL = request.getContext().bind(false, null);
355 if (req.sendAllDataReadEvent()) {
356 req.getReadListener().onAllDataRead();
357 }
358 } finally {
359 request.getContext().unbind(false, oldCL);
360 }
361 }
362
363 Throwable throwable =
364 (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
365
366 // If an async request was started, is not going to end once
367 // this container thread finishes and an error occurred, trigger
368 // the async error process
369 if (!request.isAsyncCompleting() && throwable != null) {
370 request.getAsyncContextInternal().setErrorState(throwable, true);
371 }
372 } else {
373 request.finishRequest();
374 response.finishResponse();
375 }
376
377 } catch (IOException e) {
378 // Ignore
379 } finally {
380 AtomicBoolean error = new AtomicBoolean(false);
381 res.action(ActionCode.IS_ERROR, error);
382
383 if (request.isAsyncCompleting() && error.get()) {
384 // Connection will be forcibly closed which will prevent
385 // completion happening at the usual point. Need to trigger
386 // call to onComplete() here.
387 res.action(ActionCode.ASYNC_POST_PROCESS, null);
388 async = false;
389 }
390
391 // Access log
392 if (!async && postParseSuccess) {
393 // Log only if processing was invoked.
394 // If postParseRequest() failed, it has already logged it.
395 Context context = request.getContext();
396 Host host = request.getHost();
397 // If the context is null, it is likely that the endpoint was
398 // shutdown, this connection closed and the request recycled in
399 // a different thread. That thread will have updated the access
400 // log so it is OK not to update the access log here in that
401 // case.
402 // The other possibility is that an error occurred early in
403 // processing and the request could not be mapped to a Context.
404 // Log via the host or engine in that case.
405 long time = System.currentTimeMillis() - req.getStartTime();
406 if (context != null) {
407 context.logAccess(request, response, time, false);
408 } else if (response.isError()) {
409 if (host != null) {
410 host.logAccess(request, response, time, false);
411 } else {
412 connector.getService().getContainer().logAccess(
413 request, response, time, false);
414 }
415 }
416 }
417
418 req.getRequestProcessor().setWorkerThreadName(null);
419
420 // Recycle the wrapper request and response
421 if (!async) {
422 updateWrapperErrorCount(request, response);
423 request.recycle();
424 response.recycle();
425 }
426 }
427 }
428
429
430 private void updateWrapperErrorCount(Request request, Response response) {
431 if (response.isError()) {
432 Wrapper wrapper = request.getWrapper();
433 if (wrapper != null) {
434 wrapper.incrementErrorCount();
435 }
436 }
437 }
438
439
440 @Override
441 public boolean prepare(org.apache.coyote.Request req, org.apache.coyote.Response res)
442 throws IOException, ServletException {
443 Request request = (Request) req.getNote(ADAPTER_NOTES);
444 Response response = (Response) res.getNote(ADAPTER_NOTES);
445
446 return postParseRequest(req, request, res, response);
447 }
448
449
450 @Override
451 public void log(org.apache.coyote.Request req,
452 org.apache.coyote.Response res, long time) {
453
454 Request request = (Request) req.getNote(ADAPTER_NOTES);
455 Response response = (Response) res.getNote(ADAPTER_NOTES);
456
457 if (request == null) {
458 // Create objects
459 request = connector.createRequest();
460 request.setCoyoteRequest(req);
461 response = connector.createResponse();
462 response.setCoyoteResponse(res);
463
464 // Link objects
465 request.setResponse(response);
466 response.setRequest(request);
467
468 // Set as notes
469 req.setNote(ADAPTER_NOTES, request);
470 res.setNote(ADAPTER_NOTES, response);
471
472 // Set query string encoding
473 req.getParameters().setQueryStringCharset(connector.getURICharset());
474 }
475
476 try {
477 // Log at the lowest level available. logAccess() will be
478 // automatically called on parent containers.
479 boolean logged = false;
480 Context context = request.mappingData.context;
481 Host host = request.mappingData.host;
482 if (context != null) {
483 logged = true;
484 context.logAccess(request, response, time, true);
485 } else if (host != null) {
486 logged = true;
487 host.logAccess(request, response, time, true);
488 }
489 if (!logged) {
490 connector.getService().getContainer().logAccess(request, response, time, true);
491 }
492 } catch (Throwable t) {
493 ExceptionUtils.handleThrowable(t);
494 log.warn(sm.getString("coyoteAdapter.accesslogFail"), t);
495 } finally {
496 updateWrapperErrorCount(request, response);
497 request.recycle();
498 response.recycle();
499 }
500 }
501
502
503 private static class RecycleRequiredException extends Exception {
504 private static final long serialVersionUID = 1L;
505 }
506
507 @Override
508 public void checkRecycled(org.apache.coyote.Request req,
509 org.apache.coyote.Response res) {
510 Request request = (Request) req.getNote(ADAPTER_NOTES);
511 Response response = (Response) res.getNote(ADAPTER_NOTES);
512 String messageKey = null;
513 if (request != null && request.getHost() != null) {
514 messageKey = "coyoteAdapter.checkRecycled.request";
515 } else if (response != null && response.getContentWritten() != 0) {
516 messageKey = "coyoteAdapter.checkRecycled.response";
517 }
518 if (messageKey != null) {
519 // Log this request, as it has probably skipped the access log.
520 // The log() method will take care of recycling.
521 log(req, res, 0L);
522
523 if (connector.getState().isAvailable()) {
524 if (log.isInfoEnabled()) {
525 log.info(sm.getString(messageKey),
526 new RecycleRequiredException());
527 }
528 } else {
529 // There may be some aborted requests.
530 // When connector shuts down, the request and response will not
531 // be reused, so there is no issue to warn about here.
532 if (log.isDebugEnabled()) {
533 log.debug(sm.getString(messageKey),
534 new RecycleRequiredException());
535 }
536 }
537 }
538 }
539
540
541 @Override
542 public String getDomain() {
543 return connector.getDomain();
544 }
545
546
547 // ------------------------------------------------------ Protected Methods
548
549 /**
550 * Perform the necessary processing after the HTTP headers have been parsed
551 * to enable the request/response pair to be passed to the start of the
552 * container pipeline for processing.
553 *
554 * @param req The coyote request object
555 * @param request The catalina request object
556 * @param res The coyote response object
557 * @param response The catalina response object
558 *
559 * @return <code>true</code> if the request should be passed on to the start
560 * of the container pipeline, otherwise <code>false</code>
561 *
562 * @throws IOException If there is insufficient space in a buffer while
563 * processing headers
564 * @throws ServletException If the supported methods of the target servlet
565 * cannot be determined
566 */
567 protected boolean postParseRequest(org.apache.coyote.Request req, Request request,
568 org.apache.coyote.Response res, Response response) throws IOException, ServletException {
569
570 // If the processor has set the scheme (AJP does this, HTTP does this if
571 // SSL is enabled) use this to set the secure flag as well. If the
572 // processor hasn't set it, use the settings from the connector
573 if (req.scheme().isNull()) {
574 // Use connector scheme and secure configuration, (defaults to
575 // "http" and false respectively)
576 req.scheme().setString(connector.getScheme());
577 request.setSecure(connector.getSecure());
578 } else {
579 // Use processor specified scheme to determine secure state
580 request.setSecure(req.scheme().equals("https"));
581 }
582
583 // At this point the Host header has been processed.
584 // Override if the proxyPort/proxyHost are set
585 String proxyName = connector.getProxyName();
586 int proxyPort = connector.getProxyPort();
587 if (proxyPort != 0) {
588 req.setServerPort(proxyPort);
589 } else if (req.getServerPort() == -1) {
590 // Not explicitly set. Use default ports based on the scheme
591 if (req.scheme().equals("https")) {
592 req.setServerPort(443);
593 } else {
594 req.setServerPort(80);
595 }
596 }
597 if (proxyName != null) {
598 req.serverName().setString(proxyName);
599 }
600
601 MessageBytes undecodedURI = req.requestURI();
602
603 // Check for ping OPTIONS * request
604 if (undecodedURI.equals("*")) {
605 if (req.method().equalsIgnoreCase("OPTIONS")) {
606 StringBuilder allow = new StringBuilder();
607 allow.append("GET, HEAD, POST, PUT, DELETE, OPTIONS");
608 // Trace if allowed
609 if (connector.getAllowTrace()) {
610 allow.append(", TRACE");
611 }
612 res.setHeader("Allow", allow.toString());
613 // Access log entry as processing won't reach AccessLogValve
614 connector.getService().getContainer().logAccess(request, response, 0, true);
615 return false;
616 } else {
617 response.sendError(400, "Invalid URI");
618 }
619 }
620
621 MessageBytes decodedURI = req.decodedURI();
622
623 if (undecodedURI.getType() == MessageBytes.T_BYTES) {
624 // Copy the raw URI to the decodedURI
625 decodedURI.duplicate(undecodedURI);
626
627 // Parse the path parameters. This will:
628 // - strip out the path parameters
629 // - convert the decodedURI to bytes
630 parsePathParameters(req, request);
631
632 // URI decoding
633 // %xx decoding of the URL
634 try {
635 req.getURLDecoder().convert(decodedURI, false);
636 } catch (IOException ioe) {
637 response.sendError(400, "Invalid URI: " + ioe.getMessage());
638 }
639 // Normalization
640 if (normalize(req.decodedURI())) {
641 // Character decoding
642 convertURI(decodedURI, request);
643 // Check that the URI is still normalized
644 if (!checkNormalize(req.decodedURI())) {
645 response.sendError(400, "Invalid URI");
646 }
647 } else {
648 response.sendError(400, "Invalid URI");
649 }
650 } else {
651 /* The URI is chars or String, and has been sent using an in-memory
652 * protocol handler. The following assumptions are made:
653 * - req.requestURI() has been set to the 'original' non-decoded,
654 * non-normalized URI
655 * - req.decodedURI() has been set to the decoded, normalized form
656 * of req.requestURI()
657 */
658 decodedURI.toChars();
659 // Remove all path parameters; any needed path parameter should be set
660 // using the request object rather than passing it in the URL
661 CharChunk uriCC = decodedURI.getCharChunk();
662 int semicolon = uriCC.indexOf(';');
663 if (semicolon > 0) {
664 decodedURI.setChars(uriCC.getBuffer(), uriCC.getStart(), semicolon);
665 }
666 }
667
668 // Request mapping.
669 MessageBytes serverName;
670 if (connector.getUseIPVHosts()) {
671 serverName = req.localName();
672 if (serverName.isNull()) {
673 // well, they did ask for it
674 res.action(ActionCode.REQ_LOCAL_NAME_ATTRIBUTE, null);
675 }
676 } else {
677 serverName = req.serverName();
678 }
679
680 // Version for the second mapping loop and
681 // Context that we expect to get for that version
682 String version = null;
683 Context versionContext = null;
684 boolean mapRequired = true;
685
686 if (response.isError()) {
687 // An error this early means the URI is invalid. Ensure invalid data
688 // is not passed to the mapper. Note we still want the mapper to
689 // find the correct host.
690 decodedURI.recycle();
691 }
692
693 while (mapRequired) {
694 // This will map the the latest version by default
695 connector.getService().getMapper().map(serverName, decodedURI,
696 version, request.getMappingData());
697
698 // If there is no context at this point, either this is a 404
699 // because no ROOT context has been deployed or the URI was invalid
700 // so no context could be mapped.
701 if (request.getContext() == null) {
702 // Don't overwrite an existing error
703 if (!response.isError()) {
704 response.sendError(404, "Not found");
705 }
706 // Allow processing to continue.
707 // If present, the error reporting valve will provide a response
708 // body.
709 return true;
710 }
711
712 // Now we have the context, we can parse the session ID from the URL
713 // (if any). Need to do this before we redirect in case we need to
714 // include the session id in the redirect
715 String sessionID;
716 if (request.getServletContext().getEffectiveSessionTrackingModes()
717 .contains(SessionTrackingMode.URL)) {
718
719 // Get the session ID if there was one
720 sessionID = request.getPathParameter(
721 SessionConfig.getSessionUriParamName(
722 request.getContext()));
723 if (sessionID != null) {
724 request.setRequestedSessionId(sessionID);
725 request.setRequestedSessionURL(true);
726 }
727 }
728
729 // Look for session ID in cookies and SSL session
730 try {
731 parseSessionCookiesId(request);
732 } catch (IllegalArgumentException e) {
733 // Too many cookies
734 if (!response.isError()) {
735 response.setError();
736 response.sendError(400);
737 }
738 return true;
739 }
740 parseSessionSslId(request);
741
742 sessionID = request.getRequestedSessionId();
743
744 mapRequired = false;
745 if (version != null && request.getContext() == versionContext) {
746 // We got the version that we asked for. That is it.
747 } else {
748 version = null;
749 versionContext = null;
750
751 Context[] contexts = request.getMappingData().contexts;
752 // Single contextVersion means no need to remap
753 // No session ID means no possibility of remap
754 if (contexts != null && sessionID != null) {
755 // Find the context associated with the session
756 for (int i = contexts.length; i > 0; i--) {
757 Context ctxt = contexts[i - 1];
758 if (ctxt.getManager().findSession(sessionID) != null) {
759 // We found a context. Is it the one that has
760 // already been mapped?
761 if (!ctxt.equals(request.getMappingData().context)) {
762 // Set version so second time through mapping
763 // the correct context is found
764 version = ctxt.getWebappVersion();
765 versionContext = ctxt;
766 // Reset mapping
767 request.getMappingData().recycle();
768 mapRequired = true;
769 // Recycle cookies and session info in case the
770 // correct context is configured with different
771 // settings
772 request.recycleSessionInfo();
773 request.recycleCookieInfo(true);
774 }
775 break;
776 }
777 }
778 }
779 }
780
781 if (!mapRequired && request.getContext().getPaused()) {
782 // Found a matching context but it is paused. Mapping data will
783 // be wrong since some Wrappers may not be registered at this
784 // point.
785 try {
786 Thread.sleep(1000);
787 } catch (InterruptedException e) {
788 // Should never happen
789 }
790 // Reset mapping
791 request.getMappingData().recycle();
792 mapRequired = true;
793 }
794 }
795
796 // Possible redirect
797 MessageBytes redirectPathMB = request.getMappingData().redirectPath;
798 if (!redirectPathMB.isNull()) {
799 String redirectPath = URLEncoder.DEFAULT.encode(
800 redirectPathMB.toString(), StandardCharsets.UTF_8);
801 String query = request.getQueryString();
802 if (request.isRequestedSessionIdFromURL()) {
803 // This is not optimal, but as this is not very common, it
804 // shouldn't matter
805 redirectPath = redirectPath + ";" +
806 SessionConfig.getSessionUriParamName(
807 request.getContext()) +
808 "=" + request.getRequestedSessionId();
809 }
810 if (query != null) {
811 // This is not optimal, but as this is not very common, it
812 // shouldn't matter
813 redirectPath = redirectPath + "?" + query;
814 }
815 response.sendRedirect(redirectPath);
816 request.getContext().logAccess(request, response, 0, true);
817 return false;
818 }
819
820 // Filter trace method
821 if (!connector.getAllowTrace()
822 && req.method().equalsIgnoreCase("TRACE")) {
823 Wrapper wrapper = request.getWrapper();
824 String header = null;
825 if (wrapper != null) {
826 String[] methods = wrapper.getServletMethods();
827 if (methods != null) {
828 for (int i=0; i < methods.length; i++) {
829 if ("TRACE".equals(methods[i])) {
830 continue;
831 }
832 if (header == null) {
833 header = methods[i];
834 } else {
835 header += ", " + methods[i];
836 }
837 }
838 }
839 }
840 if (header != null) {
841 res.addHeader("Allow", header);
842 }
843 response.sendError(405, "TRACE method is not allowed");
844 // Safe to skip the remainder of this method.
845 return true;
846 }
847
848 doConnectorAuthenticationAuthorization(req, request);
849
850 return true;
851 }
852
853
854 private void doConnectorAuthenticationAuthorization(org.apache.coyote.Request req, Request request) {
855 // Set the remote principal
856 String username = req.getRemoteUser().toString();
857 if (username != null) {
858 if (log.isDebugEnabled()) {
859 log.debug(sm.getString("coyoteAdapter.authenticate", username));
860 }
861 if (req.getRemoteUserNeedsAuthorization()) {
862 Authenticator authenticator = request.getContext().getAuthenticator();
863 if (!(authenticator instanceof AuthenticatorBase)) {
864 if (log.isDebugEnabled()) {
865 log.debug(sm.getString("coyoteAdapter.authorize", username));
866 }
867 // Custom authenticator that may not trigger authorization.
868 // Do the authorization here to make sure it is done.
869 request.setUserPrincipal(
870 request.getContext().getRealm().authenticate(username));
871 }
872 // If the Authenticator is an instance of AuthenticatorBase then
873 // it will check req.getRemoteUserNeedsAuthorization() and
874 // trigger authorization as necessary. It will also cache the
875 // result preventing excessive calls to the Realm.
876 } else {
877 // The connector isn't configured for authorization. Create a
878 // user without any roles using the supplied user name.
879 request.setUserPrincipal(new CoyotePrincipal(username));
880 }
881 }
882
883 // Set the authorization type
884 String authType = req.getAuthType().toString();
885 if (authType != null) {
886 request.setAuthType(authType);
887 }
888 }
889
890
891 /**
892 * Extract the path parameters from the request. This assumes parameters are
893 * of the form /path;name=value;name2=value2/ etc. Currently only really
894 * interested in the session ID that will be in this form. Other parameters
895 * can safely be ignored.
896 *
897 * @param req The Coyote request object
898 * @param request The Servlet request object
899 */
900 protected void parsePathParameters(org.apache.coyote.Request req,
901 Request request) {
902
903 // Process in bytes (this is default format so this is normally a NO-OP
904 req.decodedURI().toBytes();
905
906 ByteChunk uriBC = req.decodedURI().getByteChunk();
907 int semicolon = uriBC.indexOf(';', 0);
908 // Performance optimisation. Return as soon as it is known there are no
909 // path parameters;
910 if (semicolon == -1) {
911 return;
912 }
913
914 // What encoding to use? Some platforms, eg z/os, use a default
915 // encoding that doesn't give the expected result so be explicit
916 Charset charset = connector.getURICharset();
917
918 if (log.isDebugEnabled()) {
919 log.debug(sm.getString("coyoteAdapter.debug", "uriBC",
920 uriBC.toString()));
921 log.debug(sm.getString("coyoteAdapter.debug", "semicolon",
922 String.valueOf(semicolon)));
923 log.debug(sm.getString("coyoteAdapter.debug", "enc", charset.name()));
924 }
925
926 while (semicolon > -1) {
927 // Parse path param, and extract it from the decoded request URI
928 int start = uriBC.getStart();
929 int end = uriBC.getEnd();
930
931 int pathParamStart = semicolon + 1;
932 int pathParamEnd = ByteChunk.findBytes(uriBC.getBuffer(),
933 start + pathParamStart, end,
934 new byte[] {';', '/'});
935
936 String pv = null;
937
938 if (pathParamEnd >= 0) {
939 if (charset != null) {
940 pv = new String(uriBC.getBuffer(), start + pathParamStart,
941 pathParamEnd - pathParamStart, charset);
942 }
943 // Extract path param from decoded request URI
944 byte[] buf = uriBC.getBuffer();
945 for (int i = 0; i < end - start - pathParamEnd; i++) {
946 buf[start + semicolon + i]
947 = buf[start + i + pathParamEnd];
948 }
949 uriBC.setBytes(buf, start,
950 end - start - pathParamEnd + semicolon);
951 } else {
952 if (charset != null) {
953 pv = new String(uriBC.getBuffer(), start + pathParamStart,
954 (end - start) - pathParamStart, charset);
955 }
956 uriBC.setEnd(start + semicolon);
957 }
958
959 if (log.isDebugEnabled()) {
960 log.debug(sm.getString("coyoteAdapter.debug", "pathParamStart",
961 String.valueOf(pathParamStart)));
962 log.debug(sm.getString("coyoteAdapter.debug", "pathParamEnd",
963 String.valueOf(pathParamEnd)));
964 log.debug(sm.getString("coyoteAdapter.debug", "pv", pv));
965 }
966
967 if (pv != null) {
968 int equals = pv.indexOf('=');
969 if (equals > -1) {
970 String name = pv.substring(0, equals);
971 String value = pv.substring(equals + 1);
972 request.addPathParameter(name, value);
973 if (log.isDebugEnabled()) {
974 log.debug(sm.getString("coyoteAdapter.debug", "equals",
975 String.valueOf(equals)));
976 log.debug(sm.getString("coyoteAdapter.debug", "name",
977 name));
978 log.debug(sm.getString("coyoteAdapter.debug", "value",
979 value));
980 }
981 }
982 }
983
984 semicolon = uriBC.indexOf(';', semicolon);
985 }
986 }
987
988
989 /**
990 * Look for SSL session ID if required. Only look for SSL Session ID if it
991 * is the only tracking method enabled.
992 *
993 * @param request The Servlet request object
994 */
995 protected void parseSessionSslId(Request request) {
996 if (request.getRequestedSessionId() == null &&
997 SSL_ONLY.equals(request.getServletContext()
998 .getEffectiveSessionTrackingModes()) &&
999 request.connector.secure) {
1000 String sessionId = (String) request.getAttribute(SSLSupport.SESSION_ID_KEY);
1001 if (sessionId != null) {
1002 request.setRequestedSessionId(sessionId);
1003 request.setRequestedSessionSSL(true);
1004 }
1005 }
1006 }
1007
1008
1009 /**
1010 * Parse session id in Cookie.
1011 *
1012 * @param request The Servlet request object
1013 */
1014 protected void parseSessionCookiesId(Request request) {
1015
1016 // If session tracking via cookies has been disabled for the current
1017 // context, don't go looking for a session ID in a cookie as a cookie
1018 // from a parent context with a session ID may be present which would
1019 // overwrite the valid session ID encoded in the URL
1020 Context context = request.getMappingData().context;
1021 if (context != null && !context.getServletContext()
1022 .getEffectiveSessionTrackingModes().contains(
1023 SessionTrackingMode.COOKIE)) {
1024 return;
1025 }
1026
1027 // Parse session id from cookies
1028 ServerCookies serverCookies = request.getServerCookies();
1029 int count = serverCookies.getCookieCount();
1030 if (count <= 0) {
1031 return;
1032 }
1033
1034 String sessionCookieName = SessionConfig.getSessionCookieName(context);
1035
1036 for (int i = 0; i < count; i++) {
1037 ServerCookie scookie = serverCookies.getCookie(i);
1038 if (scookie.getName().equals(sessionCookieName)) {
1039 // Override anything requested in the URL
1040 if (!request.isRequestedSessionIdFromCookie()) {
1041 // Accept only the first session id cookie
1042 convertMB(scookie.getValue());
1043 request.setRequestedSessionId
1044 (scookie.getValue().toString());
1045 request.setRequestedSessionCookie(true);
1046 request.setRequestedSessionURL(false);
1047 if (log.isDebugEnabled()) {
1048 log.debug(" Requested cookie session id is " +
1049 request.getRequestedSessionId());
1050 }
1051 } else {
1052 if (!request.isRequestedSessionIdValid()) {
1053 // Replace the session id until one is valid
1054 convertMB(scookie.getValue());
1055 request.setRequestedSessionId
1056 (scookie.getValue().toString());
1057 }
1058 }
1059 }
1060 }
1061
1062 }
1063
1064
1065 /**
1066 * Character conversion of the URI.
1067 *
1068 * @param uri MessageBytes object containing the URI
1069 * @param request The Servlet request object
1070 * @throws IOException if a IO exception occurs sending an error to the client
1071 */
1072 protected void convertURI(MessageBytes uri, Request request) throws IOException {
1073
1074 ByteChunk bc = uri.getByteChunk();
1075 int length = bc.getLength();
1076 CharChunk cc = uri.getCharChunk();
1077 cc.allocate(length, -1);
1078
1079 Charset charset = connector.getURICharset();
1080
1081 B2CConverter conv = request.getURIConverter();
1082 if (conv == null) {
1083 conv = new B2CConverter(charset, true);
1084 request.setURIConverter(conv);
1085 } else {
1086 conv.recycle();
1087 }
1088
1089 try {
1090 conv.convert(bc, cc, true);
1091 uri.setChars(cc.getBuffer(), cc.getStart(), cc.getLength());
1092 } catch (IOException ioe) {
1093 // Should never happen as B2CConverter should replace
1094 // problematic characters
1095 request.getResponse().sendError(HttpServletResponse.SC_BAD_REQUEST);
1096 }
1097 }
1098
1099
1100 /**
1101 * Character conversion of the a US-ASCII MessageBytes.
1102 *
1103 * @param mb The MessageBytes instance containing the bytes that should be converted to chars
1104 */
1105 protected void convertMB(MessageBytes mb) {
1106
1107 // This is of course only meaningful for bytes
1108 if (mb.getType() != MessageBytes.T_BYTES) {
1109 return;
1110 }
1111
1112 ByteChunk bc = mb.getByteChunk();
1113 CharChunk cc = mb.getCharChunk();
1114 int length = bc.getLength();
1115 cc.allocate(length, -1);
1116
1117 // Default encoding: fast conversion
1118 byte[] bbuf = bc.getBuffer();
1119 char[] cbuf = cc.getBuffer();
1120 int start = bc.getStart();
1121 for (int i = 0; i < length; i++) {
1122 cbuf[i] = (char) (bbuf[i + start] & 0xff);
1123 }
1124 mb.setChars(cbuf, 0, length);
1125
1126 }
1127
1128
1129 /**
1130 * This method normalizes "\", "//", "/./" and "/../".
1131 *
1132 * @param uriMB URI to be normalized
1133 *
1134 * @return <code>false</code> if normalizing this URI would require going
1135 * above the root, or if the URI contains a null byte, otherwise
1136 * <code>true</code>
1137 */
1138 public static boolean normalize(MessageBytes uriMB) {
1139
1140 ByteChunk uriBC = uriMB.getByteChunk();
1141 final byte[] b = uriBC.getBytes();
1142 final int start = uriBC.getStart();
1143 int end = uriBC.getEnd();
1144
1145 // An empty URL is not acceptable
1146 if (start == end) {
1147 return false;
1148 }
1149
1150 int pos = 0;
1151 int index = 0;
1152
1153 // Replace '\' with '/'
1154 // Check for null byte
1155 for (pos = start; pos < end; pos++) {
1156 if (b[pos] == (byte) '\\') {
1157 if (ALLOW_BACKSLASH) {
1158 b[pos] = (byte) '/';
1159 } else {
1160 return false;
1161 }
1162 }
1163 if (b[pos] == (byte) 0) {
1164 return false;
1165 }
1166 }
1167
1168 // The URL must start with '/'
1169 if (b[start] != (byte) '/') {
1170 return false;
1171 }
1172
1173 // Replace "//" with "/"
1174 for (pos = start; pos < (end - 1); pos++) {
1175 if (b[pos] == (byte) '/') {
1176 while ((pos + 1 < end) && (b[pos + 1] == (byte) '/')) {
1177 copyBytes(b, pos, pos + 1, end - pos - 1);
1178 end--;
1179 }
1180 }
1181 }
1182
1183 // If the URI ends with "/." or "/..", then we append an extra "/"
1184 // Note: It is possible to extend the URI by 1 without any side effect
1185 // as the next character is a non-significant WS.
1186 if (((end - start) >= 2) && (b[end - 1] == (byte) '.')) {
1187 if ((b[end - 2] == (byte) '/')
1188 || ((b[end - 2] == (byte) '.')
1189 && (b[end - 3] == (byte) '/'))) {
1190 b[end] = (byte) '/';
1191 end++;
1192 }
1193 }
1194
1195 uriBC.setEnd(end);
1196
1197 index = 0;
1198
1199 // Resolve occurrences of "/./" in the normalized path
1200 while (true) {
1201 index = uriBC.indexOf("/./", 0, 3, index);
1202 if (index < 0) {
1203 break;
1204 }
1205 copyBytes(b, start + index, start + index + 2,
1206 end - start - index - 2);
1207 end = end - 2;
1208 uriBC.setEnd(end);
1209 }
1210
1211 index = 0;
1212
1213 // Resolve occurrences of "/../" in the normalized path
1214 while (true) {
1215 index = uriBC.indexOf("/../", 0, 4, index);
1216 if (index < 0) {
1217 break;
1218 }
1219 // Prevent from going outside our context
1220 if (index == 0) {
1221 return false;
1222 }
1223 int index2 = -1;
1224 for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos --) {
1225 if (b[pos] == (byte) '/') {
1226 index2 = pos;
1227 }
1228 }
1229 copyBytes(b, start + index2, start + index + 3,
1230 end - start - index - 3);
1231 end = end + index2 - index - 3;
1232 uriBC.setEnd(end);
1233 index = index2;
1234 }
1235
1236 return true;
1237
1238 }
1239
1240
1241 /**
1242 * Check that the URI is normalized following character decoding. This
1243 * method checks for "\", 0, "//", "/./" and "/../".
1244 *
1245 * @param uriMB URI to be checked (should be chars)
1246 *
1247 * @return <code>false</code> if sequences that are supposed to be
1248 * normalized are still present in the URI, otherwise
1249 * <code>true</code>
1250 */
1251 public static boolean checkNormalize(MessageBytes uriMB) {
1252
1253 CharChunk uriCC = uriMB.getCharChunk();
1254 char[] c = uriCC.getChars();
1255 int start = uriCC.getStart();
1256 int end = uriCC.getEnd();
1257
1258 int pos = 0;
1259
1260 // Check for '\' and 0
1261 for (pos = start; pos < end; pos++) {
1262 if (c[pos] == '\\') {
1263 return false;
1264 }
1265 if (c[pos] == 0) {
1266 return false;
1267 }
1268 }
1269
1270 // Check for "//"
1271 for (pos = start; pos < (end - 1); pos++) {
1272 if (c[pos] == '/') {
1273 if (c[pos + 1] == '/') {
1274 return false;
1275 }
1276 }
1277 }
1278
1279 // Check for ending with "/." or "/.."
1280 if (((end - start) >= 2) && (c[end - 1] == '.')) {
1281 if ((c[end - 2] == '/')
1282 || ((c[end - 2] == '.')
1283 && (c[end - 3] == '/'))) {
1284 return false;
1285 }
1286 }
1287
1288 // Check for "/./"
1289 if (uriCC.indexOf("/./", 0, 3, 0) >= 0) {
1290 return false;
1291 }
1292
1293 // Check for "/../"
1294 if (uriCC.indexOf("/../", 0, 4, 0) >= 0) {
1295 return false;
1296 }
1297
1298 return true;
1299
1300 }
1301
1302
1303 // ------------------------------------------------------ Protected Methods
1304
1305
1306 /**
1307 * Copy an array of bytes to a different position. Used during
1308 * normalization.
1309 *
1310 * @param b The bytes that should be copied
1311 * @param dest Destination offset
1312 * @param src Source offset
1313 * @param len Length
1314 */
1315 protected static void copyBytes(byte[] b, int dest, int src, int len) {
1316 System.arraycopy(b, src, b, dest, len);
1317 }
1318 }
1319