/**
* Copyright 2005-2014 Restlet
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: Apache 2.0 or or EPL 1.0 (the "Licenses"). You can
* select the license that you prefer but you may not use this file except in
* compliance with one of these Licenses.
*
* You can obtain a copy of the Apache 2.0 license at
* http://www.opensource.org/licenses/apache-2.0
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://restlet.com/products/restlet-framework
*
* Restlet is a registered trademark of Restlet S.A.S.
*/
package org.restlet.engine.header;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import org.restlet.Context;
import org.restlet.Message;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.AuthenticationInfo;
import org.restlet.data.ChallengeRequest;
import org.restlet.data.ChallengeResponse;
import org.restlet.data.ClientInfo;
import org.restlet.data.Conditions;
import org.restlet.data.CookieSetting;
import org.restlet.data.Digest;
import org.restlet.data.Disposition;
import org.restlet.data.Header;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.data.Tag;
import org.restlet.engine.Engine;
import org.restlet.engine.util.CaseInsensitiveHashSet;
import org.restlet.engine.util.DateUtils;
import org.restlet.engine.util.StringUtils;
import org.restlet.representation.EmptyRepresentation;
import org.restlet.representation.Representation;
import org.restlet.util.Series;
/**
* HTTP-style header utilities.
*
* @author Jerome Louvel
*/
public class HeaderUtils {
/**
* Standard set of headers which cannot be modified.
*/
private static final Set<String> STANDARD_HEADERS = Collections
.unmodifiableSet(new CaseInsensitiveHashSet(Arrays.asList(
HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,
HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_HEADERS,
HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_METHODS,
HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
HeaderConstants.HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
HeaderConstants.HEADER_ACCESS_CONTROL_REQUEST_METHOD,
HeaderConstants.HEADER_ACCEPT,
HeaderConstants.HEADER_ACCEPT_CHARSET,
HeaderConstants.HEADER_ACCEPT_ENCODING,
HeaderConstants.HEADER_ACCEPT_LANGUAGE,
HeaderConstants.HEADER_ACCEPT_PATCH,
HeaderConstants.HEADER_ACCEPT_RANGES,
HeaderConstants.HEADER_AGE, HeaderConstants.HEADER_ALLOW,
HeaderConstants.HEADER_AUTHENTICATION_INFO,
HeaderConstants.HEADER_AUTHORIZATION,
HeaderConstants.HEADER_CACHE_CONTROL,
HeaderConstants.HEADER_CONNECTION,
HeaderConstants.HEADER_CONTENT_DISPOSITION,
HeaderConstants.HEADER_CONTENT_ENCODING,
HeaderConstants.HEADER_CONTENT_LANGUAGE,
HeaderConstants.HEADER_CONTENT_LENGTH,
HeaderConstants.HEADER_CONTENT_LOCATION,
HeaderConstants.HEADER_CONTENT_MD5,
HeaderConstants.HEADER_CONTENT_RANGE,
HeaderConstants.HEADER_CONTENT_TYPE,
HeaderConstants.HEADER_COOKIE, HeaderConstants.HEADER_DATE,
HeaderConstants.HEADER_ETAG, HeaderConstants.HEADER_EXPECT,
HeaderConstants.HEADER_EXPIRES,
HeaderConstants.HEADER_FROM, HeaderConstants.HEADER_HOST,
HeaderConstants.HEADER_IF_MATCH,
HeaderConstants.HEADER_IF_MODIFIED_SINCE,
HeaderConstants.HEADER_IF_NONE_MATCH,
HeaderConstants.HEADER_IF_RANGE,
HeaderConstants.HEADER_IF_UNMODIFIED_SINCE,
HeaderConstants.HEADER_LAST_MODIFIED,
HeaderConstants.HEADER_LOCATION,
HeaderConstants.HEADER_MAX_FORWARDS,
HeaderConstants.HEADER_PROXY_AUTHENTICATE,
HeaderConstants.HEADER_PROXY_AUTHORIZATION,
HeaderConstants.HEADER_RANGE,
HeaderConstants.HEADER_REFERRER,
HeaderConstants.HEADER_RETRY_AFTER,
HeaderConstants.HEADER_SERVER,
HeaderConstants.HEADER_SET_COOKIE,
HeaderConstants.HEADER_SET_COOKIE2,
HeaderConstants.HEADER_USER_AGENT,
HeaderConstants.HEADER_VARY, HeaderConstants.HEADER_VIA,
HeaderConstants.HEADER_WARNING,
HeaderConstants.HEADER_WWW_AUTHENTICATE)));
/**
* Set of unsupported headers that will be covered in future versions.
*/
private static final Set<String> UNSUPPORTED_STANDARD_HEADERS = Collections
.unmodifiableSet(new CaseInsensitiveHashSet(Arrays.asList(
HeaderConstants.HEADER_PRAGMA,
HeaderConstants.HEADER_TRAILER,
HeaderConstants.HEADER_TRANSFER_ENCODING,
HeaderConstants.HEADER_TRANSFER_EXTENSION,
HeaderConstants.HEADER_UPGRADE)));
/**
* Adds the entity headers based on the {@link Representation} to the
* {@link Series}.
*
* @param entity
* The source entity {@link Representation}.
* @param headers
* The target headers {@link Series}.
*/
public static void addEntityHeaders(Representation entity,
Series<Header> headers) {
if (entity == null || !entity.isAvailable()) {
addHeader(HeaderConstants.HEADER_CONTENT_LENGTH, "0", headers);
} else if (entity.getAvailableSize() != Representation.UNKNOWN_SIZE) {
addHeader(HeaderConstants.HEADER_CONTENT_LENGTH,
Long.toString(entity.getAvailableSize()), headers);
}
if (entity != null) {
addHeader(HeaderConstants.HEADER_CONTENT_ENCODING,
EncodingWriter.write(entity.getEncodings()), headers);
addHeader(HeaderConstants.HEADER_CONTENT_LANGUAGE,
LanguageWriter.write(entity.getLanguages()), headers);
if (entity.getLocationRef() != null) {
addHeader(HeaderConstants.HEADER_CONTENT_LOCATION, entity
.getLocationRef().getTargetRef().toString(), headers);
}
// [ifndef gwt]
if (entity.getDigest() != null
&& Digest.ALGORITHM_MD5.equals(entity.getDigest()
.getAlgorithm())) {
addHeader(
HeaderConstants.HEADER_CONTENT_MD5,
new String(org.restlet.engine.util.Base64.encode(entity
.getDigest().getValue(), false)), headers);
}
// [enddef]
if (entity.getRange() != null) {
addHeader(HeaderConstants.HEADER_CONTENT_RANGE,
RangeWriter.write(entity.getRange(), entity.getSize()),
headers);
}
if (entity.getMediaType() != null) {
addHeader(HeaderConstants.HEADER_CONTENT_TYPE,
ContentType.writeHeader(entity), headers);
}
if (entity.getExpirationDate() != null) {
addHeader(HeaderConstants.HEADER_EXPIRES,
DateWriter.write(entity.getExpirationDate()), headers);
}
if (entity.getModificationDate() != null) {
addHeader(HeaderConstants.HEADER_LAST_MODIFIED,
DateWriter.write(entity.getModificationDate()), headers);
}
if (entity.getTag() != null) {
addHeader(HeaderConstants.HEADER_ETAG,
TagWriter.write(entity.getTag()), headers);
}
if (entity.getDisposition() != null
&& !Disposition.TYPE_NONE.equals(entity.getDisposition()
.getType())) {
addHeader(HeaderConstants.HEADER_CONTENT_DISPOSITION,
DispositionWriter.write(entity.getDisposition()),
headers);
}
}
}
/**
* Adds extension headers if they are non-standard headers.
*
* @param existingHeaders
* The headers to update.
* @param additionalHeaders
* The headers to add.
*/
public static void addExtensionHeaders(Series<Header> existingHeaders,
Series<Header> additionalHeaders) {
if (additionalHeaders != null) {
for (Header param : additionalHeaders) {
if (STANDARD_HEADERS.contains(param.getName())) {
// Standard headers that can't be overridden
Context.getCurrentLogger()
.warning(
"Addition of the standard header \""
+ param.getName()
+ "\" is not allowed. Please use the equivalent property in the Restlet API.");
} else if (UNSUPPORTED_STANDARD_HEADERS.contains(param
.getName())) {
Context.getCurrentLogger()
.warning(
"Addition of the standard header \""
+ param.getName()
+ "\" is discouraged as a future version of the Restlet API will directly support it.");
existingHeaders.add(param);
} else {
existingHeaders.add(param);
}
}
}
}
/**
* Adds the general headers from the {@link Message} to the {@link Series}.
*
* @param message
* The source {@link Message}.
* @param headers
* The target headers {@link Series}.
*/
public static void addGeneralHeaders(Message message, Series<Header> headers) {
addHeader(HeaderConstants.HEADER_CACHE_CONTROL,
CacheDirectiveWriter.write(message.getCacheDirectives()),
headers);
if (message.getDate() == null) {
message.setDate(new Date());
}
addHeader(HeaderConstants.HEADER_DATE,
DateWriter.write(message.getDate()), headers);
addHeader(HeaderConstants.HEADER_VIA,
RecipientInfoWriter.write(message.getRecipientsInfo()), headers);
addHeader(HeaderConstants.HEADER_WARNING,
WarningWriter.write(message.getWarnings()), headers);
}
/**
* Adds a header to the given list. Checks for exceptions and logs them.
*
* @param headerName
* The header name.
* @param headerValue
* The header value.
* @param headers
* The headers list.
*/
public static void addHeader(String headerName, String headerValue,
Series<Header> headers) {
if ((headerName != null) && (headerValue != null)
&& (headerValue.length() > 0)) {
try {
headers.add(headerName, headerValue);
} catch (Throwable t) {
Context.getCurrentLogger().log(Level.WARNING,
"Unable to format the " + headerName + " header", t);
}
}
}
/**
* Adds the entity headers based on the {@link Representation} to the
* {@link Series} when a 304 (Not Modified) status is returned.
*
* @param entity
* The source entity {@link Representation}.
* @param headers
* The target headers {@link Series}.
*/
public static void addNotModifiedEntityHeaders(Representation entity,
Series<Header> headers) {
if (entity != null) {
if (entity.getTag() != null) {
HeaderUtils.addHeader(HeaderConstants.HEADER_ETAG,
TagWriter.write(entity.getTag()), headers);
}
if (entity.getLocationRef() != null) {
HeaderUtils.addHeader(HeaderConstants.HEADER_CONTENT_LOCATION,
entity.getLocationRef().getTargetRef().toString(),
headers);
}
}
}
/**
* Adds the headers based on the {@link Request} to the given {@link Series}
* .
*
* @param request
* The {@link Request} to copy the headers from.
* @param headers
* The {@link Series} to copy the headers to.
*/
@SuppressWarnings("unchecked")
public static void addRequestHeaders(Request request, Series<Header> headers) {
ClientInfo clientInfo = request.getClientInfo();
if (!clientInfo.getAcceptedMediaTypes().isEmpty()) {
addHeader(HeaderConstants.HEADER_ACCEPT,
PreferenceWriter.write(clientInfo.getAcceptedMediaTypes()),
headers);
} else {
addHeader(HeaderConstants.HEADER_ACCEPT, MediaType.ALL.getName(),
headers);
}
if (!clientInfo.getAcceptedCharacterSets().isEmpty()) {
addHeader(HeaderConstants.HEADER_ACCEPT_CHARSET,
PreferenceWriter.write(clientInfo
.getAcceptedCharacterSets()), headers);
}
if (!clientInfo.getAcceptedEncodings().isEmpty()) {
addHeader(HeaderConstants.HEADER_ACCEPT_ENCODING,
PreferenceWriter.write(clientInfo.getAcceptedEncodings()),
headers);
}
if (!clientInfo.getAcceptedLanguages().isEmpty()) {
addHeader(HeaderConstants.HEADER_ACCEPT_LANGUAGE,
PreferenceWriter.write(clientInfo.getAcceptedLanguages()),
headers);
}
if (!clientInfo.getAcceptedPatches().isEmpty()) {
addHeader(HeaderConstants.HEADER_ACCEPT_PATCH,
PreferenceWriter.write(clientInfo.getAcceptedPatches()),
headers);
}
// [ifndef gwt]
if (!clientInfo.getExpectations().isEmpty()) {
addHeader(HeaderConstants.HEADER_EXPECT,
ExpectationWriter.write(clientInfo.getExpectations()),
headers);
}
// [enddef]
if (clientInfo.getFrom() != null) {
addHeader(HeaderConstants.HEADER_FROM, request.getClientInfo()
.getFrom(), headers);
}
// Manually add the host name and port when it is potentially
// different from the one specified in the target resource reference.
Reference hostRef = (request.getResourceRef().getBaseRef() != null) ? request
.getResourceRef().getBaseRef() : request.getResourceRef();
if (hostRef.getHostDomain() != null) {
String host = hostRef.getHostDomain();
int hostRefPortValue = hostRef.getHostPort();
if ((hostRefPortValue != -1)
&& (hostRefPortValue != request.getProtocol()
.getDefaultPort())) {
host = host + ':' + hostRefPortValue;
}
addHeader(HeaderConstants.HEADER_HOST, host, headers);
}
Conditions conditions = request.getConditions();
addHeader(HeaderConstants.HEADER_IF_MATCH,
TagWriter.write(conditions.getMatch()), headers);
addHeader(HeaderConstants.HEADER_IF_NONE_MATCH,
TagWriter.write(conditions.getNoneMatch()), headers);
if (conditions.getModifiedSince() != null) {
addHeader(HeaderConstants.HEADER_IF_MODIFIED_SINCE,
DateWriter.write(conditions.getModifiedSince()), headers);
}
if (conditions.getRangeTag() != null
&& conditions.getRangeDate() != null) {
Context.getCurrentLogger()
.log(Level.WARNING,
"Unable to format the HTTP If-Range header due to the presence of both entity tag and modification date.");
} else if (conditions.getRangeTag() != null) {
addHeader(HeaderConstants.HEADER_IF_RANGE,
TagWriter.write(conditions.getRangeTag()), headers);
} else if (conditions.getRangeDate() != null) {
addHeader(HeaderConstants.HEADER_IF_RANGE,
DateWriter.write(conditions.getRangeDate()), headers);
}
if (conditions.getUnmodifiedSince() != null) {
addHeader(HeaderConstants.HEADER_IF_UNMODIFIED_SINCE,
DateWriter.write(conditions.getUnmodifiedSince()), headers);
}
if (request.getMaxForwards() > -1) {
addHeader(HeaderConstants.HEADER_MAX_FORWARDS,
Integer.toString(request.getMaxForwards()), headers);
}
if (!request.getRanges().isEmpty()) {
addHeader(HeaderConstants.HEADER_RANGE,
RangeWriter.write(request.getRanges()), headers);
}
if (request.getReferrerRef() != null) {
addHeader(HeaderConstants.HEADER_REFERRER, request.getReferrerRef()
.toString(), headers);
}
if (request.getClientInfo().getAgent() != null) {
addHeader(HeaderConstants.HEADER_USER_AGENT, request
.getClientInfo().getAgent(), headers);
// [ifndef gwt]
} else {
addHeader(HeaderConstants.HEADER_USER_AGENT, Engine.VERSION_HEADER,
headers);
// [enddef]
}
// [ifndef gwt]
if (clientInfo.getExpectations().size() > 0) {
addHeader(HeaderConstants.HEADER_ACCEPT_ENCODING,
PreferenceWriter.write(clientInfo.getAcceptedEncodings()),
headers);
}
// [enddef]
// CORS headers
if (request.getAccessControlRequestHeaders() != null) {
addHeader(
HeaderConstants.HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
StringWriter.write(request.getAccessControlRequestHeaders()),
headers);
}
if (request.getAccessControlRequestMethod() != null) {
addHeader(HeaderConstants.HEADER_ACCESS_CONTROL_REQUEST_METHOD,
request.getAccessControlRequestMethod().getName(), headers);
}
// ----------------------------------
// 3) Add supported extension headers
// ----------------------------------
if (request.getCookies().size() > 0) {
addHeader(HeaderConstants.HEADER_COOKIE,
CookieWriter.write(request.getCookies()), headers);
}
// -------------------------------------
// 4) Add user-defined extension headers
// -------------------------------------
Series<Header> additionalHeaders = (Series<Header>) request
.getAttributes().get(HeaderConstants.ATTRIBUTE_HEADERS);
addExtensionHeaders(headers, additionalHeaders);
// ---------------------------------------
// 5) Add authorization headers at the end
// ---------------------------------------
// [ifndef gwt]
// Add the security headers. NOTE: This must stay at the end because
// the AWS challenge scheme requires access to all HTTP headers
ChallengeResponse challengeResponse = request.getChallengeResponse();
if (challengeResponse != null) {
String authHeader = org.restlet.engine.security.AuthenticatorUtils
.formatResponse(challengeResponse, request, headers);
if (authHeader != null) {
addHeader(HeaderConstants.HEADER_AUTHORIZATION, authHeader,
headers);
}
}
ChallengeResponse proxyChallengeResponse = request
.getProxyChallengeResponse();
if (proxyChallengeResponse != null) {
String authHeader = org.restlet.engine.security.AuthenticatorUtils
.formatResponse(proxyChallengeResponse, request, headers);
if (authHeader != null) {
addHeader(HeaderConstants.HEADER_PROXY_AUTHORIZATION,
authHeader, headers);
}
}
// [enddef]
}
// [ifndef gwt] method
/**
* Adds the headers based on the {@link Response} to the given
* {@link Series}.
*
* @param response
* The {@link Response} to copy the headers from.
* @param headers
* The {@link Series} to copy the headers to.
*/
@SuppressWarnings("unchecked")
public static void addResponseHeaders(Response response,
Series<Header> headers) {
if (response.getServerInfo().isAcceptingRanges()) {
addHeader(HeaderConstants.HEADER_ACCEPT_RANGES, "bytes", headers);
}
if (response.getAge() > 0) {
addHeader(HeaderConstants.HEADER_AGE,
Integer.toString(response.getAge()), headers);
}
if (response.getStatus().equals(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED)
|| Method.OPTIONS.equals(response.getRequest().getMethod())) {
addHeader(HeaderConstants.HEADER_ALLOW,
MethodWriter.write(response.getAllowedMethods()), headers);
}
if (response.getLocationRef() != null) {
// The location header must contain an absolute URI.
addHeader(HeaderConstants.HEADER_LOCATION, response
.getLocationRef().getTargetRef().toString(), headers);
}
if (response.getProxyChallengeRequests() != null) {
for (ChallengeRequest challengeRequest : response
.getProxyChallengeRequests()) {
addHeader(HeaderConstants.HEADER_PROXY_AUTHENTICATE,
org.restlet.engine.security.AuthenticatorUtils
.formatRequest(challengeRequest, response,
headers), headers);
}
}
if (response.getRetryAfter() != null) {
addHeader(HeaderConstants.HEADER_RETRY_AFTER,
DateWriter.write(response.getRetryAfter()), headers);
}
if ((response.getServerInfo() != null)
&& (response.getServerInfo().getAgent() != null)) {
addHeader(HeaderConstants.HEADER_SERVER, response.getServerInfo()
.getAgent(), headers);
} else {
addHeader(HeaderConstants.HEADER_SERVER, Engine.VERSION_HEADER,
headers);
}
// Send the Vary header only to none-MSIE user agents as MSIE seems
// to support partially and badly this header (cf issue 261).
if (!((response.getRequest().getClientInfo().getAgent() != null) && response
.getRequest().getClientInfo().getAgent().contains("MSIE"))) {
// Add the Vary header if content negotiation was used
addHeader(HeaderConstants.HEADER_VARY,
DimensionWriter.write(response.getDimensions()), headers);
}
// Set the security data
if (response.getChallengeRequests() != null) {
for (ChallengeRequest challengeRequest : response
.getChallengeRequests()) {
addHeader(HeaderConstants.HEADER_WWW_AUTHENTICATE,
org.restlet.engine.security.AuthenticatorUtils
.formatRequest(challengeRequest, response,
headers), headers);
}
}
// CORS headers
if (response.getAccessControlAllowCredentials() != null) {
addHeader(HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,
response.getAccessControlAllowCredentials().toString(),
headers);
}
if (response.getAccessControlAllowHeaders() != null) {
addHeader(
HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_HEADERS,
StringWriter.write(response.getAccessControlAllowHeaders()),
headers);
}
if (response.getAccessControlAllowOrigin() != null) {
addHeader(HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
response.getAccessControlAllowOrigin(), headers);
}
if (response.getAccessControlAllowMethods() != null) {
addHeader(
HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_METHODS,
MethodWriter.write(response.getAccessControlAllowMethods()),
headers);
}
// ----------------------------------
// 3) Add supported extension headers
// ----------------------------------
// Add the Authentication-Info header
if (response.getAuthenticationInfo() != null) {
addHeader(HeaderConstants.HEADER_AUTHENTICATION_INFO,
org.restlet.engine.security.AuthenticatorUtils
.formatAuthenticationInfo(response
.getAuthenticationInfo()), headers);
}
// Cookies settings should be written in a single header, but Web
// browsers does not seem to support it.
for (CookieSetting cookieSetting : response.getCookieSettings()) {
addHeader(HeaderConstants.HEADER_SET_COOKIE,
CookieSettingWriter.write(cookieSetting), headers);
}
// -------------------------------------
// 4) Add user-defined extension headers
// -------------------------------------
Series<Header> additionalHeaders = (Series<Header>) response
.getAttributes().get(HeaderConstants.ATTRIBUTE_HEADERS);
addExtensionHeaders(headers, additionalHeaders);
}
/**
* Copies extension headers into a request.
*
* @param headers
* The headers to copy.
* @param request
* The request to update.
*/
public static void keepExtensionHeadersOnly(Message message) {
Series<Header> headers = message.getHeaders();
// [ifndef gwt] instruction
Series<Header> extensionHeaders = new Series<Header>(Header.class);
// [ifdef gwt] instruction uncomment
// Series<Header> extensionHeaders = new
// org.restlet.engine.util.HeaderSeries();
for (Header header : headers) {
if (!STANDARD_HEADERS.contains(header.getName())) {
extensionHeaders.add(header);
}
}
message.getAttributes().put(HeaderConstants.ATTRIBUTE_HEADERS,
extensionHeaders);
}
/**
* Copies extension headers into a request or a response.
*
* @param headers
* The headers to copy.
* @param request
* The request to update.
*/
public static void copyExtensionHeaders(Series<Header> headers,
Message message) {
if (headers != null) {
Series<Header> extensionHeaders = message.getHeaders();
for (Header header : headers) {
if (!STANDARD_HEADERS.contains(header.getName())) {
extensionHeaders.add(header);
}
}
}
}
/**
* Copies headers into a response.
*
* @param headers
* The headers to copy.
* @param response
* The response to update.
*/
public static void copyResponseTransportHeaders(Series<Header> headers,
Response response) {
if (headers != null) {
for (Header header : headers) {
if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_LOCATION)) {
response.setLocationRef(header.getValue());
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_AGE)) {
try {
response.setAge(Integer.parseInt(header.getValue()));
} catch (NumberFormatException nfe) {
Context.getCurrentLogger().log(
Level.WARNING,
"Error during Age header parsing. Header: "
+ header.getValue(), nfe);
}
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_DATE)) {
Date date = DateUtils.parse(header.getValue());
if (date == null) {
date = new Date();
}
response.setDate(date);
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_RETRY_AFTER)) {
// [ifndef gwt]
Date retryAfter = DateUtils.parse(header.getValue());
if (retryAfter == null) {
// The date might be expressed as a number of seconds
try {
int retryAfterSecs = Integer.parseInt(header
.getValue());
java.util.Calendar calendar = java.util.Calendar
.getInstance();
calendar.add(java.util.Calendar.SECOND,
retryAfterSecs);
retryAfter = calendar.getTime();
} catch (NumberFormatException nfe) {
Context.getCurrentLogger().log(
Level.WARNING,
"Error during Retry-After header parsing. Header: "
+ header.getValue(), nfe);
}
}
response.setRetryAfter(retryAfter);
// [enddef]
} else if ((header.getName()
.equalsIgnoreCase(HeaderConstants.HEADER_SET_COOKIE))
|| (header.getName()
.equalsIgnoreCase(HeaderConstants.HEADER_SET_COOKIE2))) {
try {
CookieSettingReader cr = new CookieSettingReader(
header.getValue());
response.getCookieSettings().add(cr.readValue());
} catch (Exception e) {
Context.getCurrentLogger().log(
Level.WARNING,
"Error during cookie setting parsing. Header: "
+ header.getValue(), e);
}
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_WWW_AUTHENTICATE)) {
// [ifndef gwt]
List<ChallengeRequest> crs = org.restlet.engine.security.AuthenticatorUtils
.parseRequest(response, header.getValue(), headers);
response.getChallengeRequests().addAll(crs);
// [enddef]
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_PROXY_AUTHENTICATE)) {
// [ifndef gwt]
List<ChallengeRequest> crs = org.restlet.engine.security.AuthenticatorUtils
.parseRequest(response, header.getValue(), headers);
response.getProxyChallengeRequests().addAll(crs);
// [enddef]
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_AUTHENTICATION_INFO)) {
// [ifndef gwt]
AuthenticationInfo authenticationInfo = org.restlet.engine.security.AuthenticatorUtils
.parseAuthenticationInfo(header.getValue());
response.setAuthenticationInfo(authenticationInfo);
// [enddef]
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_SERVER)) {
response.getServerInfo().setAgent(header.getValue());
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_ALLOW)) {
MethodReader
.addValues(header, response.getAllowedMethods());
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_VARY)) {
DimensionReader.addValues(header, response.getDimensions());
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_VIA)) {
RecipientInfoReader.addValues(header,
response.getRecipientsInfo());
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_WARNING)) {
WarningReader.addValues(header, response.getWarnings());
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_CACHE_CONTROL)) {
CacheDirectiveReader.addValues(header,
response.getCacheDirectives());
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_ACCEPT_RANGES)) {
TokenReader tr = new TokenReader(header.getValue());
response.getServerInfo().setAcceptingRanges(
tr.readValues().contains("bytes"));
} else if (header
.getName()
.equalsIgnoreCase(
HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS)) {
response.setAccessControlAllowCredentials(Boolean
.parseBoolean(header.getValue()));
StringReader.addValues(header,
response.getAccessControlAllowHeaders());
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_ORIGIN)) {
response.setAccessControlAllowOrigin(header.getValue());
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_ACCESS_CONTROL_ALLOW_METHODS)) {
MethodReader.addValues(header,
response.getAccessControlAllowMethods());
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_ACCESS_CONTROL_EXPOSE_HEADERS)) {
StringReader.addValues(header,
response.getAccessControlExposeHeaders());
}
}
}
}
/**
* Extracts entity headers and updates a given representation or create an
* empty one when at least one entity header is present.
*
* @param headers
* The headers to copy.
* @param representation
* The representation to update or null.
* @return a representation updated with the given entity headers.
* @throws NumberFormatException
* @see HeaderUtils#copyResponseTransportHeaders(Series, Response)
*/
public static Representation extractEntityHeaders(Iterable<Header> headers,
Representation representation) throws NumberFormatException {
Representation result = (representation == null) ? new EmptyRepresentation()
: representation;
boolean entityHeaderFound = false;
if (headers != null) {
for (Header header : headers) {
if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_CONTENT_TYPE)) {
ContentType contentType = new ContentType(header.getValue());
result.setMediaType(contentType.getMediaType());
if ((result.getCharacterSet() == null)
|| (contentType.getCharacterSet() != null)) {
result.setCharacterSet(contentType.getCharacterSet());
}
entityHeaderFound = true;
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_CONTENT_LENGTH)) {
entityHeaderFound = true;
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_EXPIRES)) {
result.setExpirationDate(HeaderReader.readDate(
header.getValue(), false));
entityHeaderFound = true;
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_CONTENT_ENCODING)) {
new EncodingReader(header.getValue()).addValues(result
.getEncodings());
entityHeaderFound = true;
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_CONTENT_LANGUAGE)) {
new LanguageReader(header.getValue()).addValues(result
.getLanguages());
entityHeaderFound = true;
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_LAST_MODIFIED)) {
result.setModificationDate(HeaderReader.readDate(
header.getValue(), false));
entityHeaderFound = true;
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_ETAG)) {
result.setTag(Tag.parse(header.getValue()));
entityHeaderFound = true;
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_CONTENT_LOCATION)) {
result.setLocationRef(header.getValue());
entityHeaderFound = true;
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_CONTENT_DISPOSITION)) {
try {
result.setDisposition(new DispositionReader(header
.getValue()).readValue());
entityHeaderFound = true;
} catch (IOException ioe) {
Context.getCurrentLogger().log(
Level.WARNING,
"Error during Content-Disposition header parsing. Header: "
+ header.getValue(), ioe);
}
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_CONTENT_RANGE)) {
// [ifndef gwt]
org.restlet.engine.header.RangeReader.update(
header.getValue(), result);
entityHeaderFound = true;
// [enddef]
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_CONTENT_MD5)) {
// [ifndef gwt]
// Since an MD5 hash is 128 bits long, its base64 encoding
// is 22 bytes if unpadded, or 24 bytes if padded. If the
// header value is unpadded, append two base64 padding
// characters ("==") before passing the value to
// Base64.decode(), which requires its input argument's
// length to be a multiple of four.
String base64hash = header.getValue();
if (base64hash.length() == 22) {
base64hash += "==";
}
result.setDigest(new org.restlet.data.Digest(
org.restlet.data.Digest.ALGORITHM_MD5,
org.restlet.engine.util.Base64.decode(base64hash)));
entityHeaderFound = true;
// [enddef]
}
}
}
// If no representation was initially expected and no entity header
// is found, then do not return any representation
if ((representation == null) && !entityHeaderFound) {
result = null;
}
return result;
}
/**
* Returns the content length of the request entity if know,
* {@link Representation#UNKNOWN_SIZE} otherwise.
*
* @return The request content length.
*/
public static long getContentLength(Series<Header> headers) {
long contentLength = Representation.UNKNOWN_SIZE;
if (headers != null) {
// Extract the content length header
for (Header header : headers) {
if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_CONTENT_LENGTH)) {
try {
contentLength = Long.parseLong(header.getValue());
} catch (NumberFormatException e) {
contentLength = Representation.UNKNOWN_SIZE;
}
}
}
}
return contentLength;
}
/**
* Indicates if the given character is alphabetical (a-z or A-Z).
*
* @param character
* The character to test.
* @return True if the given character is alphabetical (a-z or A-Z).
*/
public static boolean isAlpha(int character) {
return isUpperCase(character) || isLowerCase(character);
}
/**
* Indicates if the given character is in ASCII range.
*
* @param character
* The character to test.
* @return True if the given character is in ASCII range.
*/
public static boolean isAsciiChar(int character) {
return (character >= 0) && (character <= 127);
}
/**
* Indicates if the given character is a carriage return.
*
* @param character
* The character to test.
* @return True if the given character is a carriage return.
*/
public static boolean isCarriageReturn(int character) {
return (character == 13);
}
/**
* Indicates if the entity is chunked.
*
* @return True if the entity is chunked.
*/
public static boolean isChunkedEncoding(Series<Header> headers) {
boolean result = false;
if (headers != null) {
final String header = headers.getFirstValue(
HeaderConstants.HEADER_TRANSFER_ENCODING, true);
result = "chunked".equalsIgnoreCase(header);
}
return result;
}
/**
* Indicates if the given character is a comma, the character used as header
* value separator.
*
* @param character
* The character to test.
* @return True if the given character is a comma.
*/
public static boolean isComma(int character) {
return (character == ',');
}
/**
* Indicates if the given character is a comment text. It means
* {@link #isText(int)} returns true and the character is not '(' or ')'.
*
* @param character
* The character to test.
* @return True if the given character is a quoted text.
*/
public static boolean isCommentText(int character) {
return isText(character) && (character != '(') && (character != ')');
}
/**
* Indicates if the connection must be closed.
*
* @param headers
* The headers to test.
* @return True if the connection must be closed.
*/
public static boolean isConnectionClose(Series<Header> headers) {
boolean result = false;
if (headers != null) {
String header = headers.getFirstValue(
HeaderConstants.HEADER_CONNECTION, true);
result = "close".equalsIgnoreCase(header);
}
return result;
}
/**
* Indicates if the given character is a control character.
*
* @param character
* The character to test.
* @return True if the given character is a control character.
*/
public static boolean isControlChar(int character) {
return ((character >= 0) && (character <= 31)) || (character == 127);
}
/**
* Indicates if the given character is a digit (0-9).
*
* @param character
* The character to test.
* @return True if the given character is a digit (0-9).
*/
public static boolean isDigit(int character) {
return (character >= '0') && (character <= '9');
}
/**
* Indicates if the given character is a double quote.
*
* @param character
* The character to test.
* @return True if the given character is a double quote.
*/
public static boolean isDoubleQuote(int character) {
return (character == 34);
}
/**
* Indicates if the given character is an horizontal tab.
*
* @param character
* The character to test.
* @return True if the given character is an horizontal tab.
*/
public static boolean isHorizontalTab(int character) {
return (character == 9);
}
/**
* Indicates if the given character is in ISO Latin 1 (8859-1) range. Note
* that this range is a superset of ASCII and a subrange of Unicode (UTF-8).
*
* @param character
* The character to test.
* @return True if the given character is in ISO Latin 1 range.
*/
public static boolean isLatin1Char(int character) {
return (character >= 0) && (character <= 255);
}
/**
* Indicates if the given character is a value separator.
*
* @param character
* The character to test.
* @return True if the given character is a value separator.
*/
public static boolean isLinearWhiteSpace(int character) {
return (isCarriageReturn(character) || isSpace(character)
|| isLineFeed(character) || HeaderUtils
.isHorizontalTab(character));
}
/**
* Indicates if the given character is a line feed.
*
* @param character
* The character to test.
* @return True if the given character is a line feed.
*/
public static boolean isLineFeed(int character) {
return (character == 10);
}
/**
* Indicates if the given character is lower case (a-z).
*
* @param character
* The character to test.
* @return True if the given character is lower case (a-z).
*/
public static boolean isLowerCase(int character) {
return (character >= 'a') && (character <= 'z');
}
/**
* Indicates if the given character marks the start of a quoted pair.
*
* @param character
* The character to test.
* @return True if the given character marks the start of a quoted pair.
*/
public static boolean isQuoteCharacter(int character) {
return (character == '\\');
}
/**
* Indicates if the given character is a quoted text. It means
* {@link #isText(int)} returns true and {@link #isDoubleQuote(int)} returns
* false.
*
* @param character
* The character to test.
* @return True if the given character is a quoted text.
*/
public static boolean isQuotedText(int character) {
return isText(character) && !isDoubleQuote(character);
}
/**
* Indicates if the given character is a semicolon, the character used as
* header parameter separator.
*
* @param character
* The character to test.
* @return True if the given character is a semicolon.
*/
public static boolean isSemiColon(int character) {
return (character == ';');
}
/**
* Indicates if the given character is a separator.
*
* @param character
* The character to test.
* @return True if the given character is a separator.
*/
public static boolean isSeparator(int character) {
switch (character) {
case '(':
case ')':
case '<':
case '>':
case '@':
case ',':
case ';':
case ':':
case '\\':
case '"':
case '/':
case '[':
case ']':
case '?':
case '=':
case '{':
case '}':
case ' ':
case '\t':
return true;
default:
return false;
}
}
/**
* Indicates if the given character is a space.
*
* @param character
* The character to test.
* @return True if the given character is a space.
*/
public static boolean isSpace(int character) {
return (character == 32);
}
/**
* Indicates if the given character is textual (ISO Latin 1 and not a
* control character).
*
* @param character
* The character to test.
* @return True if the given character is textual.
*/
public static boolean isText(int character) {
return isLatin1Char(character) && !isControlChar(character);
}
/**
* Indicates if the token is valid.<br>
* Only contains valid token characters.
*
* @param token
* The token to check
* @return True if the token is valid.
*/
public static boolean isToken(CharSequence token) {
for (int i = 0; i < token.length(); i++) {
if (!isTokenChar(token.charAt(i))) {
return false;
}
}
return true;
}
/**
* Indicates if the given character is a token character (text and not a
* separator).
*
* @param character
* The character to test.
* @return True if the given character is a token character (text and not a
* separator).
*/
public static boolean isTokenChar(int character) {
return isAsciiChar(character) && !isSeparator(character);
}
/**
* Indicates if the given character is upper case (A-Z).
*
* @param character
* The character to test.
* @return True if the given character is upper case (A-Z).
*/
public static boolean isUpperCase(int character) {
return (character >= 'A') && (character <= 'Z');
}
// [ifndef gwt] method
/**
* Writes a new line.
*
* @param os
* The output stream.
* @throws IOException
*/
public static void writeCRLF(OutputStream os) throws IOException {
os.write(13); // CR
os.write(10); // LF
}
// [ifndef gwt] method
/**
* Writes a header line.
*
* @param header
* The header to write.
* @param os
* The output stream.
* @throws IOException
*/
public static void writeHeaderLine(Header header, OutputStream os)
throws IOException {
os.write(StringUtils.getAsciiBytes(header.getName()));
os.write(':');
os.write(' ');
if (header.getValue() != null) {
os.write(StringUtils.getLatin1Bytes(header.getValue()));
}
os.write(13); // CR
os.write(10); // LF
}
/**
* Private constructor to ensure that the class acts as a true utility class
* i.e. it isn't instantiable and extensible.
*/
private HeaderUtils() {
}
}