/**
* Copyright 2005-2011 Noelios Technologies.
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: LGPL 3.0 or LGPL 2.1 or CDDL 1.0 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 LGPL 3.0 license at
* http://www.opensource.org/licenses/lgpl-3.0.html
*
* You can obtain a copy of the LGPL 2.1 license at
* http://www.opensource.org/licenses/lgpl-2.1.php
*
* You can obtain a copy of the CDDL 1.0 license at
* http://www.opensource.org/licenses/cddl1.php
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* 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://www.noelios.com/products/restlet-engine
*
* Restlet is a registered trademark of Noelios Technologies.
*/
package org.restlet.engine.adapter;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.data.CacheDirective;
import org.restlet.data.ChallengeResponse;
import org.restlet.data.ClientInfo;
import org.restlet.data.Conditions;
import org.restlet.data.Cookie;
import org.restlet.data.Method;
import org.restlet.data.Parameter;
import org.restlet.data.Range;
import org.restlet.data.RecipientInfo;
import org.restlet.data.Reference;
import org.restlet.data.Tag;
import org.restlet.data.Warning;
import org.restlet.engine.header.CacheDirectiveReader;
import org.restlet.engine.header.CookieReader;
import org.restlet.engine.header.ExpectationReader;
import org.restlet.engine.header.HeaderConstants;
import org.restlet.engine.header.HeaderReader;
import org.restlet.engine.header.PreferenceReader;
import org.restlet.engine.header.RangeReader;
import org.restlet.engine.header.RecipientInfoReader;
import org.restlet.engine.header.WarningReader;
import org.restlet.engine.security.AuthenticatorUtils;
import org.restlet.engine.util.DateUtils;
import org.restlet.representation.Representation;
import org.restlet.util.Series;
/**
* Request wrapper for server HTTP calls.
*
* @author Jerome Louvel
*/
public class HttpRequest extends Request {
/**
* Adds a new header to the given request.
*
* @param request
* The request to update.
* @param headerName
* The header name to add.
* @param headerValue
* The header value to add.
*/
public static void addHeader(Request request, String headerName,
String headerValue) {
if (request instanceof HttpRequest) {
((HttpRequest) request).getHeaders().add(headerName, headerValue);
}
}
/** Indicates if the cache control data was parsed and added. */
private volatile boolean cacheDirectivesAdded;
/** Indicates if the client data was parsed and added. */
private volatile boolean clientAdded;
/** Indicates if the conditions were parsed and added. */
private volatile boolean conditionAdded;
/** The context of the HTTP server connector that issued the call. */
private volatile Context context;
/** Indicates if the cookies were parsed and added. */
private volatile boolean cookiesAdded;
/** Indicates if the request entity was added. */
private volatile boolean entityAdded;
/** The low-level HTTP call. */
private volatile ServerCall httpCall;
/** Indicates if the proxy security data was parsed and added. */
private volatile boolean proxySecurityAdded;
/** Indicates if the ranges data was parsed and added. */
private volatile boolean rangesAdded;
/** Indicates if the referrer was parsed and added. */
private volatile boolean referrerAdded;
/** Indicates if the security data was parsed and added. */
private volatile boolean securityAdded;
/** Indicates if the warning data was parsed and added. */
private volatile boolean warningsAdded;
/** Indicates if the recipients info was parsed and added. */
private volatile boolean recipientsInfoAdded;
/**
* Constructor.
*
* @param context
* The context of the HTTP server connector that issued the call.
* @param httpCall
* The low-level HTTP server call.
*/
public HttpRequest(Context context, ServerCall httpCall) {
this.context = context;
this.clientAdded = false;
this.conditionAdded = false;
this.cookiesAdded = false;
this.entityAdded = false;
this.referrerAdded = false;
this.securityAdded = false;
this.proxySecurityAdded = false;
this.recipientsInfoAdded = false;
this.warningsAdded = false;
this.httpCall = httpCall;
// Set the properties
setMethod(Method.valueOf(httpCall.getMethod()));
// Set the host reference
StringBuilder sb = new StringBuilder();
sb.append(httpCall.getProtocol().getSchemeName()).append("://");
sb.append(httpCall.getHostDomain());
if ((httpCall.getHostPort() != -1)
&& (httpCall.getHostPort() != httpCall.getProtocol()
.getDefaultPort())) {
sb.append(':').append(httpCall.getHostPort());
}
setHostRef(sb.toString());
// Set the resource reference
if (httpCall.getRequestUri() != null) {
setResourceRef(new Reference(getHostRef(), httpCall.getRequestUri()));
if (getResourceRef().isRelative()) {
// Take care of the "/" between the host part and the segments.
if (!httpCall.getRequestUri().startsWith("/")) {
setResourceRef(new Reference(getHostRef().toString() + "/"
+ httpCall.getRequestUri()));
} else {
setResourceRef(new Reference(getHostRef().toString()
+ httpCall.getRequestUri()));
}
}
setOriginalRef(getResourceRef().getTargetRef());
}
// Set the request date
String dateHeader = httpCall.getRequestHeaders().getFirstValue(
HeaderConstants.HEADER_DATE);
Date date = null;
if (dateHeader != null) {
date = DateUtils.parse(dateHeader);
}
if (date == null) {
date = new Date();
}
setDate(date);
}
@Override
public boolean abort() {
return getHttpCall().abort();
}
@Override
public List<CacheDirective> getCacheDirectives() {
List<CacheDirective> result = super.getCacheDirectives();
if (!cacheDirectivesAdded) {
for (Parameter header : getHttpCall().getRequestHeaders().subList(
HeaderConstants.HEADER_CACHE_CONTROL)) {
CacheDirectiveReader.addValues(header, result);
}
cacheDirectivesAdded = true;
}
return result;
}
@Override
public ChallengeResponse getChallengeResponse() {
ChallengeResponse result = super.getChallengeResponse();
if (!this.securityAdded) {
// Extract the header value
String authorization = getHttpCall().getRequestHeaders().getValues(
HeaderConstants.HEADER_AUTHORIZATION);
// Set the challenge response
result = AuthenticatorUtils.parseResponse(this, authorization,
getHttpCall().getRequestHeaders());
setChallengeResponse(result);
this.securityAdded = true;
}
return result;
}
/**
* Returns the client-specific information.
*
* @return The client-specific information.
*/
@Override
public ClientInfo getClientInfo() {
final ClientInfo result = super.getClientInfo();
if (!this.clientAdded) {
// Extract the header values
String acceptMediaType = getHttpCall().getRequestHeaders()
.getValues(HeaderConstants.HEADER_ACCEPT);
String acceptCharset = getHttpCall().getRequestHeaders().getValues(
HeaderConstants.HEADER_ACCEPT_CHARSET);
String acceptEncoding = getHttpCall().getRequestHeaders()
.getValues(HeaderConstants.HEADER_ACCEPT_ENCODING);
String acceptLanguage = getHttpCall().getRequestHeaders()
.getValues(HeaderConstants.HEADER_ACCEPT_LANGUAGE);
String expect = getHttpCall().getRequestHeaders().getValues(
HeaderConstants.HEADER_EXPECT);
// Parse the headers and update the call preferences
// Parse the Accept* headers. If an error occurs during the parsing
// of each header, the error is traced and we keep on with the other
// headers.
try {
PreferenceReader.addCharacterSets(acceptCharset, result);
} catch (Exception e) {
this.context.getLogger().log(Level.INFO, e.getMessage());
}
try {
PreferenceReader.addEncodings(acceptEncoding, result);
} catch (Exception e) {
this.context.getLogger().log(Level.INFO, e.getMessage());
}
try {
PreferenceReader.addLanguages(acceptLanguage, result);
} catch (Exception e) {
this.context.getLogger().log(Level.INFO, e.getMessage());
}
try {
PreferenceReader.addMediaTypes(acceptMediaType, result);
} catch (Exception e) {
this.context.getLogger().log(Level.INFO, e.getMessage());
}
try {
ExpectationReader.addValues(expect, result);
} catch (Exception e) {
this.context.getLogger().log(Level.INFO, e.getMessage());
}
// Set other properties
result.setAgent(getHttpCall().getRequestHeaders().getValues(
HeaderConstants.HEADER_USER_AGENT));
result.setFrom(getHttpCall().getRequestHeaders().getFirstValue(
HeaderConstants.HEADER_FROM));
result.setAddress(getHttpCall().getClientAddress());
result.setPort(getHttpCall().getClientPort());
if (getHttpCall().getUserPrincipal() != null) {
result.getPrincipals().add(getHttpCall().getUserPrincipal());
}
if (this.context != null) {
// Special handling for the non standard but common
// "X-Forwarded-For" header.
final boolean useForwardedForHeader = Boolean
.parseBoolean(this.context.getParameters()
.getFirstValue("useForwardedForHeader", false));
if (useForwardedForHeader) {
// Lookup the "X-Forwarded-For" header supported by popular
// proxies and caches.
final String header = getHttpCall().getRequestHeaders()
.getValues(HeaderConstants.HEADER_X_FORWARDED_FOR);
if (header != null) {
final String[] addresses = header.split(",");
for (int i = 0; i < addresses.length; i++) {
String address = addresses[i].trim();
result.getForwardedAddresses().add(address);
}
}
}
}
this.clientAdded = true;
}
return result;
}
/**
* Returns the condition data applying to this call.
*
* @return The condition data applying to this call.
*/
@Override
public Conditions getConditions() {
final Conditions result = super.getConditions();
if (!this.conditionAdded) {
// Extract the header values
String ifMatchHeader = getHttpCall().getRequestHeaders().getValues(
HeaderConstants.HEADER_IF_MATCH);
String ifNoneMatchHeader = getHttpCall().getRequestHeaders()
.getValues(HeaderConstants.HEADER_IF_NONE_MATCH);
Date ifModifiedSince = null;
Date ifUnmodifiedSince = null;
String ifRangeHeader = getHttpCall().getRequestHeaders()
.getFirstValue(HeaderConstants.HEADER_IF_RANGE);
for (Parameter header : getHttpCall().getRequestHeaders()) {
if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_IF_MODIFIED_SINCE)) {
ifModifiedSince = HeaderReader.readDate(header.getValue(),
false);
} else if (header.getName().equalsIgnoreCase(
HeaderConstants.HEADER_IF_UNMODIFIED_SINCE)) {
ifUnmodifiedSince = HeaderReader.readDate(
header.getValue(), false);
}
}
// Set the If-Modified-Since date
if ((ifModifiedSince != null) && (ifModifiedSince.getTime() != -1)) {
result.setModifiedSince(ifModifiedSince);
}
// Set the If-Unmodified-Since date
if ((ifUnmodifiedSince != null)
&& (ifUnmodifiedSince.getTime() != -1)) {
result.setUnmodifiedSince(ifUnmodifiedSince);
}
// Set the If-Match tags
List<Tag> match = null;
Tag current = null;
if (ifMatchHeader != null) {
try {
HeaderReader<Object> hr = new HeaderReader<Object>(
ifMatchHeader);
String value = hr.readRawValue();
while (value != null) {
current = Tag.parse(value);
// Is it the first tag?
if (match == null) {
match = new ArrayList<Tag>();
result.setMatch(match);
}
// Add the new tag
match.add(current);
// Read the next token
value = hr.readRawValue();
}
} catch (Exception e) {
this.context.getLogger().log(
Level.INFO,
"Unable to process the if-match header: "
+ ifMatchHeader);
}
}
// Set the If-None-Match tags
List<Tag> noneMatch = null;
if (ifNoneMatchHeader != null) {
try {
HeaderReader<Object> hr = new HeaderReader<Object>(
ifNoneMatchHeader);
String value = hr.readRawValue();
while (value != null) {
current = Tag.parse(value);
// Is it the first tag?
if (noneMatch == null) {
noneMatch = new ArrayList<Tag>();
result.setNoneMatch(noneMatch);
}
noneMatch.add(current);
// Read the next token
value = hr.readRawValue();
}
} catch (Exception e) {
this.context.getLogger().log(
Level.INFO,
"Unable to process the if-none-match header: "
+ ifNoneMatchHeader);
}
}
if (ifRangeHeader != null && ifRangeHeader.length() > 0) {
Tag tag = Tag.parse(ifRangeHeader);
if (tag != null) {
result.setRangeTag(tag);
} else {
Date date = HeaderReader.readDate(ifRangeHeader, false);
result.setRangeDate(date);
}
}
this.conditionAdded = true;
}
return result;
}
/**
* Returns the cookies provided by the client.
*
* @return The cookies provided by the client.
*/
@Override
public Series<Cookie> getCookies() {
final Series<Cookie> result = super.getCookies();
if (!this.cookiesAdded) {
String cookieValues = getHttpCall().getRequestHeaders().getValues(
HeaderConstants.HEADER_COOKIE);
if (cookieValues != null) {
new CookieReader(cookieValues).addValues(result);
}
this.cookiesAdded = true;
}
return result;
}
/**
* Returns the representation provided by the client.
*
* @return The representation provided by the client.
*/
@Override
public Representation getEntity() {
if (!this.entityAdded) {
setEntity(((ServerCall) getHttpCall()).getRequestEntity());
this.entityAdded = true;
}
return super.getEntity();
}
/**
* Returns the HTTP headers.
*
* @return The HTTP headers.
*/
@SuppressWarnings("unchecked")
public Series<Parameter> getHeaders() {
return (Series<Parameter>) getAttributes().get(
HeaderConstants.ATTRIBUTE_HEADERS);
}
/**
* Returns the low-level HTTP call.
*
* @return The low-level HTTP call.
*/
public ServerCall getHttpCall() {
return this.httpCall;
}
@Override
public ChallengeResponse getProxyChallengeResponse() {
ChallengeResponse result = super.getProxyChallengeResponse();
if (!this.proxySecurityAdded) {
// Extract the header value
final String authorization = getHttpCall().getRequestHeaders()
.getValues(HeaderConstants.HEADER_PROXY_AUTHORIZATION);
// Set the challenge response
result = AuthenticatorUtils.parseResponse(this, authorization,
getHttpCall().getRequestHeaders());
setProxyChallengeResponse(result);
this.proxySecurityAdded = true;
}
return result;
}
@Override
public List<Range> getRanges() {
final List<Range> result = super.getRanges();
if (!this.rangesAdded) {
// Extract the header value
final String ranges = getHttpCall().getRequestHeaders().getValues(
HeaderConstants.HEADER_RANGE);
result.addAll(RangeReader.read(ranges));
this.rangesAdded = true;
}
return result;
}
@Override
public List<RecipientInfo> getRecipientsInfo() {
List<RecipientInfo> result = super.getRecipientsInfo();
if (!recipientsInfoAdded) {
for (String header : getHttpCall().getRequestHeaders()
.getValuesArray(HeaderConstants.HEADER_VIA)) {
new RecipientInfoReader(header).addValues(result);
}
recipientsInfoAdded = true;
}
return result;
}
/**
* Returns the referrer reference if available.
*
* @return The referrer reference.
*/
@Override
public Reference getReferrerRef() {
if (!this.referrerAdded) {
final String referrerValue = getHttpCall().getRequestHeaders()
.getValues(HeaderConstants.HEADER_REFERRER);
if (referrerValue != null) {
setReferrerRef(new Reference(referrerValue));
}
this.referrerAdded = true;
}
return super.getReferrerRef();
}
@Override
public List<Warning> getWarnings() {
List<Warning> result = super.getWarnings();
if (!warningsAdded) {
for (String header : getHttpCall().getRequestHeaders()
.getValuesArray(HeaderConstants.HEADER_WARNING)) {
new WarningReader(header).addValues(result);
}
warningsAdded = true;
}
return result;
}
@Override
public void setChallengeResponse(ChallengeResponse response) {
super.setChallengeResponse(response);
this.securityAdded = true;
}
@Override
public void setEntity(Representation entity) {
super.setEntity(entity);
this.entityAdded = true;
}
@Override
public void setProxyChallengeResponse(ChallengeResponse response) {
super.setProxyChallengeResponse(response);
this.proxySecurityAdded = true;
}
@Override
public void setRecipientsInfo(List<RecipientInfo> recipientsInfo) {
super.setRecipientsInfo(recipientsInfo);
this.recipientsInfoAdded = true;
}
@Override
public void setWarnings(List<Warning> warnings) {
super.setWarnings(warnings);
this.warningsAdded = true;
}
}