001: /*
002: * Copyright 2005-2007 Noelios Consulting.
003: *
004: * The contents of this file are subject to the terms of the Common Development
005: * and Distribution License (the "License"). You may not use this file except in
006: * compliance with the License.
007: *
008: * You can obtain a copy of the license at
009: * http://www.opensource.org/licenses/cddl1.txt See the License for the specific
010: * language governing permissions and limitations under the License.
011: *
012: * When distributing Covered Code, include this CDDL HEADER in each file and
013: * include the License file at http://www.opensource.org/licenses/cddl1.txt If
014: * applicable, add the following below this CDDL HEADER, with the fields
015: * enclosed by brackets "[]" replaced with your own identifying information:
016: * Portions Copyright [yyyy] [name of copyright owner]
017: */
018:
019: package com.noelios.restlet.http;
020:
021: import java.util.ArrayList;
022: import java.util.Date;
023: import java.util.List;
024: import java.util.logging.Level;
025:
026: import org.restlet.Context;
027: import org.restlet.data.ChallengeResponse;
028: import org.restlet.data.ClientInfo;
029: import org.restlet.data.Conditions;
030: import org.restlet.data.Cookie;
031: import org.restlet.data.Method;
032: import org.restlet.data.Parameter;
033: import org.restlet.data.Reference;
034: import org.restlet.data.Request;
035: import org.restlet.data.Tag;
036: import org.restlet.resource.Representation;
037: import org.restlet.util.Series;
038:
039: import com.noelios.restlet.util.CookieReader;
040: import com.noelios.restlet.util.HeaderReader;
041: import com.noelios.restlet.util.PreferenceUtils;
042: import com.noelios.restlet.util.SecurityUtils;
043:
044: /**
045: * Request wrapper for server HTTP calls.
046: *
047: * @author Jerome Louvel (contact@noelios.com)
048: */
049: public class HttpRequest extends Request {
050: /** The context of the HTTP server connector that issued the call. */
051: private Context context;
052:
053: /** The low-level HTTP call. */
054: private HttpCall httpCall;
055:
056: /** Indicates if the client data was parsed and added. */
057: private boolean clientAdded;
058:
059: /** Indicates if the conditions were parsed and added. */
060: private boolean conditionAdded;
061:
062: /** Indicates if the cookies were parsed and added. */
063: private boolean cookiesAdded;
064:
065: /** Indicates if the request entity was added. */
066: private boolean entityAdded;
067:
068: /** Indicates if the referrer was parsed and added. */
069: private boolean referrerAdded;
070:
071: /** Indicates if the security data was parsed and added. */
072: private boolean securityAdded;
073:
074: /**
075: * Constructor.
076: *
077: * @param context
078: * The context of the HTTP server connector that issued the call.
079: * @param httpCall
080: * The low-level HTTP server call.
081: */
082: public HttpRequest(Context context, HttpServerCall httpCall) {
083: this .context = context;
084: this .clientAdded = false;
085: this .conditionAdded = false;
086: this .cookiesAdded = false;
087: this .entityAdded = false;
088: this .referrerAdded = false;
089: this .securityAdded = false;
090: this .httpCall = httpCall;
091:
092: // Set the properties
093: setMethod(Method.valueOf(httpCall.getMethod()));
094:
095: if (getHttpCall().isConfidential()) {
096: setConfidential(true);
097: } else {
098: // We don't want to autocreate the security data just for this
099: // information, because that will by the default value of this
100: // property if read by someone.
101: }
102:
103: // Set the host reference
104: StringBuilder sb = new StringBuilder();
105: sb.append(httpCall.getProtocol().getSchemeName()).append("://");
106: sb.append(httpCall.getHostDomain());
107: if ((httpCall.getHostPort() != -1)
108: && (httpCall.getHostPort() != httpCall.getProtocol()
109: .getDefaultPort())) {
110: sb.append(':').append(httpCall.getHostPort());
111: }
112: setHostRef(sb.toString());
113:
114: // Set the resource reference
115: setResourceRef(new Reference(getHostRef(), httpCall
116: .getRequestUri()));
117: if (getResourceRef().isRelative()) {
118: // Take care of the "/" between the host part and the segments.
119: if (!httpCall.getRequestUri().startsWith("/")) {
120: setResourceRef(new Reference(getHostRef(), getHostRef()
121: .toString()
122: + "/" + httpCall.getRequestUri()));
123: } else {
124: setResourceRef(new Reference(getHostRef(), getHostRef()
125: .toString()
126: + httpCall.getRequestUri()));
127: }
128: }
129: }
130:
131: /**
132: * Returns the client-specific information.
133: *
134: * @return The client-specific information.
135: */
136: public ClientInfo getClientInfo() {
137: ClientInfo result = super .getClientInfo();
138:
139: if (!this .clientAdded) {
140: // Extract the header values
141: String acceptCharset = getHttpCall().getRequestHeaders()
142: .getValues(HttpConstants.HEADER_ACCEPT_CHARSET);
143: String acceptEncoding = getHttpCall().getRequestHeaders()
144: .getValues(HttpConstants.HEADER_ACCEPT_ENCODING);
145: String acceptLanguage = getHttpCall().getRequestHeaders()
146: .getValues(HttpConstants.HEADER_ACCEPT_LANGUAGE);
147: String acceptMediaType = getHttpCall().getRequestHeaders()
148: .getValues(HttpConstants.HEADER_ACCEPT);
149:
150: // Parse the headers and update the call preferences
151:
152: // Parse the Accept* headers. If an error occurs during the parsing
153: // of each header, the error is traced and we keep on with the other
154: // headers.
155: try {
156: PreferenceUtils.parseCharacterSets(acceptCharset,
157: result);
158: } catch (Exception e) {
159: this .context.getLogger()
160: .log(Level.INFO, e.getMessage());
161: }
162: try {
163: PreferenceUtils.parseEncodings(acceptEncoding, result);
164: } catch (Exception e) {
165: this .context.getLogger()
166: .log(Level.INFO, e.getMessage());
167: }
168: try {
169: PreferenceUtils.parseLanguages(acceptLanguage, result);
170: } catch (Exception e) {
171: this .context.getLogger()
172: .log(Level.INFO, e.getMessage());
173: }
174: try {
175: PreferenceUtils
176: .parseMediaTypes(acceptMediaType, result);
177: } catch (Exception e) {
178: this .context.getLogger()
179: .log(Level.INFO, e.getMessage());
180: }
181:
182: // Set other properties
183: result.setAgent(getHttpCall().getRequestHeaders()
184: .getValues(HttpConstants.HEADER_USER_AGENT));
185: result.setAddress(getHttpCall().getClientAddress());
186: result.setPort(getHttpCall().getClientPort());
187:
188: // Special handling for the non standard but common
189: // "X-Forwarded-For" header.
190: boolean useForwardedForHeader = Boolean
191: .parseBoolean(this .context.getParameters()
192: .getFirstValue("useForwardedForHeader",
193: false));
194: if (useForwardedForHeader) {
195: // Lookup the "X-Forwarded-For" header supported by popular
196: // proxies and caches.
197: // This information is only safe for intermediary components
198: // within your local network.
199: // Other addresses could easily be changed by setting a fake
200: // header and should not be trusted for serious security checks.
201: String header = getHttpCall()
202: .getRequestHeaders()
203: .getValues(HttpConstants.HEADER_X_FORWARDED_FOR);
204: if (header != null) {
205: String[] addresses = header.split(",");
206: for (int i = addresses.length - 1; i >= 0; i--) {
207: result.getAddresses().add(addresses[i].trim());
208: }
209: }
210: }
211:
212: this .clientAdded = true;
213: }
214:
215: return result;
216: }
217:
218: /**
219: * Returns the condition data applying to this call.
220: *
221: * @return The condition data applying to this call.
222: */
223: public Conditions getConditions() {
224: Conditions result = super .getConditions();
225:
226: if (!this .conditionAdded) {
227: // Extract the header values
228: String ifMatchHeader = getHttpCall().getRequestHeaders()
229: .getValues(HttpConstants.HEADER_IF_MATCH);
230: String ifNoneMatchHeader = getHttpCall()
231: .getRequestHeaders().getValues(
232: HttpConstants.HEADER_IF_NONE_MATCH);
233: Date ifModifiedSince = null;
234: Date ifUnmodifiedSince = null;
235:
236: for (Parameter header : getHttpCall().getRequestHeaders()) {
237: if (header.getName().equalsIgnoreCase(
238: HttpConstants.HEADER_IF_MODIFIED_SINCE)) {
239: ifModifiedSince = getHttpCall().parseDate(
240: header.getValue(), false);
241: } else if (header.getName().equalsIgnoreCase(
242: HttpConstants.HEADER_IF_UNMODIFIED_SINCE)) {
243: ifUnmodifiedSince = getHttpCall().parseDate(
244: header.getValue(), false);
245: }
246: }
247:
248: // Set the If-Modified-Since date
249: if ((ifModifiedSince != null)
250: && (ifModifiedSince.getTime() != -1)) {
251: result.setModifiedSince(ifModifiedSince);
252: }
253:
254: // Set the If-Unmodified-Since date
255: if ((ifUnmodifiedSince != null)
256: && (ifUnmodifiedSince.getTime() != -1)) {
257: result.setUnmodifiedSince(ifUnmodifiedSince);
258: }
259:
260: // Set the If-Match tags
261: List<Tag> match = null;
262: Tag current = null;
263: if (ifMatchHeader != null) {
264: try {
265: HeaderReader hr = new HeaderReader(ifMatchHeader);
266: String value = hr.readValue();
267: while (value != null) {
268: current = Tag.parse(value);
269:
270: // Is it the first tag?
271: if (match == null) {
272: match = new ArrayList<Tag>();
273: result.setMatch(match);
274: }
275:
276: // Add the new tag
277: match.add(current);
278:
279: // Read the next token
280: value = hr.readValue();
281: }
282: } catch (Exception e) {
283: this .context.getLogger().log(
284: Level.INFO,
285: "Unable to process the if-match header: "
286: + ifMatchHeader);
287: }
288: }
289:
290: // Set the If-None-Match tags
291: List<Tag> noneMatch = null;
292: if (ifNoneMatchHeader != null) {
293: try {
294: HeaderReader hr = new HeaderReader(
295: ifNoneMatchHeader);
296: String value = hr.readValue();
297: while (value != null) {
298: current = Tag.parse(value);
299:
300: // Is it the first tag?
301: if (noneMatch == null) {
302: noneMatch = new ArrayList<Tag>();
303: result.setNoneMatch(noneMatch);
304: }
305:
306: noneMatch.add(current);
307:
308: // Read the next token
309: value = hr.readValue();
310: }
311: } catch (Exception e) {
312: this .context.getLogger().log(
313: Level.INFO,
314: "Unable to process the if-none-match header: "
315: + ifNoneMatchHeader);
316: }
317: }
318:
319: this .conditionAdded = true;
320: }
321:
322: return result;
323: }
324:
325: /**
326: * Returns the low-level HTTP call.
327: *
328: * @return The low-level HTTP call.
329: */
330: public HttpCall getHttpCall() {
331: return this .httpCall;
332: }
333:
334: /**
335: * Returns the cookies provided by the client.
336: *
337: * @return The cookies provided by the client.
338: */
339: public Series<Cookie> getCookies() {
340: Series<Cookie> result = super .getCookies();
341:
342: if (!cookiesAdded) {
343: String cookiesValue = getHttpCall().getRequestHeaders()
344: .getValues(HttpConstants.HEADER_COOKIE);
345:
346: if (cookiesValue != null) {
347: try {
348: CookieReader cr = new CookieReader(this .context
349: .getLogger(), cookiesValue);
350: Cookie current = cr.readCookie();
351: while (current != null) {
352: result.add(current);
353: current = cr.readCookie();
354: }
355: } catch (Exception e) {
356: this .context.getLogger().log(
357: Level.WARNING,
358: "An exception occurred during cookies parsing. Headers value: "
359: + cookiesValue, e);
360: }
361: }
362:
363: this .cookiesAdded = true;
364: }
365:
366: return result;
367: }
368:
369: /**
370: * Returns the representation provided by the client.
371: *
372: * @return The representation provided by the client.
373: */
374: public Representation getEntity() {
375: if (!this .entityAdded) {
376: setEntity(((HttpServerCall) getHttpCall())
377: .getRequestEntity());
378: this .entityAdded = true;
379: }
380:
381: return super .getEntity();
382: }
383:
384: /**
385: * Returns the referrer reference if available.
386: *
387: * @return The referrer reference.
388: */
389: public Reference getReferrerRef() {
390: if (!this .referrerAdded) {
391: String referrerValue = getHttpCall().getRequestHeaders()
392: .getValues(HttpConstants.HEADER_REFERRER);
393: if (referrerValue != null) {
394: setReferrerRef(new Reference(referrerValue));
395: }
396:
397: this .referrerAdded = true;
398: }
399:
400: return super .getReferrerRef();
401: }
402:
403: @Override
404: public ChallengeResponse getChallengeResponse() {
405: ChallengeResponse result = super .getChallengeResponse();
406:
407: if (!this .securityAdded) {
408: // Extract the header value
409: String authorization = getHttpCall().getRequestHeaders()
410: .getValues(HttpConstants.HEADER_AUTHORIZATION);
411:
412: // Set the challenge response
413: result = SecurityUtils.parseResponse(this , this .context
414: .getLogger(), authorization);
415: setChallengeResponse(result);
416: this .securityAdded = true;
417: }
418:
419: return result;
420: }
421:
422: @Override
423: public void setChallengeResponse(ChallengeResponse response) {
424: super .setChallengeResponse(response);
425: this .securityAdded = true;
426: }
427:
428: @Override
429: public void setEntity(Representation entity) {
430: super .setEntity(entity);
431: this .entityAdded = true;
432: }
433: }
|