Skip to content

Commit 1da7070

Browse files
author
Mark Adamcin
committed
converted RequestContent to use RFC1123 in preference to legacy joyent client format. reintroduced support for legacy request-line header on the server side, to support older clients.
1 parent 2192eda commit 1da7070

File tree

4 files changed

+124
-54
lines changed

4 files changed

+124
-54
lines changed

api/src/main/java/net/adamcin/httpsig/api/Constants.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public final class Constants {
8080
/**
8181
* List of headers to always exclude from signature calculation
8282
*/
83-
public static final List<String> IGNORE_HEADERS = Arrays.asList("authorization", HEADER_REQUEST_LINE);
83+
public static final List<String> IGNORE_HEADERS = Arrays.asList("authorization");
8484

8585
/**
8686
* Http request header representing client credentials

api/src/main/java/net/adamcin/httpsig/api/RequestContent.java

Lines changed: 63 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,7 @@
3232
import java.text.DateFormat;
3333
import java.text.ParseException;
3434
import java.text.SimpleDateFormat;
35-
import java.util.ArrayList;
36-
import java.util.Calendar;
37-
import java.util.Collections;
38-
import java.util.Date;
39-
import java.util.GregorianCalendar;
40-
import java.util.LinkedHashMap;
41-
import java.util.List;
42-
import java.util.Map;
43-
import java.util.TimeZone;
35+
import java.util.*;
4436
import java.util.logging.Logger;
4537

4638
/**
@@ -49,8 +41,18 @@
4941
*/
5042
public final class RequestContent implements Serializable {
5143
private static final Logger LOGGER = Logger.getLogger(RequestContent.class.getName());
44+
45+
/**
46+
* The official format of the Date header value (and other date-type headers) is defined by
47+
* RFC 1123 ({@link #DATE_FORMAT_RFC1123})
48+
*/
49+
@Deprecated
5250
public static final String DATE_FORMAT = "EEE MMM d HH:mm:ss yyyy zzz";
5351

52+
public static final String DATE_FORMAT_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
53+
54+
private static final List<String> SUPPORTED_DATE_FORMATS = Arrays.asList(DATE_FORMAT_RFC1123, DATE_FORMAT);
55+
5456
private static final long serialVersionUID = -2968642080214687631L;
5557

5658
@Deprecated
@@ -77,14 +79,13 @@ public static final class Builder {
7779

7880
@Deprecated
7981
public Builder setRequestLine(String requestLine) {
80-
LOGGER.warning("[setRequestLine] Use of the request-line header is deprecated. Please use (request-target) instead.");
8182
this.requestLine = requestLine;
8283
return this;
8384
}
8485

8586
public Builder setRequestTarget(String method, String path) {
86-
this.method = method.toUpperCase();
87-
this.path = path;
87+
this.method = method != null ? method.trim().toUpperCase() : null;
88+
this.path = path != null ? path.trim() : null;
8889
return this;
8990
}
9091

@@ -96,23 +97,28 @@ public Builder setRequestTarget(String method, String path) {
9697
* @return
9798
*/
9899
public Builder addHeader(final String name, final String value) {
99-
final String _name = name.toLowerCase();
100-
if (Constants.IGNORE_HEADERS.contains(_name) || _name.startsWith(":")) {
100+
if (value != null) {
101+
final String _value = value.trim();
102+
final String _name = name.trim().toLowerCase();
103+
if (Constants.IGNORE_HEADERS.contains(_name) || _name.startsWith(":")) {
101104
/* skip ignored headers and names which begin with a colon */
102-
return this;
103-
} else if (Constants.HEADER_REQUEST_TARGET.equals(_name)) {
104-
return this;
105-
} else if (!Constants.HEADER_DATE.equals(_name) || tryParseDate(value)) {
106-
List<String> values = null;
107-
if (headers.containsKey(_name)) {
108-
headers.get(_name);
109-
} else {
110-
values = new ArrayList<String>();
111-
headers.put(_name, values);
112-
}
105+
return this;
106+
} else if (Constants.HEADER_REQUEST_LINE.equals(_name)) {
107+
return this;
108+
} else if (Constants.HEADER_REQUEST_TARGET.equals(_name)) {
109+
return this;
110+
} else if (!Constants.HEADER_DATE.equals(_name) || tryParseDate(_value) != null) {
111+
List<String> values = null;
112+
if (headers.containsKey(_name)) {
113+
headers.get(_name);
114+
} else {
115+
values = new ArrayList<String>();
116+
headers.put(_name, values);
117+
}
113118

114-
if (values != null) {
115-
values.add(value);
119+
if (values != null) {
120+
values.add(_value);
121+
}
116122
}
117123
}
118124
return this;
@@ -128,7 +134,7 @@ public Builder addHeader(final String name, final String value) {
128134
*/
129135
public Builder addDate(Calendar calendar) {
130136
if (calendar != null) {
131-
DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
137+
DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT_RFC1123);
132138
dateFormat.setTimeZone(calendar.getTimeZone());
133139
this.addHeader(Constants.HEADER_DATE, dateFormat.format(calendar.getTime()));
134140
}
@@ -186,18 +192,22 @@ public byte[] getContent(List<String> headers, Charset charset) {
186192
* @return
187193
*/
188194
public String getContentString(List<String> headers) {
189-
StringBuilder hashBuilder = new StringBuilder("");
195+
StringBuilder hashBuilder = new StringBuilder();
190196
if (headers != null) {
191197
for (String header : headers) {
192198
String _header = header.toLowerCase();
193199
if (!Constants.IGNORE_HEADERS.contains(_header) && !_header.startsWith(":")) {
194-
if (Constants.HEADER_REQUEST_TARGET.equals(_header)) {
200+
if (Constants.HEADER_REQUEST_LINE.equals(_header)) {
201+
if (this.requestLine != null) {
202+
hashBuilder.append(this.requestLine).append('\n');
203+
}
204+
} else if (Constants.HEADER_REQUEST_TARGET.equals(_header)) {
195205
if (this.getRequestTarget() != null) {
196-
hashBuilder.append(this.getRequestTarget()).append("\n");
206+
hashBuilder.append(this.getRequestTarget()).append('\n');
197207
}
198208
} else {
199209
for (String value : this.getHeaderValues(_header)) {
200-
hashBuilder.append(_header).append(": ").append(value).append("\n");
210+
hashBuilder.append(_header).append(": ").append(value).append('\n');
201211
}
202212
}
203213
}
@@ -224,7 +234,6 @@ public List<String> getHeaderNames() {
224234
return Collections.unmodifiableList(headerNames);
225235
}
226236

227-
228237
/**
229238
* @return the request-line if set
230239
*/
@@ -258,7 +267,12 @@ public String getDate() {
258267
*/
259268
public List<String> getHeaderValues(String name) {
260269
String _name = name.toLowerCase();
261-
if (Constants.HEADER_REQUEST_TARGET.equals(_name)) {
270+
if (Constants.HEADER_REQUEST_LINE.equals(_name)) {
271+
LOGGER.warning("[getHeaderValues] Use of the request-line header is deprecated. Please use (request-target) instead.");
272+
return this.requestLine != null ? Collections.singletonList(
273+
this.requestLine
274+
) : Collections.<String>emptyList();
275+
} else if (Constants.HEADER_REQUEST_TARGET.equals(_name)) {
262276
return this.getRequestTarget() != null ? Collections.singletonList(
263277
this.getRequestTarget()
264278
) : Collections.<String>emptyList();
@@ -272,38 +286,34 @@ public List<String> getHeaderValues(String name) {
272286
/**
273287
* Sets the literal date header value.
274288
*
275-
* @param date a date string conforming to {@link #DATE_FORMAT}
289+
* @param date a date string conforming to {@link #DATE_FORMAT_RFC1123} or to the deprecated {@link #DATE_FORMAT}
276290
* @return true if the date header was set successfully. false if the header is already set or the provided
277-
* string does not conform to {@link #DATE_FORMAT}
291+
* string does not conform to {@link #DATE_FORMAT_RFC1123} or to {@link #DATE_FORMAT}
278292
*/
279-
private static boolean tryParseDate(String date) {
293+
protected static Date tryParseDate(String date) {
280294
if (date != null) {
281-
try {
282-
DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
283-
dateFormat.parse(date);
284-
return true;
285-
} catch (ParseException e) {
286-
LOGGER.warning("[addDate] date string " + date + " does not match format " + DATE_FORMAT);
295+
296+
for (String formatString : SUPPORTED_DATE_FORMATS) {
297+
try {
298+
DateFormat dateFormat = new SimpleDateFormat(formatString);
299+
dateFormat.setTimeZone(getGMT());
300+
return dateFormat.parse(date);
301+
} catch (ParseException e) {
302+
LOGGER.warning("[tryParseDate] date string " + date + " does not match format " + formatString);
303+
}
287304
}
288305
}
289-
return false;
306+
return null;
290307
}
291308

292-
293309
/**
294310
* Returns the currently set date header value converted to a {@link Date} object in the GMT time zone
295311
*
296312
* @return a {@link Date} object in GMT or null if header is not set or not valid
297313
*/
298314
public Date getDateGMT() {
299315
if (this.getDate() != null) {
300-
try {
301-
DateFormat dateFormat = new SimpleDateFormat(DATE_FORMAT);
302-
dateFormat.setTimeZone(getGMT());
303-
return dateFormat.parse(this.getDate());
304-
} catch (ParseException e) {
305-
LOGGER.warning("[getDateGMT] date string " + this.getDate() + " does not match format " + DATE_FORMAT);
306-
}
316+
return tryParseDate(this.getDate());
307317
}
308318
return null;
309319
}
@@ -316,8 +326,8 @@ public Date getDateGMT() {
316326
* @return a {@link Calendar} in the specified time zone or in the default time zone if the timeZone parameter is null
317327
*/
318328
public Calendar getDateTZ(TimeZone timeZone) {
319-
Date dateGMT = this.getDateGMT();
320329
TimeZone tz = timeZone != null ? timeZone : TimeZone.getDefault();
330+
Date dateGMT = this.getDateGMT();
321331
if (dateGMT != null) {
322332
Calendar calGMT = new GregorianCalendar(getGMT());
323333
calGMT.setTime(dateGMT);
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* This is free and unencumbered software released into the public domain.
3+
*
4+
* Anyone is free to copy, modify, publish, use, compile, sell, or
5+
* distribute this software, either in source code form or as a compiled
6+
* binary, for any purpose, commercial or non-commercial, and by any
7+
* means.
8+
*
9+
* In jurisdictions that recognize copyright laws, the author or authors
10+
* of this software dedicate any and all copyright interest in the
11+
* software to the public domain. We make this dedication for the benefit
12+
* of the public at large and to the detriment of our heirs and
13+
* successors. We intend this dedication to be an overt act of
14+
* relinquishment in perpetuity of all present and future rights to this
15+
* software under copyright law.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18+
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19+
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20+
* IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
21+
* OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
22+
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
23+
* OTHER DEALINGS IN THE SOFTWARE.
24+
*
25+
* For more information, please refer to <http://unlicense.org/>
26+
*/
27+
28+
package net.adamcin.httpsig.api;
29+
30+
import org.junit.Test;
31+
32+
import java.util.Arrays;
33+
import java.util.List;
34+
35+
import static org.junit.Assert.*;
36+
37+
public class RequestContentTest {
38+
39+
/**
40+
* Added for defect-1, provided test case: "Fri Jun 20 10:11:40 2014 GMT"
41+
*/
42+
@Test
43+
public void testTryParseDate() {
44+
List<String> nonDates = Arrays.asList("not a date", null, "", "Abc Def 20 10:11:40 2014 XUL");
45+
for (String nonDate : nonDates) {
46+
assertFalse("non-dates should fail the check: " + nonDate, RequestContent.tryParseDate(nonDate) != null);
47+
}
48+
49+
List<String> dates = Arrays.asList(
50+
"Fri Jun 1 10:11:40 2014 GMT",
51+
"Fri Jun 20 10:11:40 2014 GMT",
52+
"Thu, 01 Dec 1994 16:00:00 GMT",
53+
"Tue, 07 Jun 2014 20:51:35 GMT"
54+
);
55+
for (String date : dates) {
56+
assertTrue("real dates should pass the check: " + date, RequestContent.tryParseDate(date) != null);
57+
}
58+
}
59+
}

http-helpers/src/main/java/net/adamcin/httpsig/http/servlet/ServletUtil.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ public static RequestContent getRequestContent(HttpServletRequest request, Colle
9494
String path = request.getRequestURI() + (request.getQueryString() != null ? "?" + request.getQueryString() : "");
9595

9696
signatureContent.setRequestTarget(request.getMethod(), path);
97+
signatureContent.setRequestLine(request.getMethod() + " " + path + " " + request.getProtocol());
9798

9899
Enumeration headerNames = request.getHeaderNames();
99100

0 commit comments

Comments
 (0)