diff --git a/spring-test/src/main/java/org/springframework/mock/web/FastHttpDateFormat.java b/spring-test/src/main/java/org/springframework/mock/web/FastHttpDateFormat.java
new file mode 100644
index 000000000000..e40240848dfd
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/mock/web/FastHttpDateFormat.java
@@ -0,0 +1,219 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.mock.web;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.TimeZone;
+
+
+/**
+ * Utility class to generate HTTP dates.
+ *
+ * This class is based on code in Apache Tomcat.
+ *
+ * @author Remy Maucherat
+ * @author Andrey Grebnev
+ */
+public class FastHttpDateFormat {
+ //~ Static fields/initializers =====================================================================================
+
+ /** HTTP date format. */
+ protected static final SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
+
+ /** The set of SimpleDateFormat formats to use in getDateHeader()
. */
+ protected static final SimpleDateFormat[] formats = {
+ new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
+ new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
+ new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
+ };
+
+ /** GMT time zone - all HTTP dates are on GMT */
+ protected static final TimeZone gmtZone = TimeZone.getTimeZone("GMT");
+
+ static {
+ format.setTimeZone(gmtZone);
+
+ formats[0].setTimeZone(gmtZone);
+ formats[1].setTimeZone(gmtZone);
+ formats[2].setTimeZone(gmtZone);
+ }
+
+ /** Instant on which the currentDate object was generated. */
+ protected static long currentDateGenerated = 0L;
+
+ /** Current formatted date. */
+ protected static String currentDate = null;
+
+ /** Formatter cache. */
+ protected static final HashMap formatCache = new HashMap();
+
+ /** Parser cache. */
+ protected static final HashMap parseCache = new HashMap();
+
+ //~ Methods ========================================================================================================
+
+ /**
+ * Formats a specified date to HTTP format. If local format is not null
, it's used instead.
+ *
+ * @param value Date value to format
+ * @param threadLocalformat The format to use (or null
-- then HTTP format will be used)
+ *
+ * @return Formatted date
+ */
+ public static String formatDate(long value, DateFormat threadLocalformat) {
+ String cachedDate = null;
+ Long longValue = Long.valueOf(value);
+
+ try {
+ cachedDate = formatCache.get(longValue);
+ } catch (Exception ignored) {}
+
+ if (cachedDate != null) {
+ return cachedDate;
+ }
+
+ String newDate;
+ Date dateValue = new Date(value);
+
+ if (threadLocalformat != null) {
+ newDate = threadLocalformat.format(dateValue);
+
+ synchronized (formatCache) {
+ updateCache(formatCache, longValue, newDate);
+ }
+ } else {
+ synchronized (formatCache) {
+ newDate = format.format(dateValue);
+ updateCache(formatCache, longValue, newDate);
+ }
+ }
+
+ return newDate;
+ }
+
+ /**
+ * Gets the current date in HTTP format.
+ *
+ * @return Current date in HTTP format
+ */
+ public static String getCurrentDate() {
+ long now = System.currentTimeMillis();
+
+ if ((now - currentDateGenerated) > 1000) {
+ synchronized (format) {
+ if ((now - currentDateGenerated) > 1000) {
+ currentDateGenerated = now;
+ currentDate = format.format(new Date(now));
+ }
+ }
+ }
+
+ return currentDate;
+ }
+
+ /**
+ * Parses date with given formatters.
+ *
+ * @param value The string to parse
+ * @param formats Array of formats to use
+ *
+ * @return Parsed date (or null
if no formatter mached)
+ */
+ private static Long internalParseDate(String value, DateFormat[] formats) {
+ Date date = null;
+
+ for (int i = 0; (date == null) && (i < formats.length); i++) {
+ try {
+ date = formats[i].parse(value);
+ } catch (ParseException ignored) {
+ }
+ }
+
+ if (date == null) {
+ return null;
+ }
+
+ return new Long(date.getTime());
+ }
+
+ /**
+ * Tries to parse the given date as an HTTP date. If local format list is not null
, it's used
+ * instead.
+ *
+ * @param value The string to parse
+ * @param threadLocalformats Array of formats to use for parsing. If null
, HTTP formats are used.
+ *
+ * @return Parsed date (or -1 if error occurred)
+ */
+ public static long parseDate(String value, DateFormat[] threadLocalformats) {
+ Long cachedDate = null;
+
+ try {
+ cachedDate = (Long) parseCache.get(value);
+ } catch (Exception ignored) {}
+
+ if (cachedDate != null) {
+ return cachedDate.longValue();
+ }
+
+ Long date;
+
+ if (threadLocalformats != null) {
+ date = internalParseDate(value, threadLocalformats);
+
+ synchronized (parseCache) {
+ updateCache(parseCache, value, date);
+ }
+ } else {
+ synchronized (parseCache) {
+ date = internalParseDate(value, formats);
+ updateCache(parseCache, value, date);
+ }
+ }
+
+ if (date == null) {
+ return (-1L);
+ } else {
+ return date.longValue();
+ }
+ }
+
+ /**
+ * Updates cache.
+ *
+ * @param cache Cache to be updated
+ * @param key Key to be updated
+ * @param value New value
+ */
+ @SuppressWarnings("unchecked")
+ private static void updateCache(HashMap cache, Object key, Object value) {
+ if (value == null) {
+ return;
+ }
+
+ if (cache.size() > 1000) {
+ cache.clear();
+ }
+
+ cache.put(key, value);
+ }
+}
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java
index 8e1384c09e3d..0f1ff57406b4 100644
--- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java
@@ -24,6 +24,7 @@
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.security.Principal;
+import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
@@ -36,6 +37,7 @@
import java.util.Locale;
import java.util.Map;
import java.util.Set;
+import java.util.TimeZone;
import javax.servlet.AsyncContext;
import javax.servlet.DispatcherType;
@@ -197,7 +199,8 @@ public class MockHttpServletRequest implements HttpServletRequest {
private boolean requestedSessionIdFromURL = false;
private final Map parts = new LinkedHashMap();
-
+
+ private SimpleDateFormat[] formats;
// ---------------------------------------------------------------------
// Constructors
@@ -253,9 +256,16 @@ public MockHttpServletRequest(ServletContext servletContext, String method, Stri
this.method = method;
this.requestURI = requestURI;
this.locales.add(Locale.ENGLISH);
+
+ this.formats = new SimpleDateFormat[3];
+ this.formats[0] = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", this.getLocale());
+ this.formats[1] = new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", this.getLocale());
+ this.formats[2] = new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", this.getLocale());
+ this.formats[0].setTimeZone(TimeZone.getTimeZone("GMT"));
+ this.formats[1].setTimeZone(TimeZone.getTimeZone("GMT"));
+ this.formats[2].setTimeZone(TimeZone.getTimeZone("GMT"));
}
-
// ---------------------------------------------------------------------
// Lifecycle methods
// ---------------------------------------------------------------------
@@ -820,6 +830,9 @@ public long getDateHeader(String name) {
else if (value instanceof Number) {
return ((Number) value).longValue();
}
+ else if (value instanceof String) {
+ return FastHttpDateFormat.parseDate((String)value, this.formats);
+ }
else if (value != null) {
throw new IllegalArgumentException(
"Value for header '" + name + "' is neither a Date nor a Number: " + value);
diff --git a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java
index 395442fee684..165186341311 100644
--- a/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java
+++ b/spring-test/src/main/java/org/springframework/mock/web/MockHttpServletResponse.java
@@ -23,12 +23,16 @@
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.TimeZone;
+
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
@@ -460,9 +464,16 @@ public String getRedirectedUrl() {
return getHeader(LOCATION_HEADER);
}
+ private DateFormat format = null;
+
@Override
public void setDateHeader(String name, long value) {
- setHeaderValue(name, value);
+ if (this.format == null) {
+ this.format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", this.locale);
+ this.format.setTimeZone(TimeZone.getTimeZone("GMT"));
+ }
+
+ setHeaderValue(name, FastHttpDateFormat.formatDate(value, this.format));
}
@Override
diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java
index 35900fa6be23..9da666d19a21 100644
--- a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java
+++ b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java
@@ -16,14 +16,18 @@
package org.springframework.mock.web;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.TimeZone;
import org.junit.Test;
@@ -232,6 +236,20 @@ public void getRequestURLWithNegativePort() {
StringBuffer requestURL = request.getRequestURL();
assertEquals("http://localhost", requestURL.toString());
}
+
+ /**
+ * SPR-11912
+ */
+ @Test
+ public void getDateHeaderWithString() {
+ DateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", request.getLocale());
+ format.setTimeZone(TimeZone.getTimeZone("GMT"));
+ long currentTime = System.currentTimeMillis();
+ String formatStr = format.format(new Date(currentTime));
+ request.addHeader("testDate", formatStr);
+ assertEquals(Long.toString(currentTime).substring(0, 10),
+ Long.toString(request.getDateHeader("testDate")).subSequence(0, 10));
+ }
private void assertEqualEnumerations(Enumeration> enum1, Enumeration> enum2) {
assertNotNull(enum1);
diff --git a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java
index 55bb66b36d58..54c021615ced 100644
--- a/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java
+++ b/spring-test/src/test/java/org/springframework/mock/web/MockHttpServletResponseTests.java
@@ -17,13 +17,16 @@
package org.springframework.mock.web;
import java.io.IOException;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Date;
+import java.util.TimeZone;
import javax.servlet.http.HttpServletResponse;
import org.junit.Test;
-
import org.springframework.web.util.WebUtils;
import static org.junit.Assert.*;
@@ -243,4 +246,15 @@ public void modifyStatusMessageAfterSendError() throws IOException {
assertEquals(response.getStatus(),HttpServletResponse.SC_NOT_FOUND);
}
+ /**
+ * SPR-11912
+ */
+ @Test
+ public void getDateHeader() {
+ DateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", response.getLocale());
+ format.setTimeZone(TimeZone.getTimeZone("GMT"));
+ long currentTime = System.currentTimeMillis();
+ response.setDateHeader("testTime", currentTime);
+ assertEquals(format.format(new Date(currentTime)), response.getHeader("testTime"));
+ }
}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractFlashMapManager.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractFlashMapManager.java
index 503662876f63..6da2f7ffbbd0 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractFlashMapManager.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/AbstractFlashMapManager.java
@@ -16,17 +16,19 @@
package org.springframework.web.servlet.support;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
-
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.MultiValueMap;
@@ -35,6 +37,7 @@
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.FlashMapManager;
import org.springframework.web.util.UrlPathHelper;
+import org.springframework.web.util.WebUtils;
/**
* A base class for {@link FlashMapManager} implementations.
@@ -175,14 +178,30 @@ protected boolean isFlashMapForRequest(FlashMap flashMap, HttpServletRequest req
MultiValueMap targetParams = flashMap.getTargetRequestParams();
for (String expectedName : targetParams.keySet()) {
for (String expectedValue : targetParams.get(expectedName)) {
- if (!ObjectUtils.containsElement(request.getParameterValues(expectedName), expectedValue)) {
+ if (!ObjectUtils.containsElement(request.getParameterValues(expectedName), decodeString(request, expectedValue))) {
return false;
}
}
}
return true;
}
-
+
+ @SuppressWarnings("deprecation")
+ private String decodeString(HttpServletRequest request, String targetValue) {
+ String enc = request.getCharacterEncoding();
+ if (enc == null) {
+ enc = WebUtils.DEFAULT_CHARACTER_ENCODING;
+ }
+
+ String result;
+ try {
+ result = URLDecoder.decode(targetValue, enc);
+ } catch (UnsupportedEncodingException e) {
+ result = URLDecoder.decode(targetValue);
+ }
+ return result;
+ }
+
@Override
public final void saveOutputFlashMap(FlashMap flashMap, HttpServletRequest request, HttpServletResponse response) {
if (CollectionUtils.isEmpty(flashMap)) {
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/FlashMapManagerTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/FlashMapManagerTests.java
index f0399ada7d1e..0b4e06cd6568 100644
--- a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/FlashMapManagerTests.java
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/FlashMapManagerTests.java
@@ -16,16 +16,18 @@
package org.springframework.web.servlet.support;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
+
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Before;
import org.junit.Test;
-
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.util.MultiValueMap;
@@ -307,5 +309,29 @@ protected void updateFlashMaps(List flashMaps, HttpServletRequest requ
this.flashMaps = flashMaps;
}
}
+
+ // SPR-11821
+ @Test
+ public void retrieveAndUpdateMatchByParamsWithSpace() throws UnsupportedEncodingException {
+ this.request.setCharacterEncoding("UTF-8");
+ String enc = this.request.getCharacterEncoding();
+ if (enc == null) {
+ enc = WebUtils.DEFAULT_CHARACTER_ENCODING;
+ }
+
+ FlashMap flashMap = new FlashMap();
+ flashMap.put("key", "value");
+ flashMap.addTargetRequestParam("ab", URLEncoder.encode("a b", enc));
+ flashMap.addTargetRequestParam("abc", URLEncoder.encode("a b c", enc));
+
+ this.flashMapManager.setFlashMaps(flashMap);
+ this.request.setParameter("ab", "a b");
+ this.request.setParameter("abc", "a b c");
+ FlashMap inputFlashMap = this.flashMapManager.retrieveAndUpdate(this.request, this.response);
+
+ assertNotNull(inputFlashMap);
+ assertEquals("value", inputFlashMap.get("key"));
+ }
+
}