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}