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(nullfalse);
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(falsenull);
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(falsenull);
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(falsenull);
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(falsenull);
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