001/*
002 * #%L
003 * HAPI FHIR - Server Framework
004 * %%
005 * Copyright (C) 2014 - 2023 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 org.apache.commons.io.IOUtils;
032import org.apache.commons.lang3.Validate;
033
034import javax.annotation.Nonnull;
035import javax.servlet.http.HttpServletRequest;
036import javax.servlet.http.HttpServletResponse;
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(Msg.code(308) + String.format("Could not load request resource: %s", e.getMessage()));
109                }
110        }
111
112        @Override
113        public Charset getCharset() {
114                Charset charset = null;
115
116                String charsetString = myServletRequest.getCharacterEncoding();
117                if (isNotBlank(charsetString)) {
118                        charset = Charset.forName(charsetString);
119                }
120
121                return charset;
122        }
123
124        @Override
125        public FhirContext getFhirContext() {
126                return getServer().getFhirContext();
127        }
128
129        @Override
130        public String getHeader(String name) {
131                return getServletRequest().getHeader(name);
132        }
133
134        @Override
135        public List<String> getHeaders(String name) {
136                Enumeration<String> headers = getServletRequest().getHeaders(name);
137                return headers == null ? Collections.emptyList() : Collections.list(getServletRequest().getHeaders(name));
138        }
139
140        @Override
141        public Object getAttribute(String theAttributeName) {
142                Validate.notBlank(theAttributeName, "theAttributeName must not be null or blank");
143                return getServletRequest().getAttribute(theAttributeName);
144        }
145
146        @Override
147        public void setAttribute(String theAttributeName, Object theAttributeValue) {
148                Validate.notBlank(theAttributeName, "theAttributeName must not be null or blank");
149                getServletRequest().setAttribute(theAttributeName, theAttributeValue);
150        }
151
152        @Override
153        public InputStream getInputStream() throws IOException {
154                return getServletRequest().getInputStream();
155        }
156
157        @Override
158        public Reader getReader() throws IOException {
159                return getServletRequest().getReader();
160        }
161
162        @Override
163        public RestfulServer getServer() {
164                return myServer;
165        }
166
167        @Override
168        public String getServerBaseForRequest() {
169                return getServer().getServerBaseForRequest(this);
170        }
171
172        public HttpServletRequest getServletRequest() {
173                return myServletRequest;
174        }
175
176        public HttpServletResponse getServletResponse() {
177                return myServletResponse;
178        }
179
180        public void setServer(RestfulServer theServer) {
181                this.myServer = theServer;
182        }
183
184        public ServletRequestDetails setServletRequest(@Nonnull HttpServletRequest myServletRequest) {
185                this.myServletRequest = myServletRequest;
186
187                // TODO KHS move a bunch of other initialization from RestfulServer into this method
188                if ("true".equals(myServletRequest.getHeader(Constants.HEADER_REWRITE_HISTORY))) {
189                        setRewriteHistory(true);
190                }
191                setRetryFields(myServletRequest);
192                return this;
193        }
194
195        private void setRetryFields(HttpServletRequest theRequest){
196                if (theRequest == null){
197                        return;
198                }
199                Enumeration<String> headers = theRequest.getHeaders(Constants.HEADER_RETRY_ON_VERSION_CONFLICT);
200                if (headers != null) {
201                        Iterator<String> headerIterator = headers.asIterator();
202                        while(headerIterator.hasNext()){
203                                String headerValue = headerIterator.next();
204                                if (isNotBlank(headerValue)) {
205                                        StringTokenizer tok = new StringTokenizer(headerValue, ";");
206                                        while (tok.hasMoreTokens()) {
207                                                String next = trim(tok.nextToken());
208                                                if (next.equals(Constants.HEADER_RETRY)) {
209                                                        setRetry(true);
210                                                } else if (next.startsWith(Constants.HEADER_MAX_RETRIES + "=")) {
211                                                        String val = trim(next.substring((Constants.HEADER_MAX_RETRIES + "=").length()));
212                                                        int maxRetries = Integer.parseInt(val);
213                                                        setMaxRetries(maxRetries);
214                                                }
215                                        }
216                                }
217                        }
218                }
219        }
220
221        public void setServletResponse(HttpServletResponse myServletResponse) {
222                this.myServletResponse = myServletResponse;
223        }
224
225        public Map<String, List<String>> getHeaders() {
226                Map<String, List<String>> retVal = new HashMap<>();
227                Enumeration<String> names = myServletRequest.getHeaderNames();
228                while (names.hasMoreElements()) {
229                        String nextName = names.nextElement();
230                        ArrayList<String> headerValues = new ArrayList<>();
231                        retVal.put(nextName, headerValues);
232                        Enumeration<String> valuesEnum = myServletRequest.getHeaders(nextName);
233                        while (valuesEnum.hasMoreElements()) {
234                                headerValues.add(valuesEnum.nextElement());
235                        }
236                }
237                return Collections.unmodifiableMap(retVal);
238        }
239
240        /**
241         * Returns true if the `Prefer` header contains a value of `respond-async`
242         */
243        public boolean isPreferRespondAsync() {
244                String preferHeader = getHeader(Constants.HEADER_PREFER);
245                PreferHeader prefer = RestfulServerUtils.parsePreferHeader(null, preferHeader);
246                return prefer.getRespondAsync();
247        }
248
249}