1 /*
2  * JBoss, Home of Professional Open Source.
3  * Copyright 2014 Red Hat, Inc., and individual contributors
4  * as indicated by the @author tags.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  */

18
19 package io.undertow.servlet.handlers;
20
21 import io.undertow.UndertowLogger;
22 import io.undertow.UndertowMessages;
23 import io.undertow.server.DefaultByteBufferPool;
24 import io.undertow.server.HttpHandler;
25 import io.undertow.server.HttpServerExchange;
26 import io.undertow.server.HttpUpgradeListener;
27 import io.undertow.server.SSLSessionInfo;
28 import io.undertow.server.ServerConnection;
29 import io.undertow.server.XnioBufferPoolAdaptor;
30 import io.undertow.servlet.api.Deployment;
31 import io.undertow.servlet.api.ExceptionHandler;
32 import io.undertow.servlet.api.LoggingExceptionHandler;
33 import io.undertow.servlet.api.ServletDispatcher;
34 import io.undertow.servlet.api.ThreadSetupHandler;
35 import io.undertow.servlet.core.ApplicationListeners;
36 import io.undertow.servlet.core.ServletBlockingHttpExchange;
37 import io.undertow.servlet.spec.AsyncContextImpl;
38 import io.undertow.servlet.spec.HttpServletRequestImpl;
39 import io.undertow.servlet.spec.HttpServletResponseImpl;
40 import io.undertow.servlet.spec.RequestDispatcherImpl;
41 import io.undertow.servlet.spec.ServletContextImpl;
42 import io.undertow.util.HttpString;
43 import io.undertow.util.Protocols;
44 import io.undertow.util.StatusCodes;
45 import org.xnio.ChannelListener;
46 import org.xnio.Option;
47 import org.xnio.OptionMap;
48 import io.undertow.connector.ByteBufferPool;
49 import org.xnio.Pool;
50 import org.xnio.StreamConnection;
51 import org.xnio.XnioIoThread;
52 import org.xnio.XnioWorker;
53 import org.xnio.channels.ConnectedChannel;
54 import org.xnio.conduits.ConduitStreamSinkChannel;
55 import org.xnio.conduits.ConduitStreamSourceChannel;
56 import org.xnio.conduits.StreamSinkConduit;
57
58 import javax.servlet.DispatcherType;
59 import javax.servlet.ServletException;
60 import javax.servlet.ServletRequest;
61 import javax.servlet.ServletResponse;
62 import javax.servlet.http.HttpServletRequest;
63 import javax.servlet.http.HttpServletResponse;
64 import java.io.IOException;
65 import java.net.SocketAddress;
66 import java.nio.ByteBuffer;
67 import java.security.AccessController;
68 import java.security.PrivilegedExceptionAction;
69 import java.util.Map;
70 import java.util.concurrent.Executor;
71
72 /**
73  * This must be the initial handler in the blocking servlet chain. This sets up the request and response objects,
74  * and attaches them the to exchange.
75  *
76  * @author Stuart Douglas
77  */

