001package ca.uhn.fhir.rest.server.interceptor; 002 003/*- 004 * #%L 005 * HAPI FHIR - Server Framework 006 * %% 007 * Copyright (C) 2014 - 2022 Smile CDR, Inc. 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.i18n.Msg; 024import ca.uhn.fhir.context.FhirContext; 025import ca.uhn.fhir.interceptor.api.Hook; 026import ca.uhn.fhir.interceptor.api.Interceptor; 027import ca.uhn.fhir.interceptor.api.Pointcut; 028import ca.uhn.fhir.rest.api.Constants; 029import ca.uhn.fhir.rest.api.RestOperationTypeEnum; 030import ca.uhn.fhir.rest.api.server.RequestDetails; 031import ca.uhn.fhir.rest.server.RestfulServer; 032import ca.uhn.fhir.rest.server.RestfulServerUtils; 033import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; 034import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 035import org.hl7.fhir.instance.model.api.IBaseResource; 036import org.hl7.fhir.instance.model.api.IPrimitiveType; 037 038import javax.servlet.http.HttpServletRequest; 039import javax.servlet.http.HttpServletResponse; 040import java.io.IOException; 041import java.util.Collections; 042import java.util.HashSet; 043import java.util.Optional; 044import java.util.Set; 045 046import static org.apache.commons.lang3.StringUtils.isBlank; 047 048/** 049 * This interceptor allows a client to request that a Media resource be 050 * served as the raw contents of the resource, assuming either: 051 * <ul> 052 * <li>The client explicitly requests the correct content type using the Accept header</li> 053 * <li>The client explicitly requests raw output by adding the parameter <code>_output=data</code></li> 054 * </ul> 055 */ 056@Interceptor 057public class ServeMediaResourceRawInterceptor { 058 059 public static final String MEDIA_CONTENT_CONTENT_TYPE_OPT = "Media.content.contentType"; 060 061 private static final Set<RestOperationTypeEnum> RESPOND_TO_OPERATION_TYPES; 062 063 static { 064 Set<RestOperationTypeEnum> respondToOperationTypes = new HashSet<>(); 065 respondToOperationTypes.add(RestOperationTypeEnum.READ); 066 respondToOperationTypes.add(RestOperationTypeEnum.VREAD); 067 RESPOND_TO_OPERATION_TYPES = Collections.unmodifiableSet(respondToOperationTypes); 068 } 069 070 @Hook(value=Pointcut.SERVER_OUTGOING_RESPONSE, order = InterceptorOrders.SERVE_MEDIA_RESOURCE_RAW_INTERCEPTOR) 071 public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException { 072 if (theResponseObject == null) { 073 return true; 074 } 075 076 077 FhirContext context = theRequestDetails.getFhirContext(); 078 String resourceName = context.getResourceType(theResponseObject); 079 080 // Are we serving a FHIR read request on the Media resource type 081 if (!"Media".equals(resourceName) || !RESPOND_TO_OPERATION_TYPES.contains(theRequestDetails.getRestOperationType())) { 082 return true; 083 } 084 085 // What is the content type of the Media resource we're returning? 086 String contentType = null; 087 Optional<IPrimitiveType> contentTypeOpt = context.newFluentPath().evaluateFirst(theResponseObject, MEDIA_CONTENT_CONTENT_TYPE_OPT, IPrimitiveType.class); 088 if (contentTypeOpt.isPresent()) { 089 contentType = contentTypeOpt.get().getValueAsString(); 090 } 091 092 // What is the data of the Media resource we're returning? 093 IPrimitiveType<byte[]> data = null; 094 Optional<IPrimitiveType> dataOpt = context.newFluentPath().evaluateFirst(theResponseObject, "Media.content.data", IPrimitiveType.class); 095 if (dataOpt.isPresent()) { 096 data = dataOpt.get(); 097 } 098 099 if (isBlank(contentType) || data == null) { 100 return true; 101 } 102 103 RestfulServerUtils.ResponseEncoding responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequestDetails, null, contentType); 104 if (responseEncoding != null) { 105 if (contentType.equals(responseEncoding.getContentType())) { 106 returnRawResponse(theRequestDetails, theServletResponse, contentType, data); 107 return false; 108 109 } 110 } 111 112 String[] outputParam = theRequestDetails.getParameters().get("_output"); 113 if (outputParam != null && "data".equals(outputParam[0])) { 114 returnRawResponse(theRequestDetails, theServletResponse, contentType, data); 115 return false; 116 } 117 118 return true; 119 } 120 121 private void returnRawResponse(RequestDetails theRequestDetails, HttpServletResponse theServletResponse, String theContentType, IPrimitiveType<byte[]> theData) { 122 theServletResponse.setStatus(200); 123 if (theRequestDetails.getServer() instanceof RestfulServer) { 124 RestfulServer rs = (RestfulServer) theRequestDetails.getServer(); 125 rs.addHeadersToResponse(theServletResponse); 126 } 127 128 theServletResponse.addHeader(Constants.HEADER_CONTENT_TYPE, theContentType); 129 130 // Write the response 131 try { 132 theServletResponse.getOutputStream().write(theData.getValue()); 133 theServletResponse.getOutputStream().close(); 134 } catch (IOException e) { 135 throw new InternalErrorException(Msg.code(321) + e); 136 } 137 } 138}