View Javadoc
1   /*
2    * The contents of this file are subject to the terms of the Common Development and
3    * Distribution License (the License). You may not use this file except in compliance with the
4    * License.
5    *
6    * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
7    * specific language governing permission and limitations under the License.
8    *
9    * When distributing Covered Software, include this CDDL Header Notice in each file and include
10   * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
11   * Header, with the fields enclosed by brackets [] replaced by your own identifying
12   * information: "Portions Copyright [year] [name of copyright owner]".
13   *
14   * Copyright 2016 ForgeRock AS.
15   * Portions Copyright 2026 Wren Security
16   */
17  package org.forgerock.http.grizzly;
18  
19  import static org.forgerock.http.handler.Handlers.asDescribableHandler;
20  import static org.forgerock.http.handler.Handlers.chainOf;
21  import static org.forgerock.http.handler.Handlers.internalServerErrorHandler;
22  import static org.forgerock.http.io.IO.newBranchingInputStream;
23  import static org.forgerock.http.io.IO.newTemporaryStorage;
24  import static org.forgerock.http.protocol.Responses.newInternalServerError;
25  import static org.forgerock.http.routing.UriRouterContext.uriRouterContext;
26  import static org.forgerock.util.Utils.closeSilently;
27  
28  import io.swagger.v3.oas.models.OpenAPI;
29  import java.io.File;
30  import java.io.IOException;
31  import java.net.URISyntaxException;
32  import java.security.cert.X509Certificate;
33  import java.util.ArrayList;
34  import java.util.Arrays;
35  import org.forgerock.http.ApiProducer;
36  import org.forgerock.http.DescribedHttpApplication;
37  import org.forgerock.http.Handler;
38  import org.forgerock.http.HttpApplication;
39  import org.forgerock.http.HttpApplicationException;
40  import org.forgerock.http.filter.TransactionIdInboundFilter;
41  import org.forgerock.http.handler.DescribableHandler;
42  import org.forgerock.http.io.Buffer;
43  import org.forgerock.http.io.IO;
44  import org.forgerock.http.routing.UriRouterContext;
45  import org.forgerock.http.session.SessionContext;
46  import org.forgerock.http.util.CaseInsensitiveSet;
47  import org.forgerock.http.util.Uris;
48  import org.forgerock.services.context.AttributesContext;
49  import org.forgerock.services.context.ClientContext;
50  import org.forgerock.services.context.Context;
51  import org.forgerock.services.context.RequestAuditContext;
52  import org.forgerock.services.context.RootContext;
53  import org.forgerock.util.Factory;
54  import org.forgerock.util.promise.ResultHandler;
55  import org.forgerock.util.promise.RuntimeExceptionHandler;
56  import org.glassfish.grizzly.http.server.HttpHandler;
57  import org.glassfish.grizzly.http.server.Request;
58  import org.glassfish.grizzly.http.server.Response;
59  import org.glassfish.grizzly.http.server.util.Globals;
60  import org.slf4j.Logger;
61  import org.slf4j.LoggerFactory;
62  
63  /**
64   * A Grizzly implementation which provides integration between the Grizzly API and the common HTTP Framework.
65   *
66   * @see HttpApplication
67   * @see Handler
68   */
69  final class HandlerAdapter extends HttpHandler {
70  
71      /** Methods that should not include an entity body. */
72      private static final CaseInsensitiveSet NON_ENTITY_METHODS = new CaseInsensitiveSet(
73              Arrays.asList("GET", "HEAD", "TRACE"));
74  
75      private static final Logger LOGGER = LoggerFactory.getLogger(HandlerAdapter.class);
76  
77      private final HttpApplication httpApplication;
78      private final Factory<Buffer> storage;
79      private DescribableHandler describedHandler;
80  
81      HandlerAdapter(HttpApplication httpApplication) {
82          this.httpApplication = httpApplication;
83          final Factory<Buffer> applicationStorage = httpApplication.getBufferFactory();
84          this.storage = applicationStorage != null
85                  ? applicationStorage
86                  : newTemporaryStorage(new File(System.getProperty("java.io.tmpdir")));
87      }
88  
89      @Override
90      public void start() {
91          super.start();
92          try {
93              describedHandler = chainOf(httpApplication.start(), new TransactionIdInboundFilter());
94              if (httpApplication instanceof DescribedHttpApplication) {
95                  ApiProducer<OpenAPI> apiProducer = ((DescribedHttpApplication) httpApplication).getApiProducer();
96                  describedHandler.api(apiProducer);
97              }
98          } catch (HttpApplicationException e) {
99              LOGGER.error("Error while starting the application.", e);
100             describedHandler = asDescribableHandler(internalServerErrorHandler(e));
101         }
102     }
103 
104     @Override
105     public void destroy() {
106         httpApplication.stop();
107         describedHandler = null;
108         super.destroy();
109     }
110 
111     @Override
112     public void service(final Request request, final Response response) throws Exception {
113         final org.forgerock.http.protocol.Request chfRequest = toChfRequest(request);
114         final RootContext rootContext = new RootContext();
115         final SessionContext sessionContext = new SessionContext(rootContext, new SessionAdapter(request.getSession()));
116         final UriRouterContext uriRouterContext = createRouterContext(sessionContext, request, chfRequest);
117         final AttributesContext attributesContext = new AttributesContext(new RequestAuditContext(uriRouterContext));
118         final ClientContext context = createClientContext(attributesContext, request);
119 
120         // suspend IO events
121         request.getContext().suspend();
122 
123         // suspend HTTP response processing
124         response.suspend();
125 
126         describedHandler.handle(context, chfRequest)
127                 .thenOnResult(new ResultHandler<org.forgerock.http.protocol.Response>() {
128                     @Override
129                     public void handleResult(org.forgerock.http.protocol.Response chfResponse) {
130                         writeResponse(chfResponse, response, sessionContext);
131                     }
132                 })
133                 .thenOnRuntimeException(new RuntimeExceptionHandler() {
134                     @Override
135                     public void handleRuntimeException(RuntimeException e) {
136                         LOGGER.error("RuntimeException caught", e);
137                         writeResponse(
138                                 newInternalServerError(e),
139                                 response, sessionContext);
140                     }
141                 })
142                 .thenAlways(new Runnable() {
143                     @Override
144                     public void run() {
145                         response.resume();
146                     }
147                 });
148     }
149 
150     private void writeResponse(final org.forgerock.http.protocol.Response chfResponse, final Response grizzlyResponse,
151             final SessionContext sessionContext) {
152         try {
153             grizzlyResponse.setStatus(chfResponse.getStatus().getCode());
154             sessionContext.getSession().save(chfResponse);
155 
156             // response headers
157             for (String name : chfResponse.getHeaders().keySet()) {
158                 for (String value : chfResponse.getHeaders().get(name).getValues()) {
159                     if (value != null && !value.isEmpty()) {
160                         grizzlyResponse.addHeader(name, value);
161                     }
162                 }
163             }
164             IO.stream(chfResponse.getEntity().getRawContentInputStream(), grizzlyResponse.getOutputStream());
165         } catch (IOException e) {
166             LOGGER.trace("Failed to write response", e);
167         } finally {
168             closeSilently(chfResponse);
169         }
170     }
171 
172     private org.forgerock.http.protocol.Request toChfRequest(Request req) throws URISyntaxException {
173         // populate request
174         org.forgerock.http.protocol.Request request = new org.forgerock.http.protocol.Request();
175         request.setMethod(req.getMethod().toString());
176 
177         /*
178          * CHF-81: containers are generally quite tolerant of invalid query strings, so we'll try to be as well by
179          * decoding the query string and re-encoding it correctly before constructing the URI.
180          */
181         request.setUri(Uris.createNonStrict(req.getScheme(), null, req.getServerName(), req.getServerPort(),
182                 req.getRequestURI(), req.getQueryString(), null));
183 
184         // request headers
185         for (String e : req.getHeaderNames()) {
186             final ArrayList<String> values = new ArrayList<>(1);
187             for (String value : req.getHeaders(e)) {
188                 values.add(value);
189             }
190             request.getHeaders().add(e, values);
191         }
192 
193         // include request entity if appears to be provided with request
194         if ((req.getContentLength() > 0 || req.getHeader("Transfer-Encoding") != null)
195                 && !NON_ENTITY_METHODS.contains(request.getMethod())) {
196             request.setEntity(newBranchingInputStream(req.getInputStream(), storage));
197         }
198 
199         return request;
200     }
201 
202     private UriRouterContext createRouterContext(Context parent, Request req,
203             org.forgerock.http.protocol.Request request) {
204         return uriRouterContext(parent).matchedUri("").remainingUri(req.getRequestURI())
205                 .originalUri(request.getUri().asURI()).build();
206     }
207 
208     private ClientContext createClientContext(Context parent, Request req) {
209         return ClientContext.buildExternalClientContext(parent)
210                             .remoteUser(req.getRemoteUser())
211                             .remoteAddress(req.getRemoteAddr())
212                             .remotePort(req.getRemotePort())
213                             .secure("https".equalsIgnoreCase(req.getScheme()))
214                             .certificates((X509Certificate[]) req.getAttribute(Globals.CERTIFICATES_ATTR))
215                             .userAgent(req.getHeader("User-Agent"))
216                             .localAddress(req.getLocalAddr())
217                             .localPort(req.getLocalPort())
218                             .build();
219     }
220 
221 }