001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.servlet.mvc.method.annotation; 018 019import java.util.regex.Pattern; 020import javax.servlet.http.HttpServletRequest; 021 022import org.apache.commons.logging.Log; 023import org.apache.commons.logging.LogFactory; 024 025import org.springframework.core.MethodParameter; 026import org.springframework.http.MediaType; 027import org.springframework.http.converter.json.MappingJacksonValue; 028import org.springframework.http.server.ServerHttpRequest; 029import org.springframework.http.server.ServerHttpResponse; 030import org.springframework.http.server.ServletServerHttpRequest; 031import org.springframework.util.Assert; 032import org.springframework.util.ObjectUtils; 033 034/** 035 * A convenient base class for a {@code ResponseBodyAdvice} to instruct the 036 * {@link org.springframework.http.converter.json.MappingJackson2HttpMessageConverter} 037 * to serialize with JSONP formatting. 038 * 039 * <p>Sub-classes must specify the query parameter name(s) to check for the name 040 * of the JSONP callback function. 041 * 042 * <p>Sub-classes are likely to be annotated with the {@code @ControllerAdvice} 043 * annotation and auto-detected or otherwise must be registered directly with the 044 * {@code RequestMappingHandlerAdapter} and {@code ExceptionHandlerExceptionResolver}. 045 * 046 * @author Rossen Stoyanchev 047 * @since 4.1 048 * @deprecated Will be removed as of Spring Framework 5.1, use 049 * <a href="https://docs.spring.io/spring/docs/4.3.x/spring-framework-reference/html/cors.html">CORS</a> instead. 050 */ 051@Deprecated 052public abstract class AbstractJsonpResponseBodyAdvice extends AbstractMappingJacksonResponseBodyAdvice { 053 054 /** 055 * Pattern for validating jsonp callback parameter values. 056 */ 057 private static final Pattern CALLBACK_PARAM_PATTERN = Pattern.compile("[0-9A-Za-z_\\.]*"); 058 059 060 private final Log logger = LogFactory.getLog(getClass()); 061 062 private final String[] jsonpQueryParamNames; 063 064 065 protected AbstractJsonpResponseBodyAdvice(String... queryParamNames) { 066 Assert.isTrue(!ObjectUtils.isEmpty(queryParamNames), "At least one query param name is required"); 067 this.jsonpQueryParamNames = queryParamNames; 068 } 069 070 071 @Override 072 protected void beforeBodyWriteInternal(MappingJacksonValue bodyContainer, MediaType contentType, 073 MethodParameter returnType, ServerHttpRequest request, ServerHttpResponse response) { 074 075 HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest(); 076 077 for (String name : this.jsonpQueryParamNames) { 078 String value = servletRequest.getParameter(name); 079 if (value != null) { 080 if (!isValidJsonpQueryParam(value)) { 081 if (logger.isDebugEnabled()) { 082 logger.debug("Ignoring invalid jsonp parameter value: " + value); 083 } 084 continue; 085 } 086 MediaType contentTypeToUse = getContentType(contentType, request, response); 087 response.getHeaders().setContentType(contentTypeToUse); 088 bodyContainer.setJsonpFunction(value); 089 break; 090 } 091 } 092 } 093 094 /** 095 * Validate the jsonp query parameter value. The default implementation 096 * returns true if it consists of digits, letters, or "_" and ".". 097 * Invalid parameter values are ignored. 098 * @param value the query param value, never {@code null} 099 * @since 4.1.8 100 */ 101 protected boolean isValidJsonpQueryParam(String value) { 102 return CALLBACK_PARAM_PATTERN.matcher(value).matches(); 103 } 104 105 /** 106 * Return the content type to set the response to. 107 * This implementation always returns "application/javascript". 108 * @param contentType the content type selected through content negotiation 109 * @param request the current request 110 * @param response the current response 111 * @return the content type to set the response to 112 */ 113 protected MediaType getContentType(MediaType contentType, ServerHttpRequest request, ServerHttpResponse response) { 114 return new MediaType("application", "javascript"); 115 } 116 117}