78 public class ServletInitialHandler implements HttpHandler, ServletDispatcher {
79
80     private static final RuntimePermission PERMISSION = new RuntimePermission("io.undertow.servlet.CREATE_INITIAL_HANDLER");
81
82     private final HttpHandler next;
83     //private final HttpHandler asyncPath;
84
85     private final ThreadSetupHandler.Action<Object, ServletRequestContext> firstRequestHandler;
86
87     private final ServletContextImpl servletContext;
88
89     private final ApplicationListeners listeners;
90
91     private final ServletPathMatches paths;
92
93     private final ExceptionHandler exceptionHandler;
94     private final HttpHandler dispatchHandler = new HttpHandler() {
95         @Override
96         public void handleRequest(final HttpServerExchange exchange) throws Exception {
97             final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
98             if (System.getSecurityManager() == null) {
99                 dispatchRequest(exchange, servletRequestContext, servletRequestContext.getOriginalServletPathMatch().getServletChain(), DispatcherType.REQUEST);
100             } else {
101                 //sometimes thread pools inherit some random
102                 AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
103                     @Override
104                     public Object run() throws Exception {
105                         dispatchRequest(exchange, servletRequestContext, servletRequestContext.getOriginalServletPathMatch().getServletChain(), DispatcherType.REQUEST);
106                         return null;
107                     }
108                 });
109             }
110         }
111     };
112
113     public ServletInitialHandler(final ServletPathMatches paths, final HttpHandler next, final Deployment deployment, final ServletContextImpl servletContext) {
114         this.next = next;
115         this.servletContext = servletContext;
116         this.paths = paths;
117         this.listeners = servletContext.getDeployment().getApplicationListeners();
118         SecurityManager sm = System.getSecurityManager();
119         if(sm != null) {
120             //handle request can use doPrivilidged
121             //we need to make sure this is not abused
122             sm.checkPermission(PERMISSION);
123         }
124         ExceptionHandler handler = servletContext.getDeployment().getDeploymentInfo().getExceptionHandler();
125         if(handler != null) {
126              this.exceptionHandler = handler;
127         } else {
128             this.exceptionHandler = LoggingExceptionHandler.DEFAULT;
129         }
130         this.firstRequestHandler = deployment.createThreadSetupAction(new ThreadSetupHandler.Action<Object, ServletRequestContext>() {
131             @Override
132             public Object call(HttpServerExchange exchange, ServletRequestContext context) throws Exception {
133                 handleFirstRequest(exchange, context);
134                 return null;
135             }
136         });
137     }
138
139     @Override
140     public void handleRequest(final HttpServerExchange exchange) throws Exception {
141         final String path = exchange.getRelativePath();
142         if(isForbiddenPath(path)) {
143             exchange.setStatusCode(StatusCodes.NOT_FOUND);
144             return;
145         }
146         final ServletPathMatch info = paths.getServletHandlerByPath(path);
147         if (info.getType() == ServletPathMatch.Type.REWRITE) {
148             // this can only happen if the path ends with a /
149             // otherwise there would be a redirect instead
150             exchange.setRelativePath(info.getRewriteLocation());
151             exchange.setRequestPath(exchange.getResolvedPath() + info.getRewriteLocation());
152         }
153         final HttpServletResponseImpl response = new HttpServletResponseImpl(exchange, servletContext);
154         final HttpServletRequestImpl request = new HttpServletRequestImpl(exchange, servletContext);
155         final ServletRequestContext servletRequestContext = new ServletRequestContext(servletContext.getDeployment(), request, response, info);
156         //set the max request size if applicable
157         if (info.getServletChain().getManagedServlet().getMaxRequestSize() > 0) {
158             exchange.setMaxEntitySize(info.getServletChain().getManagedServlet().getMaxRequestSize());
159         }
160         exchange.putAttachment(ServletRequestContext.ATTACHMENT_KEY, servletRequestContext);
161
162         exchange.startBlocking(new ServletBlockingHttpExchange(exchange));
163         servletRequestContext.setServletPathMatch(info);
164
165         Executor executor = info.getServletChain().getExecutor();
166         if (executor == null) {
167             executor = servletContext.getDeployment().getExecutor();
168         }
169
170         if (exchange.isInIoThread() || executor != null) {
171             //either the exchange has not been dispatched yet, or we need to use a special executor
172             exchange.dispatch(executor, dispatchHandler);
173         } else {
174             dispatchRequest(exchange, servletRequestContext, info.getServletChain(), DispatcherType.REQUEST);
175         }
176     }
177
178     private boolean isForbiddenPath(String path) {
179         return path.equalsIgnoreCase("/meta-inf/")
180             || path.regionMatches(true, 0, "/web-inf/", 0, "/web-inf/".length());
181     }
182
183     public void dispatchToPath(final HttpServerExchange exchange, final ServletPathMatch pathInfo, final DispatcherType dispatcherType) throws Exception {
184         final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
185         servletRequestContext.setServletPathMatch(pathInfo);
186         dispatchRequest(exchange, servletRequestContext, pathInfo.getServletChain(), dispatcherType);
187     }
188
189     @Override
190     public void dispatchToServlet(final HttpServerExchange exchange, final ServletChain servletchain, final DispatcherType dispatcherType) throws Exception {
191         final ServletRequestContext servletRequestContext = exchange.getAttachment(ServletRequestContext.ATTACHMENT_KEY);
192
193         DispatcherType oldDispatch = servletRequestContext.getDispatcherType();
194         ServletChain oldChain = servletRequestContext.getCurrentServlet();
195         try {
196             dispatchRequest(exchange, servletRequestContext, servletchain, dispatcherType);
197         } finally {
198             servletRequestContext.setDispatcherType(oldDispatch);
199             servletRequestContext.setCurrentServlet(oldChain);
200         }
201     }
202
203     @Override
204     public void dispatchMockRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException {
205
206         final DefaultByteBufferPool bufferPool = new DefaultByteBufferPool(false, 1024, 0, 0);
207         MockServerConnection connection = new MockServerConnection(bufferPool);
208         HttpServerExchange exchange = new HttpServerExchange(connection);
209         exchange.setRequestScheme(request.getScheme());
210         exchange.setRequestMethod(new HttpString(request.getMethod()));
211         exchange.setProtocol(Protocols.HTTP_1_0);
212         exchange.setResolvedPath(request.getContextPath());
213         String relative;
214         if (request.getPathInfo() == null) {
215             relative = request.getServletPath();
216         } else {
217             relative = request.getServletPath() + request.getPathInfo();
218         }
219         exchange.setRelativePath(relative);
220         final ServletPathMatch info = paths.getServletHandlerByPath(request.getServletPath());
221         final HttpServletResponseImpl oResponse = new HttpServletResponseImpl(exchange, servletContext);
222         final HttpServletRequestImpl oRequest = new HttpServletRequestImpl(exchange, servletContext);
223         final ServletRequestContext servletRequestContext = new ServletRequestContext(servletContext.getDeployment(), oRequest, oResponse, info);
224         servletRequestContext.setServletRequest(request);
225         servletRequestContext.setServletResponse(response);
226         //set the max request size if applicable
227         if (info.getServletChain().getManagedServlet().getMaxRequestSize() > 0) {
228             exchange.setMaxEntitySize(info.getServletChain().getManagedServlet().getMaxRequestSize());
229         }
230         exchange.putAttachment(ServletRequestContext.ATTACHMENT_KEY, servletRequestContext);
231
232         exchange.startBlocking(new ServletBlockingHttpExchange(exchange));
233         servletRequestContext.setServletPathMatch(info);
234
235         try {
236             dispatchRequest(exchange, servletRequestContext, info.getServletChain(), DispatcherType.REQUEST);
237         } catch (Exception e) {
238             if (e instanceof RuntimeException) {
239                 throw (RuntimeException) e;
240             }
241             throw new ServletException(e);
242         }
243     }
244
245     private void dispatchRequest(final HttpServerExchange exchange, final ServletRequestContext servletRequestContext, final ServletChain servletChain, final DispatcherType dispatcherType) throws Exception {
246         servletRequestContext.setDispatcherType(dispatcherType);
247         servletRequestContext.setCurrentServlet(servletChain);
248         if (dispatcherType == DispatcherType.REQUEST || dispatcherType == DispatcherType.ASYNC) {
249             firstRequestHandler.call(exchange, servletRequestContext);
250         } else {
251             next.handleRequest(exchange);
252         }
253     }
254
255     private void handleFirstRequest(final HttpServerExchange exchange, ServletRequestContext servletRequestContext) throws Exception {
256         ServletRequest request = servletRequestContext.getServletRequest();
257         ServletResponse response = servletRequestContext.getServletResponse();
258         //set request attributes from the connector
259         //generally this is only applicable if apache is sending AJP_ prefixed environment variables
260         Map<String, String> attrs = exchange.getAttachment(HttpServerExchange.REQUEST_ATTRIBUTES);
261         if(attrs != null) {
262             for(Map.Entry<String, String> entry : attrs.entrySet()) {
263                 request.setAttribute(entry.getKey(), entry.getValue());
264             }
265         }
266         servletRequestContext.setRunningInsideHandler(true);
267         try {
268             listeners.requestInitialized(request);
269             next.handleRequest(exchange);
270             AsyncContextImpl asyncContextInternal = servletRequestContext.getOriginalRequest().getAsyncContextInternal();
271             if(asyncContextInternal != null && asyncContextInternal.isCompletedBeforeInitialRequestDone()) {
272                 asyncContextInternal.handleCompletedBeforeInitialRequestDone();
273             }
274             //
275             if(servletRequestContext.getErrorCode() > 0) {
276                 servletRequestContext.getOriginalResponse().doErrorDispatch(servletRequestContext.getErrorCode(), servletRequestContext.getErrorMessage());
277             }
278         } catch (Throwable t) {
279
280             servletRequestContext.setRunningInsideHandler(false);
281             AsyncContextImpl asyncContextInternal = servletRequestContext.getOriginalRequest().getAsyncContextInternal();
282             if(asyncContextInternal != null && asyncContextInternal.isCompletedBeforeInitialRequestDone()) {
283                 asyncContextInternal.handleCompletedBeforeInitialRequestDone();
284             }
285             if(asyncContextInternal != null) {
286                 asyncContextInternal.initialRequestFailed();
287             }
288             //by default this will just log the exception
289             boolean handled = exceptionHandler.handleThrowable(exchange, request, response, t);
290
291             if(handled) {
292                 exchange.endExchange();
293             } else if (request.isAsyncStarted() || request.getDispatcherType() == DispatcherType.ASYNC) {
294                 exchange.unDispatch();
295                 servletRequestContext.getOriginalRequest().getAsyncContextInternal().handleError(t);
296             } else {
297                 if (!exchange.isResponseStarted()) {
298                     response.reset();                       //reset the response
299                     exchange.setStatusCode(StatusCodes.INTERNAL_SERVER_ERROR);
300                     exchange.getResponseHeaders().clear();
301                     String location = servletContext.getDeployment().getErrorPages().getErrorLocation(t);
302                     if (location == null) {
303                         location = servletContext.getDeployment().getErrorPages().getErrorLocation(StatusCodes.INTERNAL_SERVER_ERROR);
304                     }
305                     if (location != null) {
306                         RequestDispatcherImpl dispatcher = new RequestDispatcherImpl(location, servletContext);
307                         try {
308                             dispatcher.error(servletRequestContext, request, response, servletRequestContext.getOriginalServletPathMatch().getServletChain().getManagedServlet().getServletInfo().getName(), t);
309                         } catch (Exception e) {
310                             UndertowLogger.REQUEST_LOGGER.exceptionGeneratingErrorPage(e, location);
311                         }
312                     } else {
313                         if (servletRequestContext.displayStackTraces()) {
314                             ServletDebugPageHandler.handleRequest(exchange, servletRequestContext, t);
315                         } else {
316                             servletRequestContext.getOriginalResponse().doErrorDispatch(StatusCodes.INTERNAL_SERVER_ERROR, StatusCodes.INTERNAL_SERVER_ERROR_STRING);
317                         }
318                     }
319                 }
320             }
321
322         } finally {
323             servletRequestContext.setRunningInsideHandler(false);
324             listeners.requestDestroyed(request);
325         }
326         //if it is not dispatched and is not a mock request
327         if (!exchange.isDispatched() && !(exchange.getConnection() instanceof MockServerConnection)) {
328             servletRequestContext.getOriginalResponse().responseDone();
329         }
330         if(!exchange.isDispatched()) {
331             AsyncContextImpl ctx = servletRequestContext.getOriginalRequest().getAsyncContextInternal();
332             if(ctx != null) {
333                 ctx.complete();
334             }
335         }
336     }
337
338     public HttpHandler getNext() {
339         return next;
340     }
341
342     private static class MockServerConnection extends ServerConnection {
343         private final ByteBufferPool bufferPool;
344         private SSLSessionInfo sslSessionInfo;
345         private XnioBufferPoolAdaptor poolAdaptor;
346         private MockServerConnection(ByteBufferPool bufferPool) {
347             this.bufferPool = bufferPool;
348         }
349
350         @Override
351         public Pool<ByteBuffer> getBufferPool() {
352             if(poolAdaptor == null) {
353                 poolAdaptor = new XnioBufferPoolAdaptor(getByteBufferPool());
354             }
355             return poolAdaptor;
356         }
357
358
359         @Override
360         public ByteBufferPool getByteBufferPool() {
361             return bufferPool;
362         }
363
364         @Override
365         public XnioWorker getWorker() {
366             return null;
367         }
368
369         @Override
370         public XnioIoThread getIoThread() {
371             return null;
372         }
373
374         @Override
375         public HttpServerExchange sendOutOfBandResponse(HttpServerExchange exchange) {
376             throw UndertowMessages.MESSAGES.outOfBandResponseNotSupported();
377         }
378
379         @Override
380         public boolean isContinueResponseSupported() {
381             return false;
382         }
383
384         @Override
385         public void terminateRequestChannel(HttpServerExchange exchange) {
386
387         }
388
389         @Override
390         public boolean isOpen() {
391             return true;
392         }
393
394         @Override
395         public boolean supportsOption(Option<?> option) {
396             return false;
397         }
398
399         @Override
400         public <T> T getOption(Option<T> option) throws IOException {
401             return null;
402         }
403
404         @Override
405         public <T> T setOption(Option<T> option, T value) throws IllegalArgumentException, IOException {
406             return null;
407         }
408
409         @Override
410         public void close() throws IOException {
411         }
412
413         @Override
414         public SocketAddress getPeerAddress() {
415             return null;
416         }
417
418         @Override
419         public <A extends SocketAddress> A getPeerAddress(Class<A> type) {
420             return null;
421         }
422
423         @Override
424         public ChannelListener.Setter<? extends ConnectedChannel> getCloseSetter() {
425             return null;
426         }
427
428         @Override
429         public SocketAddress getLocalAddress() {
430             return null;
431         }
432
433         @Override
434         public <A extends SocketAddress> A getLocalAddress(Class<A> type) {
435             return null;
436         }
437
438         @Override
439         public OptionMap getUndertowOptions() {
440             return OptionMap.EMPTY;
441         }
442
443         @Override
444         public int getBufferSize() {
445             return 1024;
446         }
447
448         @Override
449         public SSLSessionInfo getSslSessionInfo() {
450             return sslSessionInfo;
451         }
452
453         @Override
454         public void setSslSessionInfo(SSLSessionInfo sessionInfo) {
455             sslSessionInfo = sessionInfo;
456         }
457
458         @Override
459         public void addCloseListener(CloseListener listener) {
460         }
461
462         @Override
463         public StreamConnection upgradeChannel() {
464             return null;
465         }
466
467         @Override
468         public ConduitStreamSinkChannel getSinkChannel() {
469             return null;
470         }
471
472         @Override
473         public ConduitStreamSourceChannel getSourceChannel() {
474             return new ConduitStreamSourceChannel(nullnull);
475         }
476
477         @Override
478         protected StreamSinkConduit getSinkConduit(HttpServerExchange exchange, StreamSinkConduit conduit) {
479             return conduit;
480         }
481
482         @Override
483         protected boolean isUpgradeSupported() {
484             return false;
485         }
486
487         @Override
488         protected boolean isConnectSupported() {
489             return false;
490         }
491
492         @Override
493         protected void exchangeComplete(HttpServerExchange exchange) {
494         }
495
496         @Override
497         protected void setUpgradeListener(HttpUpgradeListener upgradeListener) {
498             //ignore
499         }
500
501         @Override
502         protected void setConnectListener(HttpUpgradeListener connectListener) {
503             //ignore
504         }
505
506         @Override
507         protected void maxEntitySizeUpdated(HttpServerExchange exchange) {
508         }
509
510         @Override
511         public String getTransportProtocol() {
512             return "mock";
513         }
514
515         @Override
516         public boolean isRequestTrailerFieldsSupported() {
517             return false;
518         }
519     }
520
521 }
522