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.rest.api.Constants;
023import ca.uhn.fhir.rest.server.BaseRestfulResponse;
024import ca.uhn.fhir.util.IoUtil;
025import jakarta.annotation.Nonnull;
026import jakarta.servlet.ServletOutputStream;
027import jakarta.servlet.http.HttpServletResponse;
028import org.apache.commons.lang3.StringUtils;
029import org.apache.commons.lang3.Validate;
030
031import java.io.Closeable;
032import java.io.IOException;
033import java.io.OutputStream;
034import java.io.OutputStreamWriter;
035import java.io.Writer;
036import java.nio.charset.StandardCharsets;
037import java.util.List;
038import java.util.Map.Entry;
039import java.util.zip.GZIPOutputStream;
040
041public class ServletRestfulResponse extends BaseRestfulResponse<ServletRequestDetails> {
042
043        private Writer myWriter;
044        private OutputStream myOutputStream;
045
046        /**
047         * Constructor
048         */
049        public ServletRestfulResponse(ServletRequestDetails servletRequestDetails) {
050                super(servletRequestDetails);
051        }
052
053        @Nonnull
054        @Override
055        public OutputStream getResponseOutputStream(int theStatusCode, String theContentType, Integer theContentLength)
056                        throws IOException {
057                Validate.isTrue(myWriter == null, "getResponseOutputStream() called multiple times");
058                Validate.isTrue(myOutputStream == null, "getResponseOutputStream() called after getResponseWriter()");
059
060                addHeaders();
061                HttpServletResponse httpResponse = getRequestDetails().getServletResponse();
062                httpResponse.setStatus(theStatusCode);
063                httpResponse.setContentType(theContentType);
064                httpResponse.setCharacterEncoding(null);
065                if (theContentLength != null) {
066                        httpResponse.setContentLength(theContentLength);
067                }
068                myOutputStream = httpResponse.getOutputStream();
069                return myOutputStream;
070        }
071
072        @Nonnull
073        @Override
074        public Writer getResponseWriter(int theStatusCode, String theContentType, String theCharset, boolean theRespondGzip)
075                        throws IOException {
076                Validate.isTrue(myOutputStream == null, "getResponseWriter() called after getResponseOutputStream()");
077
078                addHeaders();
079                HttpServletResponse theHttpResponse = getRequestDetails().getServletResponse();
080                theHttpResponse.setCharacterEncoding(theCharset);
081                theHttpResponse.setStatus(theStatusCode);
082                theHttpResponse.setContentType(theContentType);
083                if (theRespondGzip) {
084                        theHttpResponse.addHeader(Constants.HEADER_CONTENT_ENCODING, Constants.ENCODING_GZIP);
085                        ServletOutputStream outputStream = theHttpResponse.getOutputStream();
086                        myWriter = new OutputStreamWriter(new GZIPOutputStream(outputStream), StandardCharsets.UTF_8);
087                        return myWriter;
088                }
089
090                myWriter = theHttpResponse.getWriter();
091                return myWriter;
092        }
093
094        private void addHeaders() {
095                HttpServletResponse httpResponse = getRequestDetails().getServletResponse();
096                getRequestDetails().getServer().addHeadersToResponse(httpResponse);
097                for (Entry<String, List<String>> header : getHeaders().entrySet()) {
098                        String key = header.getKey();
099                        key = sanitizeHeaderField(key);
100                        boolean first = true;
101                        for (String value : header.getValue()) {
102                                value = sanitizeHeaderField(value);
103
104                                // existing headers should be overridden
105                                if (first) {
106                                        httpResponse.setHeader(key, value);
107                                        first = false;
108                                } else {
109                                        httpResponse.addHeader(key, value);
110                                }
111                        }
112                }
113        }
114
115        @Override
116        public final Object commitResponse(@Nonnull Closeable theWriterOrOutputStream) {
117                IoUtil.closeQuietly(theWriterOrOutputStream);
118                return null;
119        }
120
121        static String sanitizeHeaderField(String theKey) {
122                return StringUtils.replaceChars(theKey, "\r\n", null);
123        }
124}