//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Specialized; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Net.Mime; using System.ServiceModel.Channels; using System.Web; using Validation; /// /// 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 : HttpRequestBase { /// /// The HTTP verb in the request. /// private readonly string httpMethod; /// /// The full request URL. /// private readonly Uri requestUri; /// /// The HTTP headers. /// private readonly NameValueCollection headers; /// /// The variables defined in the query part of the URL. /// private readonly NameValueCollection queryString; /// /// The POSTed form variables. /// private readonly NameValueCollection form; /// /// The server variables collection. /// private readonly NameValueCollection serverVariables; /// /// The backing field for the property. /// private readonly HttpCookieCollection cookies; /// /// Initializes a new instance of the class. /// /// The request. /// The request URI. internal HttpRequestInfo(HttpRequestMessageProperty request, Uri requestUri) { Requires.NotNull(request, "request"); Requires.NotNull(requestUri, "requestUri"); this.httpMethod = request.Method; this.headers = request.Headers; this.requestUri = requestUri; this.form = new NameValueCollection(); this.queryString = HttpUtility.ParseQueryString(requestUri.Query); this.serverVariables = new NameValueCollection(); this.cookies = new HttpCookieCollection(); Reporting.RecordRequestStatistics(this); } /// /// Initializes a new instance of the class. /// /// The HTTP method. /// The request URI. /// The form variables. /// The HTTP headers. /// The cookies in the request. internal HttpRequestInfo(string httpMethod, Uri requestUri, NameValueCollection form = null, NameValueCollection headers = null, HttpCookieCollection cookies = null) { Requires.NotNullOrEmpty(httpMethod, "httpMethod"); Requires.NotNull(requestUri, "requestUri"); this.httpMethod = httpMethod; this.requestUri = requestUri; this.form = form ?? new NameValueCollection(); this.queryString = HttpUtility.ParseQueryString(requestUri.Query); this.headers = headers ?? new WebHeaderCollection(); this.serverVariables = new NameValueCollection(); this.cookies = cookies ?? new HttpCookieCollection(); } /// /// Initializes a new instance of the class. /// /// Details on the incoming HTTP request. internal HttpRequestInfo(HttpListenerRequest listenerRequest) { Requires.NotNull(listenerRequest, "listenerRequest"); this.httpMethod = listenerRequest.HttpMethod; this.requestUri = listenerRequest.Url; this.queryString = listenerRequest.QueryString; this.headers = listenerRequest.Headers; this.form = ParseFormData(listenerRequest.HttpMethod, listenerRequest.Headers, () => listenerRequest.InputStream); this.serverVariables = new NameValueCollection(); this.cookies = new HttpCookieCollection(); Reporting.RecordRequestStatistics(this); } /// /// Initializes a new instance of the class. /// /// The request. internal HttpRequestInfo(HttpRequestMessage request) { Requires.NotNull(request, "request"); this.httpMethod = request.Method.ToString(); this.requestUri = request.RequestUri; this.queryString = HttpUtility.ParseQueryString(request.RequestUri.Query); this.headers = new NameValueCollection(); AddHeaders(this.headers, request.Headers); AddHeaders(this.headers, request.Content.Headers); this.form = ParseFormData(this.httpMethod, this.headers, () => request.Content.ReadAsStreamAsync().Result); this.serverVariables = new NameValueCollection(); this.cookies = new HttpCookieCollection(); Reporting.RecordRequestStatistics(this); } /// /// Initializes a new instance of the class. /// /// The HTTP method. /// The request URI. /// The headers. /// The input stream. internal HttpRequestInfo(string httpMethod, Uri requestUri, NameValueCollection headers, Stream inputStream) { Requires.NotNullOrEmpty(httpMethod, "httpMethod"); Requires.NotNull(requestUri, "requestUri"); this.httpMethod = httpMethod; this.requestUri = requestUri; this.headers = headers; this.queryString = HttpUtility.ParseQueryString(requestUri.Query); this.form = ParseFormData(httpMethod, headers, () => inputStream); this.serverVariables = new NameValueCollection(); this.cookies = new HttpCookieCollection(); Reporting.RecordRequestStatistics(this); } /// /// Gets the HTTP method. /// public override string HttpMethod { get { return this.httpMethod; } } /// /// Gets the headers. /// public override NameValueCollection Headers { get { return this.headers; } } /// /// Gets the URL. /// public override Uri Url { get { return this.requestUri; } } /// /// Gets the raw URL. /// public override string RawUrl { get { return this.requestUri.AbsolutePath + this.requestUri.Query; } } /// /// Gets the form. /// public override NameValueCollection Form { get { return this.form; } } /// /// Gets the query string. /// public override NameValueCollection QueryString { get { return this.queryString; } } /// /// Gets the server variables. /// public override NameValueCollection ServerVariables { get { return this.serverVariables; } } /// /// Gets the collection of cookies that were sent by the client. /// /// The client's cookies. public override HttpCookieCollection Cookies { get { return this.cookies; } } /// /// When overridden in a derived class, gets an array of client-supported MIME accept types. /// /// An array of client-supported MIME accept types. public override string[] AcceptTypes { get { if (this.Headers["Accept"] != null) { return this.headers["Accept"].Split(','); } return new string[0]; } } /// /// When overridden in a derived class, gets information about the URL of the client request that linked to the current URL. /// /// The URL of the page that linked to the current request. public override Uri UrlReferrer { get { if (this.Headers["Referer"] != null) { // misspelled word intentional, per RFC return new Uri(this.Headers["Referer"]); } return null; } } /// /// When overridden in a derived class, gets the length, in bytes, of content that was sent by the client. /// /// The length, in bytes, of content that was sent by the client. public override int ContentLength { get { if (this.Headers["Content-Length"] != null) { return int.Parse(this.headers["Content-Length"]); } return 0; } } /// /// When overridden in a derived class, gets or sets the MIME content type of the request. /// /// The MIME content type of the request, such as "text/html". /// Setter always throws public override string ContentType { get { return this.Headers["Content-Type"]; } set { throw new NotImplementedException(); } } /// /// Creates an instance that describes the specified HTTP request. /// /// The request. /// The request URI. /// An instance of . public static HttpRequestBase Create(HttpRequestMessageProperty request, Uri requestUri) { return new HttpRequestInfo(request, requestUri); } /// /// Creates an instance that describes the specified HTTP request. /// /// The listener request. /// An instance of . public static HttpRequestBase Create(HttpListenerRequest listenerRequest) { return new HttpRequestInfo(listenerRequest); } #if CLR4 /// /// Creates an instance that describes the specified HTTP request. /// /// The HTTP request. /// An instance of . public static HttpRequestBase Create(HttpRequestMessage request) { return new HttpRequestInfo(request); } #endif /// /// Creates an instance that describes the specified HTTP request. /// /// The HTTP method. /// The request URI. /// The form variables. /// The HTTP headers. /// An instance of . public static HttpRequestBase Create(string httpMethod, Uri requestUri, NameValueCollection form = null, NameValueCollection headers = null) { return new HttpRequestInfo(httpMethod, requestUri, form, headers); } /// /// Creates an instance that describes the specified HTTP request. /// /// The HTTP method. /// The request URI. /// The headers. /// The input stream. /// An instance of . public static HttpRequestBase Create(string httpMethod, Uri requestUri, NameValueCollection headers, Stream inputStream) { return new HttpRequestInfo(httpMethod, requestUri, headers, inputStream); } /// /// Reads name=value pairs from the POSTed form entity when the HTTP headers indicate that that is the payload of the entity. /// /// The HTTP method. /// The headers. /// A function that returns the input stream. /// The non-null collection of form variables. private static NameValueCollection ParseFormData(string httpMethod, NameValueCollection headers, Func inputStreamFunc) { Requires.NotNullOrEmpty(httpMethod, "httpMethod"); Requires.NotNull(headers, "headers"); ContentType contentType = string.IsNullOrEmpty(headers[HttpRequestHeaders.ContentType]) ? null : new ContentType(headers[HttpRequestHeaders.ContentType]); if (httpMethod == "POST" && contentType != null && string.Equals(contentType.MediaType, Channel.HttpFormUrlEncoded, StringComparison.Ordinal) && inputStreamFunc != null) { var inputStream = inputStreamFunc(); var reader = new StreamReader(inputStream); long originalPosition = 0; if (inputStream.CanSeek) { originalPosition = inputStream.Position; } string postEntity = reader.ReadToEnd(); if (inputStream.CanSeek) { inputStream.Seek(originalPosition, SeekOrigin.Begin); } return HttpUtility.ParseQueryString(postEntity); } return new NameValueCollection(); } /// /// Adds HTTP headers to a . /// /// The collection to be modified with added entries. /// The collection to read from. private static void AddHeaders(NameValueCollection collectionToFill, HttpHeaders headers) { Requires.NotNull(collectionToFill, "collectionToFill"); Requires.NotNull(headers, "headers"); foreach (var header in headers) { foreach (var value in header.Value) { collectionToFill.Add(header.Key, value); } } } } }