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}