001/*
002 * #%L
003 * HAPI FHIR - Server Framework
004 * %%
005 * Copyright (C) 2014 - 2024 Smile CDR, Inc.
006 * %%
007 * Licensed under the Apache License, Version 2.0 (the "License");
008 * you may not use this file except in compliance with the License.
009 * You may obtain a copy of the License at
010 *
011 * http://www.apache.org/licenses/LICENSE-2.0
012 *
013 * Unless required by applicable law or agreed to in writing, software
014 * distributed under the License is distributed on an "AS IS" BASIS,
015 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016 * See the License for the specific language governing permissions and
017 * limitations under the License.
018 * #L%
019 */
020package ca.uhn.fhir.rest.server.servlet;
021
022import ca.uhn.fhir.context.FhirContext;
023import ca.uhn.fhir.i18n.Msg;
024import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
025import ca.uhn.fhir.rest.api.Constants;
026import ca.uhn.fhir.rest.api.PreferHeader;
027import ca.uhn.fhir.rest.api.server.RequestDetails;
028import ca.uhn.fhir.rest.server.RestfulServer;
029import ca.uhn.fhir.rest.server.RestfulServerUtils;
030import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
031import jakarta.annotation.Nonnull;
032import jakarta.servlet.http.HttpServletRequest;
033import jakarta.servlet.http.HttpServletResponse;
034import org.apache.commons.io.IOUtils;
035import org.apache.commons.lang3.Validate;
036
037import java.io.ByteArrayInputStream;
038import java.io.IOException;
039import java.io.InputStream;
040import java.io.Reader;
041import java.nio.charset.Charset;
042import java.util.ArrayList;
043import java.util.Collections;
044import java.util.Enumeration;
045import java.util.HashMap;
046import java.util.Iterator;
047import java.util.List;
048import java.util.Map;
049import java.util.StringTokenizer;
050import java.util.zip.GZIPInputStream;
051
052import static org.apache.commons.lang3.StringUtils.isNotBlank;
053import static org.apache.commons.lang3.StringUtils.trim;
054
055public class ServletRequestDetails extends RequestDetails {
056
057        private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletRequestDetails.class);
058
059        private RestfulServer myServer;
060        private HttpServletRequest myServletRequest;
061        private HttpServletResponse myServletResponse;
062
063        /**
064         * Constructor for testing only
065         */
066        public ServletRequestDetails() {
067                this((IInterceptorBroadcaster) null);
068        }
069
070        /**
071         * Constructor
072         */
073        public ServletRequestDetails(IInterceptorBroadcaster theInterceptorBroadcaster) {
074                super(theInterceptorBroadcaster);
075                setResponse(new ServletRestfulResponse(this));
076        }
077
078        /**
079         * Copy constructor
080         */
081        public ServletRequestDetails(ServletRequestDetails theRequestDetails) {
082                super(theRequestDetails);
083
084                myServer = theRequestDetails.getServer();
085                myServletRequest = theRequestDetails.getServletRequest();
086                myServletResponse = theRequestDetails.getServletResponse();
087        }
088
089        @Override
090        protected byte[] getByteStreamRequestContents() {
091                try {
092                        InputStream inputStream = getInputStream();
093                        byte[] requestContents = IOUtils.toByteArray(inputStream);
094
095                        if (myServer.isUncompressIncomingContents()) {
096                                String contentEncoding = myServletRequest.getHeader(Constants.HEADER_CONTENT_ENCODING);
097                                if ("gzip".equals(contentEncoding)) {
098                                        ourLog.debug("Uncompressing (GZip) incoming content");
099                                        if (requestContents.length > 0) {
100                                                GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(requestContents));
101                                                requestContents = IOUtils.toByteArray(gis);
102                                        }
103                                }
104                        }
105                        return requestContents;
106                } catch (IOException e) {
107                        ourLog.error("Could not load request resource", e);
108                        throw new InvalidRequestException(
109                                        Msg.code(308) + String.format("Could not load request resource: %s", e.getMessage()));
110                }
111        }
112
113        @Override
114        public Charset getCharset() {
115                Charset charset = null;
116
117                String charsetString = myServletRequest.getCharacterEncoding();
118                if (isNotBlank(charsetString)) {
119                        charset = Charset.forName(charsetString);
120                }
121
122                return charset;
123        }
124
125        @Override
126        public FhirContext getFhirContext() {
127                return getServer().getFhirContext();
128        }
129
130        @Override
131        public String getHeader(String name) {
132                return getServletRequest().getHeader(name);
133        }
134
135        @Override
136        public List<String> getHeaders(String name) {
137                Enumeration<String> headers = getServletRequest().getHeaders(name);
138                return headers == null
139                                ? Collections.emptyList()
140                                : Collections.list(getServletRequest().getHeaders(name));
141        }
142
143        @Override
144        public Object getAttribute(String theAttributeName) {
145                Validate.notBlank(theAttributeName, "theAttributeName must not be null or blank");
146                return getServletRequest().getAttribute(theAttributeName);
147        }
148
149        @Override
150        public void setAttribute(String theAttributeName, Object theAttributeValue) {
151                Validate.notBlank(theAttributeName, "theAttributeName must not be null or blank");
152                getServletRequest().setAttribute(theAttributeName, theAttributeValue);
153        }
154
155        @Override
156        public InputStream getInputStream() throws IOException {
157                return getServletRequest().getInputStream();
158        }
159
160        @Override
161        public Reader getReader() throws IOException {
162                return getServletRequest().getReader();
163        }
164
165        @Override
166        public RestfulServer getServer() {
167                return myServer;
168        }
169
170        @Override
171        public String getServerBaseForRequest() {
172                return getServer().getServerBaseForRequest(this);
173        }
174
175        public HttpServletRequest getServletRequest() {
176                return myServletRequest;
177        }
178
179        public HttpServletResponse getServletResponse() {
180                return myServletResponse;
181        }
182
183        public void setServer(RestfulServer theServer) {
184                this.myServer = theServer;
185        }
186
187        public ServletRequestDetails setServletRequest(@Nonnull HttpServletRequest myServletRequest) {
188                this.myServletRequest = myServletRequest;
189
190                // TODO KHS move a bunch of other initialization from RestfulServer into this method
191                if ("true".equals(myServletRequest.getHeader(Constants.HEADER_REWRITE_HISTORY))) {
192                        setRewriteHistory(true);
193                }
194                setRetryFields(myServletRequest);
195                return this;
196        }
197
198        private void setRetryFields(HttpServletRequest theRequest) {
199                if (theRequest == null) {
200                        return;
201                }
202                Enumeration<String> headers = theRequest.getHeaders(Constants.HEADER_RETRY_ON_VERSION_CONFLICT);
203                if (headers != null) {
204                        Iterator<String> headerIterator = headers.asIterator();
205                        while (headerIterator.hasNext()) {
206                                String headerValue = headerIterator.next();
207                                if (isNotBlank(headerValue)) {
208                                        StringTokenizer tok = new StringTokenizer(headerValue, ";");
209                                        while (tok.hasMoreTokens()) {
210                                                String next = trim(tok.nextToken());
211                                                if (next.equals(Constants.HEADER_RETRY)) {
212                                                        setRetry(true);
213                                                } else if (next.startsWith(Constants.HEADER_MAX_RETRIES + "=")) {
214                                                        String val = trim(next.substring((Constants.HEADER_MAX_RETRIES + "=").length()));
215                                                        int maxRetries = Integer.parseInt(val);
216                                                        setMaxRetries(maxRetries);
217                                                }
218                                        }
219                                }
220                        }
221                }
222        }
223
224        public void setServletResponse(HttpServletResponse myServletResponse) {
225                this.myServletResponse = myServletResponse;
226        }
227
228        public Map<String, List<String>> getHeaders() {
229                Map<String, List<String>> retVal = new HashMap<>();
230                Enumeration<String> names = myServletRequest.getHeaderNames();
231                while (names.hasMoreElements()) {
232                        String nextName = names.nextElement();
233                        ArrayList<String> headerValues = new ArrayList<>();
234                        retVal.put(nextName, headerValues);
235                        Enumeration<String> valuesEnum = myServletRequest.getHeaders(nextName);
236                        while (valuesEnum.hasMoreElements()) {
237                                headerValues.add(valuesEnum.nextElement());
238                        }
239                }
240                return Collections.unmodifiableMap(retVal);
241        }
242
243        /**
244         * Returns true if the `Prefer` header contains a value of `respond-async`
245         */
246        public boolean isPreferRespondAsync() {
247                String preferHeader = getHeader(Constants.HEADER_PREFER);
248                PreferHeader prefer = RestfulServerUtils.parsePreferHeader(null, preferHeader);
249                return prefer.getRespondAsync();
250        }
251}