001package ca.uhn.fhir.rest.server.interceptor;
002
003/*-
004 * #%L
005 * HAPI FHIR - Server Framework
006 * %%
007 * Copyright (C) 2014 - 2019 University Health Network
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 *
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 *
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import ca.uhn.fhir.context.FhirContext;
024import ca.uhn.fhir.interceptor.api.Hook;
025import ca.uhn.fhir.interceptor.api.Interceptor;
026import ca.uhn.fhir.interceptor.api.Pointcut;
027import ca.uhn.fhir.rest.api.Constants;
028import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
029import ca.uhn.fhir.rest.api.server.RequestDetails;
030import ca.uhn.fhir.rest.server.RestfulServer;
031import ca.uhn.fhir.rest.server.RestfulServerUtils;
032import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
033import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
034import org.hl7.fhir.instance.model.api.IBaseResource;
035import org.hl7.fhir.instance.model.api.IPrimitiveType;
036
037import javax.servlet.http.HttpServletRequest;
038import javax.servlet.http.HttpServletResponse;
039import java.io.IOException;
040import java.util.Collections;
041import java.util.HashSet;
042import java.util.Optional;
043import java.util.Set;
044
045import static org.apache.commons.lang3.StringUtils.isBlank;
046
047/**
048 * This interceptor allows a client to request that a Media resource be
049 * served as the raw contents of the resource, assuming either:
050 * <ul>
051 * <li>The client explicitly requests the correct content type using the Accept header</li>
052 * <li>The client explicitly requests raw output by adding the parameter <code>_output=data</code></li>
053 * </ul>
054 */
055@Interceptor
056public class ServeMediaResourceRawInterceptor {
057
058        public static final String MEDIA_CONTENT_CONTENT_TYPE_OPT = "Media.content.contentType";
059
060        private static final Set<RestOperationTypeEnum> RESPOND_TO_OPERATION_TYPES;
061
062        static {
063                Set<RestOperationTypeEnum> respondToOperationTypes = new HashSet<>();
064                respondToOperationTypes.add(RestOperationTypeEnum.READ);
065                respondToOperationTypes.add(RestOperationTypeEnum.VREAD);
066                RESPOND_TO_OPERATION_TYPES = Collections.unmodifiableSet(respondToOperationTypes);
067        }
068
069        @Hook(value=Pointcut.SERVER_OUTGOING_RESPONSE, order = InterceptorOrders.SERVE_MEDIA_RESOURCE_RAW_INTERCEPTOR)
070        public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
071                if (theResponseObject == null) {
072                        return true;
073                }
074
075
076                FhirContext context = theRequestDetails.getFhirContext();
077                String resourceName = context.getResourceDefinition(theResponseObject).getName();
078
079                // Are we serving a FHIR read request on the Media resource type
080                if (!"Media".equals(resourceName) || !RESPOND_TO_OPERATION_TYPES.contains(theRequestDetails.getRestOperationType())) {
081                        return true;
082                }
083
084                // What is the content type of the Media resource we're returning?
085                String contentType = null;
086                Optional<IPrimitiveType> contentTypeOpt = context.newFluentPath().evaluateFirst(theResponseObject, MEDIA_CONTENT_CONTENT_TYPE_OPT, IPrimitiveType.class);
087                if (contentTypeOpt.isPresent()) {
088                        contentType = contentTypeOpt.get().getValueAsString();
089                }
090
091                // What is the data of the Media resource we're returning?
092                IPrimitiveType<byte[]> data = null;
093                Optional<IPrimitiveType> dataOpt = context.newFluentPath().evaluateFirst(theResponseObject, "Media.content.data", IPrimitiveType.class);
094                if (dataOpt.isPresent()) {
095                        data = dataOpt.get();
096                }
097
098                if (isBlank(contentType) || data == null) {
099                        return true;
100                }
101
102                RestfulServerUtils.ResponseEncoding responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequestDetails, null, contentType);
103                if (responseEncoding != null) {
104                        if (contentType.equals(responseEncoding.getContentType())) {
105                                returnRawResponse(theRequestDetails, theServletResponse, contentType, data);
106                                return false;
107
108                        }
109                }
110
111                String[] outputParam = theRequestDetails.getParameters().get("_output");
112                if (outputParam != null && "data".equals(outputParam[0])) {
113                        returnRawResponse(theRequestDetails, theServletResponse, contentType, data);
114                        return false;
115                }
116
117                return true;
118        }
119
120        private void returnRawResponse(RequestDetails theRequestDetails, HttpServletResponse theServletResponse, String theContentType, IPrimitiveType<byte[]> theData) {
121                theServletResponse.setStatus(200);
122                if (theRequestDetails.getServer() instanceof RestfulServer) {
123                        RestfulServer rs = (RestfulServer) theRequestDetails.getServer();
124                        rs.addHeadersToResponse(theServletResponse);
125                }
126
127                theServletResponse.addHeader(Constants.HEADER_CONTENT_TYPE, theContentType);
128
129                // Write the response
130                try {
131                        theServletResponse.getOutputStream().write(theData.getValue());
132                        theServletResponse.getOutputStream().close();
133                } catch (IOException e) {
134                        throw new InternalErrorException(e);
135                }
136        }
137}