001/* 002 * Copyright 2002-2020 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.bind.support; 018 019import java.util.List; 020import java.util.Map; 021import javax.servlet.http.HttpServletRequest; 022import javax.servlet.http.Part; 023 024import org.springframework.beans.MutablePropertyValues; 025import org.springframework.util.ClassUtils; 026import org.springframework.util.LinkedMultiValueMap; 027import org.springframework.util.MultiValueMap; 028import org.springframework.util.StringUtils; 029import org.springframework.validation.BindException; 030import org.springframework.web.bind.WebDataBinder; 031import org.springframework.web.context.request.NativeWebRequest; 032import org.springframework.web.context.request.WebRequest; 033import org.springframework.web.multipart.MultipartException; 034import org.springframework.web.multipart.MultipartRequest; 035 036/** 037 * Special {@link org.springframework.validation.DataBinder} to perform data binding 038 * from web request parameters to JavaBeans, including support for multipart files. 039 * 040 * <p>See the DataBinder/WebDataBinder superclasses for customization options, 041 * which include specifying allowed/required fields, and registering custom 042 * property editors. 043 * 044 * <p>Can also used for manual data binding in custom web controllers or interceptors 045 * that build on Spring's {@link org.springframework.web.context.request.WebRequest} 046 * abstraction: e.g. in a {@link org.springframework.web.context.request.WebRequestInterceptor} 047 * implementation. Simply instantiate a WebRequestDataBinder for each binding 048 * process, and invoke {@code bind} with the current WebRequest as argument: 049 * 050 * <pre class="code"> 051 * MyBean myBean = new MyBean(); 052 * // apply binder to custom target object 053 * WebRequestDataBinder binder = new WebRequestDataBinder(myBean); 054 * // register custom editors, if desired 055 * binder.registerCustomEditor(...); 056 * // trigger actual binding of request parameters 057 * binder.bind(request); 058 * // optionally evaluate binding errors 059 * Errors errors = binder.getErrors(); 060 * ...</pre> 061 * 062 * @author Juergen Hoeller 063 * @author Brian Clozel 064 * @since 2.5.2 065 * @see #bind(org.springframework.web.context.request.WebRequest) 066 * @see #registerCustomEditor 067 * @see #setAllowedFields 068 * @see #setRequiredFields 069 * @see #setFieldMarkerPrefix 070 */ 071public class WebRequestDataBinder extends WebDataBinder { 072 073 private static final boolean servlet3Parts = ClassUtils.hasMethod(HttpServletRequest.class, "getParts"); 074 075 076 /** 077 * Create a new WebRequestDataBinder instance, with default object name. 078 * @param target the target object to bind onto (or {@code null} 079 * if the binder is just used to convert a plain parameter value) 080 * @see #DEFAULT_OBJECT_NAME 081 */ 082 public WebRequestDataBinder(Object target) { 083 super(target); 084 } 085 086 /** 087 * Create a new WebRequestDataBinder instance. 088 * @param target the target object to bind onto (or {@code null} 089 * if the binder is just used to convert a plain parameter value) 090 * @param objectName the name of the target object 091 */ 092 public WebRequestDataBinder(Object target, String objectName) { 093 super(target, objectName); 094 } 095 096 097 /** 098 * Bind the parameters of the given request to this binder's target, 099 * also binding multipart files in case of a multipart request. 100 * <p>This call can create field errors, representing basic binding 101 * errors like a required field (code "required"), or type mismatch 102 * between value and bean property (code "typeMismatch"). 103 * <p>Multipart files are bound via their parameter name, just like normal 104 * HTTP parameters: i.e. "uploadedFile" to an "uploadedFile" bean property, 105 * invoking a "setUploadedFile" setter method. 106 * <p>The type of the target property for a multipart file can be Part, MultipartFile, 107 * byte[], or String. The latter two receive the contents of the uploaded file; 108 * all metadata like original file name, content type, etc are lost in those cases. 109 * @param request the request with parameters to bind (can be multipart) 110 * @see org.springframework.web.multipart.MultipartRequest 111 * @see org.springframework.web.multipart.MultipartFile 112 * @see javax.servlet.http.Part 113 * @see #bind(org.springframework.beans.PropertyValues) 114 */ 115 public void bind(WebRequest request) { 116 MutablePropertyValues mpvs = new MutablePropertyValues(request.getParameterMap()); 117 if (request instanceof NativeWebRequest) { 118 MultipartRequest multipartRequest = ((NativeWebRequest) request).getNativeRequest(MultipartRequest.class); 119 if (multipartRequest != null) { 120 bindMultipart(multipartRequest.getMultiFileMap(), mpvs); 121 } 122 else if (servlet3Parts && isMultipartRequest(request)) { 123 HttpServletRequest serlvetRequest = ((NativeWebRequest) request).getNativeRequest(HttpServletRequest.class); 124 new Servlet3MultipartHelper(isBindEmptyMultipartFiles()).bindParts(serlvetRequest, mpvs); 125 } 126 } 127 doBind(mpvs); 128 } 129 130 /** 131 * Check if the request is a multipart request (by checking its Content-Type header). 132 * @param request the request with parameters to bind 133 */ 134 private boolean isMultipartRequest(WebRequest request) { 135 String contentType = request.getHeader("Content-Type"); 136 return StringUtils.startsWithIgnoreCase(contentType, "multipart/"); 137 } 138 139 /** 140 * Treats errors as fatal. 141 * <p>Use this method only if it's an error if the input isn't valid. 142 * This might be appropriate if all input is from dropdowns, for example. 143 * @throws BindException if binding errors have been encountered 144 */ 145 public void closeNoCatch() throws BindException { 146 if (getBindingResult().hasErrors()) { 147 throw new BindException(getBindingResult()); 148 } 149 } 150 151 152 /** 153 * Encapsulate Part binding code for Servlet 3.0+ only containers. 154 * @see javax.servlet.http.Part 155 */ 156 private static class Servlet3MultipartHelper { 157 158 private final boolean bindEmptyMultipartFiles; 159 160 public Servlet3MultipartHelper(boolean bindEmptyMultipartFiles) { 161 this.bindEmptyMultipartFiles = bindEmptyMultipartFiles; 162 } 163 164 public void bindParts(HttpServletRequest request, MutablePropertyValues mpvs) { 165 try { 166 MultiValueMap<String, Part> map = new LinkedMultiValueMap<String, Part>(); 167 for (Part part : request.getParts()) { 168 map.add(part.getName(), part); 169 } 170 for (Map.Entry<String, List<Part>> entry: map.entrySet()) { 171 if (entry.getValue().size() == 1) { 172 Part part = entry.getValue().get(0); 173 if (this.bindEmptyMultipartFiles || part.getSize() > 0) { 174 mpvs.add(entry.getKey(), part); 175 } 176 } 177 else { 178 mpvs.add(entry.getKey(), entry.getValue()); 179 } 180 } 181 } 182 catch (Exception ex) { 183 throw new MultipartException("Failed to get request parts", ex); 184 } 185 } 186 } 187 188}