001/* 002 * Copyright 2002-2017 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.util; 018 019import java.io.BufferedReader; 020import java.io.ByteArrayOutputStream; 021import java.io.IOException; 022import java.io.InputStreamReader; 023import java.net.URLEncoder; 024import java.util.Arrays; 025import java.util.Enumeration; 026import java.util.Iterator; 027import java.util.List; 028import java.util.Map; 029import javax.servlet.ServletInputStream; 030import javax.servlet.http.HttpServletRequest; 031import javax.servlet.http.HttpServletRequestWrapper; 032 033import org.springframework.http.HttpMethod; 034 035/** 036 * {@link javax.servlet.http.HttpServletRequest} wrapper that caches all content read from 037 * the {@linkplain #getInputStream() input stream} and {@linkplain #getReader() reader}, 038 * and allows this content to be retrieved via a {@link #getContentAsByteArray() byte array}. 039 * 040 * <p>Used e.g. by {@link org.springframework.web.filter.AbstractRequestLoggingFilter}. 041 * 042 * @author Juergen Hoeller 043 * @author Brian Clozel 044 * @since 4.1.3 045 * @see ContentCachingResponseWrapper 046 */ 047public class ContentCachingRequestWrapper extends HttpServletRequestWrapper { 048 049 private static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded"; 050 051 052 private final ByteArrayOutputStream cachedContent; 053 054 private final Integer contentCacheLimit; 055 056 private ServletInputStream inputStream; 057 058 private BufferedReader reader; 059 060 061 /** 062 * Create a new ContentCachingRequestWrapper for the given servlet request. 063 * @param request the original servlet request 064 */ 065 public ContentCachingRequestWrapper(HttpServletRequest request) { 066 super(request); 067 int contentLength = request.getContentLength(); 068 this.cachedContent = new ByteArrayOutputStream(contentLength >= 0 ? contentLength : 1024); 069 this.contentCacheLimit = null; 070 } 071 072 /** 073 * Create a new ContentCachingRequestWrapper for the given servlet request. 074 * @param request the original servlet request 075 * @param contentCacheLimit the maximum number of bytes to cache per request 076 * @since 4.3.6 077 * @see #handleContentOverflow(int) 078 */ 079 public ContentCachingRequestWrapper(HttpServletRequest request, int contentCacheLimit) { 080 super(request); 081 this.cachedContent = new ByteArrayOutputStream(contentCacheLimit); 082 this.contentCacheLimit = contentCacheLimit; 083 } 084 085 086 @Override 087 public ServletInputStream getInputStream() throws IOException { 088 if (this.inputStream == null) { 089 this.inputStream = new ContentCachingInputStream(getRequest().getInputStream()); 090 } 091 return this.inputStream; 092 } 093 094 @Override 095 public String getCharacterEncoding() { 096 String enc = super.getCharacterEncoding(); 097 return (enc != null ? enc : WebUtils.DEFAULT_CHARACTER_ENCODING); 098 } 099 100 @Override 101 public BufferedReader getReader() throws IOException { 102 if (this.reader == null) { 103 this.reader = new BufferedReader(new InputStreamReader(getInputStream(), getCharacterEncoding())); 104 } 105 return this.reader; 106 } 107 108 @Override 109 public String getParameter(String name) { 110 if (this.cachedContent.size() == 0 && isFormPost()) { 111 writeRequestParametersToCachedContent(); 112 } 113 return super.getParameter(name); 114 } 115 116 @Override 117 public Map<String, String[]> getParameterMap() { 118 if (this.cachedContent.size() == 0 && isFormPost()) { 119 writeRequestParametersToCachedContent(); 120 } 121 return super.getParameterMap(); 122 } 123 124 @Override 125 public Enumeration<String> getParameterNames() { 126 if (this.cachedContent.size() == 0 && isFormPost()) { 127 writeRequestParametersToCachedContent(); 128 } 129 return super.getParameterNames(); 130 } 131 132 @Override 133 public String[] getParameterValues(String name) { 134 if (this.cachedContent.size() == 0 && isFormPost()) { 135 writeRequestParametersToCachedContent(); 136 } 137 return super.getParameterValues(name); 138 } 139 140 141 private boolean isFormPost() { 142 String contentType = getContentType(); 143 return (contentType != null && contentType.contains(FORM_CONTENT_TYPE) && 144 HttpMethod.POST.matches(getMethod())); 145 } 146 147 private void writeRequestParametersToCachedContent() { 148 try { 149 if (this.cachedContent.size() == 0) { 150 String requestEncoding = getCharacterEncoding(); 151 Map<String, String[]> form = super.getParameterMap(); 152 for (Iterator<String> nameIterator = form.keySet().iterator(); nameIterator.hasNext(); ) { 153 String name = nameIterator.next(); 154 List<String> values = Arrays.asList(form.get(name)); 155 for (Iterator<String> valueIterator = values.iterator(); valueIterator.hasNext(); ) { 156 String value = valueIterator.next(); 157 this.cachedContent.write(URLEncoder.encode(name, requestEncoding).getBytes()); 158 if (value != null) { 159 this.cachedContent.write('='); 160 this.cachedContent.write(URLEncoder.encode(value, requestEncoding).getBytes()); 161 if (valueIterator.hasNext()) { 162 this.cachedContent.write('&'); 163 } 164 } 165 } 166 if (nameIterator.hasNext()) { 167 this.cachedContent.write('&'); 168 } 169 } 170 } 171 } 172 catch (IOException ex) { 173 throw new IllegalStateException("Failed to write request parameters to cached content", ex); 174 } 175 } 176 177 /** 178 * Return the cached request content as a byte array. 179 * <p>The returned array will never be larger than the content cache limit. 180 * @see #ContentCachingRequestWrapper(HttpServletRequest, int) 181 */ 182 public byte[] getContentAsByteArray() { 183 return this.cachedContent.toByteArray(); 184 } 185 186 /** 187 * Template method for handling a content overflow: specifically, a request 188 * body being read that exceeds the specified content cache limit. 189 * <p>The default implementation is empty. Subclasses may override this to 190 * throw a payload-too-large exception or the like. 191 * @param contentCacheLimit the maximum number of bytes to cache per request 192 * which has just been exceeded 193 * @since 4.3.6 194 * @see #ContentCachingRequestWrapper(HttpServletRequest, int) 195 */ 196 protected void handleContentOverflow(int contentCacheLimit) { 197 } 198 199 200 private class ContentCachingInputStream extends ServletInputStream { 201 202 private final ServletInputStream is; 203 204 private boolean overflow = false; 205 206 public ContentCachingInputStream(ServletInputStream is) { 207 this.is = is; 208 } 209 210 @Override 211 public int read() throws IOException { 212 int ch = this.is.read(); 213 if (ch != -1 && !this.overflow) { 214 if (contentCacheLimit != null && cachedContent.size() == contentCacheLimit) { 215 this.overflow = true; 216 handleContentOverflow(contentCacheLimit); 217 } 218 else { 219 cachedContent.write(ch); 220 } 221 } 222 return ch; 223 } 224 } 225 226}