Author: mheath
Date: Thu Mar 6 22:19:15 2008
New Revision: 634555
URL: http://svn.apache.org/viewvc?rev=634555&view=rev
Log:
Added support for decoding HTTP response Set-Cookie headers.
Added:
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/DateParseException.java
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/DateUtil.java
mina/asyncweb/trunk/common/src/test/catalina/webapps/ROOT/cookie.jsp
mina/asyncweb/trunk/common/src/test/catalina/webapps/ROOT/redirect.jsp
Modified:
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/DefaultHttpRequest.java
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/HttpHeaderConstants.java
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/HttpResponseDecodingState.java
mina/asyncweb/trunk/common/src/test/java/org/apache/asyncweb/common/integration/ResponseOutput.java
mina/asyncweb/trunk/common/src/test/java/org/apache/asyncweb/common/integration/SimpleHttpTest.java
Added:
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/DateParseException.java
URL:
http://svn.apache.org/viewvc/mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/DateParseException.java?rev=634555&view=auto
==============================================================================
---
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/DateParseException.java
(added)
+++
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/DateParseException.java
Thu Mar 6 22:19:15 2008
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.asyncweb.common;
+
+/**
+ * An exception to indicate an error parsing a date string.
+ *
+ * @see DateUtil
+ * @author Michael Becke
+ */
+public class DateParseException extends Exception {
+
+ /**
+ * Instantiates a new date parse exception.
+ */
+ DateParseException() {
+ super();
+ }
+
+ /**
+ * Instantiates a new date parse exception.
+ *
+ * @param message the message
+ */
+ DateParseException(String message) {
+ super(message);
+ }
+
+}
\ No newline at end of file
Added:
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/DateUtil.java
URL:
http://svn.apache.org/viewvc/mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/DateUtil.java?rev=634555&view=auto
==============================================================================
---
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/DateUtil.java
(added)
+++
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/DateUtil.java
Thu Mar 6 22:19:15 2008
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.apache.asyncweb.common;
+
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * A utility class for parsing and formatting HTTP dates as used in cookies and
+ * other headers. This class handles dates as defined by RFC 2616 section
+ * 3.3.1 as well as some other common non-standard formats.
+ *
+ * @author Christopher Brown
+ * @author Michael Becke
+ */
+final class DateUtil {
+
+ /**
+ * Date format pattern used to parse HTTP date headers in RFC 1123 format.
+ */
+ public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss
zzz";
+
+ /**
+ * Date format pattern used to parse HTTP date headers in RFC 1036 format.
+ */
+ public static final String PATTERN_RFC1036 = "EEEE, dd-MMM-yy HH:mm:ss
zzz";
+
+ /**
+ * Date format pattern used to parse HTTP date headers in ANSI C
+ * <code>asctime()</code> format.
+ */
+ public static final String PATTERN_ASCTIME = "EEE MMM d HH:mm:ss yyyy";
+
+ private static final Collection<String> DEFAULT_PATTERNS = Arrays.asList(
+ new String[] {PATTERN_ASCTIME, PATTERN_RFC1036, PATTERN_RFC1123});
+
+ private static final Date DEFAULT_TWO_DIGIT_YEAR_START;
+
+ static {
+ Calendar calendar = Calendar.getInstance();
+ calendar.set(2000, Calendar.JANUARY, 1, 0, 0);
+ DEFAULT_TWO_DIGIT_YEAR_START = calendar.getTime();
+ }
+
+ private static final TimeZone GMT = TimeZone.getTimeZone("GMT");
+
+ /**
+ * This class should not be instantiated.
+ */
+ private DateUtil() {
+ }
+
+
+ /**
+ * Parses a date value. The formats used for parsing the date value are
retrieved from
+ * the default http params.
+ *
+ * @param dateValue the date value to parse
+ * @return the parsed date
+ * @throws DateParseException if the value could not be parsed using any
of the
+ * supported date formats
+ */
+ public static Date parseDate(String dateValue) throws DateParseException {
+ return parseDate(dateValue, null, null);
+ }
+
+ /**
+ * Parses the date value using the given date formats.
+ *
+ * @param dateValue the date value to parse
+ * @param dateFormats the date formats to use
+ * @return the parsed date
+ * @throws DateParseException if none of the dataFormats could parse the
dateValue
+ */
+ public static Date parseDate(String dateValue, Collection<String>
dateFormats)
+ throws DateParseException {
+ return parseDate(dateValue, dateFormats, null);
+ }
+
+ /**
+ * Parses the date value using the given date formats.
+ *
+ * @param dateValue the date value to parse
+ * @param dateFormats the date formats to use
+ * @param startDate During parsing, two digit years will be placed in
the range
+ * <code>startDate</code> to <code>startDate + 100
years</code>. This value may
+ * be <code>null</code>. When <code>null</code> is
given as a parameter, year
+ * <code>2000</code> will be used.
+ * @return the parsed date
+ * @throws DateParseException if none of the dataFormats could parse the
dateValue
+ */
+ public static Date parseDate(
+ String dateValue,
+ Collection<String> dateFormats,
+ Date startDate
+ ) throws DateParseException {
+
+ if (dateValue == null) {
+ throw new IllegalArgumentException("dateValue is null");
+ }
+ if (dateFormats == null) {
+ dateFormats = DEFAULT_PATTERNS;
+ }
+ if (startDate == null) {
+ startDate = DEFAULT_TWO_DIGIT_YEAR_START;
+ }
+ // trim single quotes around date if present
+ // see issue #5279
+ if (dateValue.length() > 1
+ && dateValue.startsWith("'")
+ && dateValue.endsWith("'")
+ ) {
+ dateValue = dateValue.substring(1, dateValue.length() - 1);
+ }
+
+ SimpleDateFormat dateParser = null;
+ for (String format : dateFormats) {
+ if (dateParser == null) {
+ dateParser = new SimpleDateFormat(format, Locale.US);
+ dateParser.setTimeZone(TimeZone.getTimeZone("GMT"));
+ dateParser.set2DigitYearStart(startDate);
+ } else {
+ dateParser.applyPattern(format);
+ }
+ try {
+ return dateParser.parse(dateValue);
+ } catch (ParseException pe) {
+ // ignore this exception, we will try the next format
+ }
+ }
+
+ // we were unable to parse the date
+ throw new DateParseException("Unable to parse the date " + dateValue);
+ }
+
+ /**
+ * Formats the given date according to the RFC 1123 pattern.
+ *
+ * @param date The date to format.
+ * @return An RFC 1123 formatted date string.
+ * @see #PATTERN_RFC1123
+ */
+ public static String formatDate(Date date) {
+ return formatDate(date, PATTERN_RFC1123);
+ }
+
+ /**
+ * Formats the given date according to the specified pattern. The pattern
+ * must conform to that used by the [EMAIL PROTECTED]
java.text.SimpleDateFormat simple date
+ * format} class.
+ *
+ * @param date The date to format.
+ * @param pattern The pattern to use for formatting the date.
+ * @return A formatted date string.
+ * @throws IllegalArgumentException If the given date pattern is invalid.
+ * @see java.text.SimpleDateFormat
+ */
+ public static String formatDate(Date date, String pattern) {
+ if (date == null) {
+ throw new IllegalArgumentException("date is null");
+ }
+ if (pattern == null) {
+ throw new IllegalArgumentException("pattern is null");
+ }
+
+ SimpleDateFormat formatter = new SimpleDateFormat(pattern, Locale.US);
+ formatter.setTimeZone(GMT);
+ return formatter.format(date);
+ }
+
+}
\ No newline at end of file
Modified:
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/DefaultHttpRequest.java
URL:
http://svn.apache.org/viewvc/mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/DefaultHttpRequest.java?rev=634555&r1=634554&r2=634555&view=diff
==============================================================================
---
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/DefaultHttpRequest.java
(original)
+++
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/DefaultHttpRequest.java
Thu Mar 6 22:19:15 2008
@@ -43,7 +43,7 @@
* A default implementation of [EMAIL PROTECTED] MutableHttpRequest}.
*
* @author The Apache MINA Project ([EMAIL PROTECTED])
- * @version $Rev$, $Date$
+ * @version $Rev: 615489 $, $Date: 2008-01-26 13:59:06 -0700 (Sat, 26 Jan
2008) $
*/
public class DefaultHttpRequest extends DefaultHttpMessage implements
MutableHttpRequest {
@@ -382,7 +382,7 @@
Set<Cookie> cookies = getCookies();
if (!cookies.isEmpty()) {
// Clear previous values.
- removeHeader(HttpHeaderConstants.KEY_SET_COOKIE);
+ removeHeader(HttpHeaderConstants.KEY_COOKIE);
// And encode.
for (Cookie c: cookies) {
Modified:
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/HttpHeaderConstants.java
URL:
http://svn.apache.org/viewvc/mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/HttpHeaderConstants.java?rev=634555&r1=634554&r2=634555&view=diff
==============================================================================
---
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/HttpHeaderConstants.java
(original)
+++
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/HttpHeaderConstants.java
Thu Mar 6 22:19:15 2008
@@ -23,7 +23,7 @@
* HTTP Header Constants.
*
* @author The Apache MINA Project ([EMAIL PROTECTED])
- * @version $Rev$, $Date$
+ * @version $Rev: 615489 $, $Date: 2008-01-26 13:59:06 -0700 (Sat, 26 Jan
2008) $
*/
public class HttpHeaderConstants {
@@ -95,12 +95,12 @@
public static final String KEY_DATE = "Date";
/**
- * The "cookie" header.
+ * The "cookie" request header.
*/
public static final String KEY_COOKIE = "Cookie";
/**
- * The "set-cookie" header.
+ * The "set-cookie" response header.
*/
public static final String KEY_SET_COOKIE = "Set-Cookie";
@@ -108,6 +108,11 @@
* The "host" header.
*/
public static final String KEY_HOST = "Host";
+
+ /**
+ * The "location" header.
+ */
+ public static final String KEY_LOCATION = "Location";
private HttpHeaderConstants() {
}
Modified:
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/HttpResponseDecodingState.java
URL:
http://svn.apache.org/viewvc/mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/HttpResponseDecodingState.java?rev=634555&r1=634554&r2=634555&view=diff
==============================================================================
---
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/HttpResponseDecodingState.java
(original)
+++
mina/asyncweb/trunk/common/src/main/java/org/apache/asyncweb/common/HttpResponseDecodingState.java
Thu Mar 6 22:19:15 2008
@@ -47,15 +47,13 @@
* each new parse.
*
* @author The Apache MINA Project ([EMAIL PROTECTED])
- * @version $Rev$, $Date$
+ * @version $Rev: 615489 $, $Date: 2008-01-26 13:59:06 -0700 (Sat, 26 Jan
2008) $
*/
abstract class HttpResponseDecodingState extends DecodingStateMachine {
private static final Logger LOG = LoggerFactory
.getLogger(HttpResponseDecodingState.class);
- private static final String HEADER_COOKIE = "Cookie";
-
/**
* The header which provides a requests transfer coding
*/
@@ -76,21 +74,25 @@
*/
private static final char EXTENSION_CHAR = ';';
+ public static final String COOKIE_COMMENT = "comment";
+
+ public static final String COOKIE_DOMAIN = "domain";
+
+ public static final String COOKIE_EXPIRES = "expires";
+
+ public static final String COOKIE_MAX_AGE = "max-age";
+
+ public static final String COOKIE_PATH = "path";
+
+ public static final String COOKIE_SECURE = "secure";
+
+ public static final String COOKIE_VERSION = "version";
+
/**
* The request we are building
*/
private MutableHttpResponse response;
- private boolean parseCookies = true;
-
- public boolean isParseCookies() {
- return parseCookies;
- }
-
- public void setParseCookies(boolean parseCookies) {
- this.parseCookies = parseCookies;
- }
-
@Override
protected DecodingState init() throws Exception {
response = new DefaultHttpResponse();
@@ -154,17 +156,12 @@
ProtocolDecoderOutput out) throws Exception {
Map<String, List<String>> headers = (Map<String, List<String>>)
childProducts
.get(0);
- if (parseCookies) {
- List<String> cookies = headers.remove(HEADER_COOKIE);
- if (cookies != null && !cookies.isEmpty()) {
- if (cookies.size() > 1) {
- if (LOG.isWarnEnabled()) {
- LOG.warn("Ignoring extra cookie headers: "
- + cookies.subList(1, cookies.size()));
- }
- }
- // FIXME Parse cookies.
- //response.setCookies(cookies.get(0));
+
+ // Parse cookies
+ List<String> cookies =
headers.get(HttpHeaderConstants.KEY_SET_COOKIE);
+ if (cookies != null && !cookies.isEmpty()) {
+ for (String cookie : cookies) {
+ response.addCookie(parseCookie(cookie));
}
}
response.setHeaders(headers);
@@ -253,6 +250,43 @@
}
}
return nextState;
+ }
+
+ private Cookie parseCookie(String cookieHeader) throws
DateParseException {
+
+ MutableCookie cookie = null;
+
+ String pairs[] = cookieHeader.split(";");
+ for (int i = 0; i < pairs.length; i++) {
+ String nameValue[] = pairs[i].trim().split("=");
+ String name = nameValue[0].trim();
+ String value = (nameValue.length == 2) ? nameValue[1].trim() :
null;
+
+ //First pair is the cookie name/value
+ if (i == 0) {
+ cookie = new DefaultCookie(name, value);
+ } else if (name.equalsIgnoreCase(COOKIE_COMMENT)) {
+ cookie.setComment(value);
+ } else if (name.equalsIgnoreCase(COOKIE_PATH)) {
+ cookie.setPath(value);
+ } else if (name.equalsIgnoreCase(COOKIE_SECURE)) {
+ cookie.setSecure(true);
+ } else if (name.equalsIgnoreCase(COOKIE_VERSION)) {
+ cookie.setVersion(Integer.parseInt(value));
+ } else if (name.equalsIgnoreCase(COOKIE_MAX_AGE)) {
+ int age = Integer.parseInt(value);
+ cookie.setMaxAge(age);
+ } else if (name.equalsIgnoreCase(COOKIE_EXPIRES)) {
+ long createdDate = System.currentTimeMillis();
+ int age = (int)(DateUtil.parseDate(value).getTime() -
createdDate) / 1000;
+ cookie.setCreatedDate(createdDate);
+ cookie.setMaxAge(age);
+ } else if (name.equalsIgnoreCase(COOKIE_DOMAIN)) {
+ cookie.setDomain(value);
+ }
+ }
+
+ return cookie;
}
/**
Added: mina/asyncweb/trunk/common/src/test/catalina/webapps/ROOT/cookie.jsp
URL:
http://svn.apache.org/viewvc/mina/asyncweb/trunk/common/src/test/catalina/webapps/ROOT/cookie.jsp?rev=634555&view=auto
==============================================================================
--- mina/asyncweb/trunk/common/src/test/catalina/webapps/ROOT/cookie.jsp (added)
+++ mina/asyncweb/trunk/common/src/test/catalina/webapps/ROOT/cookie.jsp Thu
Mar 6 22:19:15 2008
@@ -0,0 +1,8 @@
+<%
+ Cookie cookie = new Cookie(
+
org.apache.asyncweb.common.integration.ResponseOutput.COOKIE_NAME,
+
org.apache.asyncweb.common.integration.ResponseOutput.COOKIE_VALUE
+ );
+
cookie.setMaxAge(org.apache.asyncweb.common.integration.ResponseOutput.COOKIE_MAX_AGE);
+ response.addCookie(cookie);
+%>
\ No newline at end of file
Added: mina/asyncweb/trunk/common/src/test/catalina/webapps/ROOT/redirect.jsp
URL:
http://svn.apache.org/viewvc/mina/asyncweb/trunk/common/src/test/catalina/webapps/ROOT/redirect.jsp?rev=634555&view=auto
==============================================================================
--- mina/asyncweb/trunk/common/src/test/catalina/webapps/ROOT/redirect.jsp
(added)
+++ mina/asyncweb/trunk/common/src/test/catalina/webapps/ROOT/redirect.jsp Thu
Mar 6 22:19:15 2008
@@ -0,0 +1,3 @@
+<%
+ response.sendRedirect("/helloworld.jsp");
+%>
Modified:
mina/asyncweb/trunk/common/src/test/java/org/apache/asyncweb/common/integration/ResponseOutput.java
URL:
http://svn.apache.org/viewvc/mina/asyncweb/trunk/common/src/test/java/org/apache/asyncweb/common/integration/ResponseOutput.java?rev=634555&r1=634554&r2=634555&view=diff
==============================================================================
---
mina/asyncweb/trunk/common/src/test/java/org/apache/asyncweb/common/integration/ResponseOutput.java
(original)
+++
mina/asyncweb/trunk/common/src/test/java/org/apache/asyncweb/common/integration/ResponseOutput.java
Thu Mar 6 22:19:15 2008
@@ -3,5 +3,9 @@
public class ResponseOutput {
public static final String HELLO_WORLD = "Hello HTTP World";
+
+ public static final String COOKIE_NAME = "test";
+ public static final String COOKIE_VALUE = "value";
+ public static final int COOKIE_MAX_AGE = 3600;
}
Modified:
mina/asyncweb/trunk/common/src/test/java/org/apache/asyncweb/common/integration/SimpleHttpTest.java
URL:
http://svn.apache.org/viewvc/mina/asyncweb/trunk/common/src/test/java/org/apache/asyncweb/common/integration/SimpleHttpTest.java?rev=634555&r1=634554&r2=634555&view=diff
==============================================================================
---
mina/asyncweb/trunk/common/src/test/java/org/apache/asyncweb/common/integration/SimpleHttpTest.java
(original)
+++
mina/asyncweb/trunk/common/src/test/java/org/apache/asyncweb/common/integration/SimpleHttpTest.java
Thu Mar 6 22:19:15 2008
@@ -1,8 +1,11 @@
package org.apache.asyncweb.common.integration;
import java.nio.charset.Charset;
+import java.util.Set;
+import org.apache.asyncweb.common.Cookie;
import org.apache.asyncweb.common.DefaultHttpRequest;
+import org.apache.asyncweb.common.HttpHeaderConstants;
import org.apache.asyncweb.common.HttpResponse;
import org.apache.asyncweb.common.HttpResponseStatus;
import org.apache.asyncweb.common.MutableHttpRequest;
@@ -23,5 +26,51 @@
assertEquals(HttpResponseStatus.OK, response.getStatus());
assertEquals(ResponseOutput.HELLO_WORLD,
response.getContent().getString(Charset.defaultCharset().newDecoder()));
}
+
+ public void testRedirectResponse() throws Exception {
+ // Send request
+ MutableHttpRequest request = new DefaultHttpRequest();
+ request.setRequestUri(getBaseURI().resolve("/redirect.jsp"));
+ request.normalize();
+ session.write(request);
+
+ // Wait for response
+ HttpResponse response = (HttpResponse)
session.read().await().getMessage();
+
+ // Test response
+ assertEquals(HttpResponseStatus.FOUND, response.getStatus());
+ assertEquals(getBaseURI().resolve("/helloworld.jsp").toString(),
response.getHeader(HttpHeaderConstants.KEY_LOCATION));
+ }
+
+ public void testCookieResponse() throws Exception {
+ // Send request
+ MutableHttpRequest request = new DefaultHttpRequest();
+ request.setRequestUri(getBaseURI().resolve("/cookie.jsp"));
+ request.normalize();
+ session.write(request);
+
+ // Wait for response
+ HttpResponse response = (HttpResponse)
session.read().await().getMessage();
+ // Test response
+ System.out.println(response.getHeaders());
+ assertEquals(HttpResponseStatus.OK, response.getStatus());
+ Set<Cookie> cookies = response.getCookies();
+ assertEquals(2, cookies.size());
+
+ Cookie cookie = findCookie(ResponseOutput.COOKIE_NAME, cookies);
+ assertNotNull(cookie);
+ assertEquals(ResponseOutput.COOKIE_NAME, cookie.getName());
+ assertEquals(ResponseOutput.COOKIE_VALUE, cookie.getValue());
+ assertTrue(Math.abs(ResponseOutput.COOKIE_MAX_AGE -
cookie.getMaxAge()) < 100); // Make sure the cookie max age is close (because
of difference from date decoding
+ }
+
+ private Cookie findCookie(String name, Iterable<Cookie> cookies) {
+ for (Cookie cookie : cookies) {
+ if (cookie.getName().equals(name)) {
+ return cookie;
+ }
+ }
+ return null;
+ }
}