//-----------------------------------------------------------------------
//
// Copyright (c) Andrew Arnott. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOpenAuth.Messaging {
using System;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Mime;
using System.ServiceModel.Channels;
using System.Web;
///
/// A property store of details of an incoming HTTP request.
///
///
/// This serves a very similar purpose to , except that
/// ASP.NET does not let us fully initialize that class, so we have to write one
/// of our one.
///
public class HttpRequestInfo {
///
/// The key/value pairs found in the entity of a POST request.
///
private NameValueCollection form;
///
/// The key/value pairs found in the querystring of the incoming request.
///
private NameValueCollection queryString;
///
/// Backing field for the property.
///
private NameValueCollection queryStringBeforeRewriting;
///
/// Backing field for the property.
///
private IDirectedProtocolMessage message;
///
/// Initializes a new instance of the class.
///
/// The ASP.NET structure to copy from.
public HttpRequestInfo(HttpRequest request) {
Requires.NotNull(request, "request");
Contract.Ensures(this.HttpMethod == request.HttpMethod);
Contract.Ensures(this.Url == request.Url);
Contract.Ensures(this.RawUrl == request.RawUrl);
Contract.Ensures(this.UrlBeforeRewriting != null);
Contract.Ensures(this.Headers != null);
Contract.Ensures(this.InputStream == request.InputStream);
Contract.Ensures(this.form == request.Form);
Contract.Ensures(this.queryString == request.QueryString);
this.HttpMethod = request.HttpMethod;
this.Url = request.Url;
this.UrlBeforeRewriting = GetPublicFacingUrl(request);
this.RawUrl = request.RawUrl;
this.Headers = GetHeaderCollection(request.Headers);
this.InputStream = request.InputStream;
// These values would normally be calculated, but we'll reuse them from
// HttpRequest since they're already calculated, and there's a chance ()
// that ASP.NET does a better job of being comprehensive about gathering
// these as well.
this.form = request.Form;
this.queryString = request.QueryString;
Reporting.RecordRequestStatistics(this);
}
///
/// Initializes a new instance of the class.
///
/// The HTTP method (i.e. GET or POST) of the incoming request.
/// The URL being requested.
/// The raw URL that appears immediately following the HTTP verb in the request,
/// before any URL rewriting takes place.
/// Headers in the HTTP request.
/// The entity stream, if any. (POST requests typically have these). Use null for GET requests.
public HttpRequestInfo(string httpMethod, Uri requestUrl, string rawUrl, WebHeaderCollection headers, Stream inputStream) {
Requires.NotNullOrEmpty(httpMethod, "httpMethod");
Requires.NotNull(requestUrl, "requestUrl");
Requires.NotNull(rawUrl, "rawUrl");
Requires.NotNull(headers, "headers");
this.HttpMethod = httpMethod;
this.Url = requestUrl;
this.UrlBeforeRewriting = requestUrl;
this.RawUrl = rawUrl;
this.Headers = headers;
this.InputStream = inputStream;
Reporting.RecordRequestStatistics(this);
}
///
/// Initializes a new instance of the class.
///
/// Details on the incoming HTTP request.
public HttpRequestInfo(HttpListenerRequest listenerRequest) {
Requires.NotNull(listenerRequest, "listenerRequest");
this.HttpMethod = listenerRequest.HttpMethod;
this.Url = listenerRequest.Url;
this.UrlBeforeRewriting = listenerRequest.Url;
this.RawUrl = listenerRequest.RawUrl;
this.Headers = new WebHeaderCollection();
foreach (string key in listenerRequest.Headers) {
this.Headers[key] = listenerRequest.Headers[key];
}
this.InputStream = listenerRequest.InputStream;
Reporting.RecordRequestStatistics(this);
}
///
/// Initializes a new instance of the class.
///
/// The WCF incoming request structure to get the HTTP information from.
/// The URI of the service endpoint.
public HttpRequestInfo(HttpRequestMessageProperty request, Uri requestUri) {
Requires.NotNull(request, "request");
Requires.NotNull(requestUri, "requestUri");
this.HttpMethod = request.Method;
this.Headers = request.Headers;
this.Url = requestUri;
this.UrlBeforeRewriting = requestUri;
this.RawUrl = MakeUpRawUrlFromUrl(requestUri);
Reporting.RecordRequestStatistics(this);
}
///
/// Initializes a new instance of the class.
///
internal HttpRequestInfo() {
Contract.Ensures(this.HttpMethod == "GET");
Contract.Ensures(this.Headers != null);
this.HttpMethod = "GET";
this.Headers = new WebHeaderCollection();
}
///
/// Initializes a new instance of the class.
///
/// The HttpWebRequest (that was never used) to copy from.
internal HttpRequestInfo(WebRequest request) {
Requires.NotNull(request, "request");
this.HttpMethod = request.Method;
this.Url = request.RequestUri;
this.UrlBeforeRewriting = request.RequestUri;
this.RawUrl = MakeUpRawUrlFromUrl(request.RequestUri);
this.Headers = GetHeaderCollection(request.Headers);
this.InputStream = null;
Reporting.RecordRequestStatistics(this);
}
///
/// Initializes a new instance of the class.
///
/// The message being passed in through a mock transport. May be null.
/// The HTTP method that the incoming request came in on, whether or not is null.
internal HttpRequestInfo(IDirectedProtocolMessage message, HttpDeliveryMethods httpMethod) {
this.message = message;
this.HttpMethod = MessagingUtilities.GetHttpVerb(httpMethod);
}
///
/// Gets or sets the message that is being sent over a mock transport (for testing).
///
internal virtual IDirectedProtocolMessage Message {
get { return this.message; }
set { this.message = value; }
}
///
/// Gets or sets the verb in the request (i.e. GET, POST, etc.)
///
internal string HttpMethod { get; set; }
///
/// Gets or sets the entire URL of the request, after any URL rewriting.
///
internal Uri Url { get; set; }
///
/// Gets or sets the raw URL that appears immediately following the HTTP verb in the request,
/// before any URL rewriting takes place.
///
internal string RawUrl { get; set; }
///
/// Gets or sets the full public URL used by the remote client to initiate this request,
/// before any URL rewriting and before any changes made by web farm load distributors.
///
internal Uri UrlBeforeRewriting { get; set; }
///
/// Gets the query part of the URL (The ? and everything after it), after URL rewriting.
///
internal string Query {
get { return this.Url != null ? this.Url.Query : null; }
}
///
/// Gets or sets the collection of headers that came in with the request.
///
internal WebHeaderCollection Headers { get; set; }
///
/// Gets or sets the entity, or body of the request, if any.
///
internal Stream InputStream { get; set; }
///
/// Gets the key/value pairs found in the entity of a POST request.
///
internal NameValueCollection Form {
get {
Contract.Ensures(Contract.Result() != null);
if (this.form == null) {
ContentType contentType = string.IsNullOrEmpty(this.Headers[HttpRequestHeader.ContentType]) ? null : new ContentType(this.Headers[HttpRequestHeader.ContentType]);
if (this.HttpMethod == "POST" && contentType != null && string.Equals(contentType.MediaType, Channel.HttpFormUrlEncoded, StringComparison.Ordinal)) {
StreamReader reader = new StreamReader(this.InputStream);
long originalPosition = 0;
if (this.InputStream.CanSeek) {
originalPosition = this.InputStream.Position;
}
this.form = HttpUtility.ParseQueryString(reader.ReadToEnd());
if (this.InputStream.CanSeek) {
this.InputStream.Seek(originalPosition, SeekOrigin.Begin);
}
} else {
this.form = new NameValueCollection();
}
}
return this.form;
}
}
///
/// Gets the key/value pairs found in the querystring of the incoming request.
///
internal NameValueCollection QueryString {
get {
if (this.queryString == null) {
this.queryString = this.Query != null ? HttpUtility.ParseQueryString(this.Query) : new NameValueCollection();
}
return this.queryString;
}
}
///
/// Gets the query data from the original request (before any URL rewriting has occurred.)
///
/// A containing all the parameters in the query string.
internal NameValueCollection QueryStringBeforeRewriting {
get {
if (this.queryStringBeforeRewriting == null) {
// This request URL may have been rewritten by the host site.
// For openid protocol purposes, we really need to look at
// the original query parameters before any rewriting took place.
if (!this.IsUrlRewritten) {
// No rewriting has taken place.
this.queryStringBeforeRewriting = this.QueryString;
} else {
// Rewriting detected! Recover the original request URI.
ErrorUtilities.VerifyInternal(this.UrlBeforeRewriting != null, "UrlBeforeRewriting is null, so the query string cannot be determined.");
this.queryStringBeforeRewriting = HttpUtility.ParseQueryString(this.UrlBeforeRewriting.Query);
}
}
return this.queryStringBeforeRewriting;
}
}
///
/// Gets a value indicating whether the request's URL was rewritten by ASP.NET
/// or some other module.
///
///
/// true if this request's URL was rewritten; otherwise, false.
///
internal bool IsUrlRewritten {
get { return this.Url != this.UrlBeforeRewriting; }
}
///
/// Gets the public facing URL for the given incoming HTTP request.
///
/// The request.
/// The server variables to consider part of the request.
///
/// The URI that the outside world used to create this request.
///
///
/// Although the value can be obtained from
/// , it's useful to be able to pass them
/// in so we can simulate injected values from our unit tests since the actual property
/// is a read-only kind of .
///
internal static Uri GetPublicFacingUrl(HttpRequest request, NameValueCollection serverVariables) {
Requires.NotNull(request, "request");
Requires.NotNull(serverVariables, "serverVariables");
// Due to URL rewriting, cloud computing (i.e. Azure)
// and web farms, etc., we have to be VERY careful about what
// we consider the incoming URL. We want to see the URL as it would
// appear on the public-facing side of the hosting web site.
// HttpRequest.Url gives us the internal URL in a cloud environment,
// So we use a variable that (at least from what I can tell) gives us
// the public URL:
if (serverVariables["HTTP_HOST"] != null) {
ErrorUtilities.VerifySupported(request.Url.Scheme == Uri.UriSchemeHttps || request.Url.Scheme == Uri.UriSchemeHttp, "Only HTTP and HTTPS are supported protocols.");
string scheme = serverVariables["HTTP_X_FORWARDED_PROTO"] ?? request.Url.Scheme;
Uri hostAndPort = new Uri(scheme + Uri.SchemeDelimiter + serverVariables["HTTP_HOST"]);
UriBuilder publicRequestUri = new UriBuilder(request.Url);
publicRequestUri.Scheme = scheme;
publicRequestUri.Host = hostAndPort.Host;
publicRequestUri.Port = hostAndPort.Port; // CC missing Uri.Port contract that's on UriBuilder.Port
return publicRequestUri.Uri;
} else {
// Failover to the method that works for non-web farm enviroments.
// We use Request.Url for the full path to the server, and modify it
// with Request.RawUrl to capture both the cookieless session "directory" if it exists
// and the original path in case URL rewriting is going on. We don't want to be
// fooled by URL rewriting because we're comparing the actual URL with what's in
// the return_to parameter in some cases.
// Response.ApplyAppPathModifier(builder.Path) would have worked for the cookieless
// session, but not the URL rewriting problem.
return new Uri(request.Url, request.RawUrl);
}
}
///
/// Gets the query or form data from the original request (before any URL rewriting has occurred.)
///
/// A set of name=value pairs.
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Expensive call")]
internal NameValueCollection GetQueryOrFormFromContext() {
NameValueCollection query;
if (this.HttpMethod == "GET") {
query = this.QueryStringBeforeRewriting;
} else {
query = this.Form;
}
return query;
}
///
/// Gets the public facing URL for the given incoming HTTP request.
///
/// The request.
/// The URI that the outside world used to create this request.
private static Uri GetPublicFacingUrl(HttpRequest request) {
Requires.NotNull(request, "request");
return GetPublicFacingUrl(request, request.ServerVariables);
}
///
/// Makes up a reasonable guess at the raw URL from the possibly rewritten URL.
///
/// A full URL.
/// A raw URL that might have come in on the HTTP verb.
private static string MakeUpRawUrlFromUrl(Uri url) {
Requires.NotNull(url, "url");
return url.AbsolutePath + url.Query + url.Fragment;
}
///
/// Converts a NameValueCollection to a WebHeaderCollection.
///
/// The collection a HTTP headers.
/// A new collection of the given headers.
private static WebHeaderCollection GetHeaderCollection(NameValueCollection pairs) {
Requires.NotNull(pairs, "pairs");
WebHeaderCollection headers = new WebHeaderCollection();
foreach (string key in pairs) {
try {
headers.Add(key, pairs[key]);
} catch (ArgumentException ex) {
Logger.Messaging.WarnFormat(
"{0} thrown when trying to add web header \"{1}: {2}\". {3}",
ex.GetType().Name,
key,
pairs[key],
ex.Message);
}
}
return headers;
}
#if CONTRACTS_FULL
///
/// Verifies conditions that should be true for any valid state of this object.
///
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")]
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")]
[ContractInvariantMethod]
private void ObjectInvariant() {
}
#endif
}
}