Fix for issue #47, and refactored HttpListenerRequest.cs

This commit is contained in:
sta 2014-05-21 11:05:58 +09:00
parent 5bd88ee1a3
commit be1470a32e
2 changed files with 96 additions and 126 deletions

View File

@ -1337,7 +1337,7 @@ namespace WebSocketSharp
/// </param> /// </param>
public static bool IsPredefinedScheme (this string value) public static bool IsPredefinedScheme (this string value)
{ {
if (value == null && value.Length < 2) if (value == null || value.Length < 2)
return false; return false;
var c = value [0]; var c = value [0];
@ -1407,7 +1407,7 @@ namespace WebSocketSharp
/// Determines whether the specified <see cref="string"/> is a URI string. /// Determines whether the specified <see cref="string"/> is a URI string.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// <c>true</c> if <paramref name="value"/> is maybe a URI string; otherwise, <c>false</c>. /// <c>true</c> if <paramref name="value"/> may be a URI string; otherwise, <c>false</c>.
/// </returns> /// </returns>
/// <param name="value"> /// <param name="value">
/// A <see cref="string"/> to test. /// A <see cref="string"/> to test.
@ -1778,18 +1778,18 @@ namespace WebSocketSharp
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A <see cref="Uri"/> converted from <paramref name="uriString"/>, or <see langword="null"/> /// A <see cref="Uri"/> converted from <paramref name="uriString"/>, or <see langword="null"/>
/// if <paramref name="uriString"/> is <see langword="null"/> or empty. /// if <paramref name="uriString"/> isn't successfully converted.
/// </returns> /// </returns>
/// <param name="uriString"> /// <param name="uriString">
/// A <see cref="string"/> to convert. /// A <see cref="string"/> to convert.
/// </param> /// </param>
public static Uri ToUri (this string uriString) public static Uri ToUri (this string uriString)
{ {
return uriString == null || uriString.Length == 0 Uri res;
? null return Uri.TryCreate (
: uriString.MaybeUri () uriString, uriString.MaybeUri () ? UriKind.Absolute : UriKind.Relative, out res)
? new Uri (uriString) ? res
: new Uri (uriString, UriKind.Relative); : null;
} }
/// <summary> /// <summary>

View File

@ -33,22 +33,22 @@
#region Authors #region Authors
/* /*
* Authors: * Authors:
* Gonzalo Paniagua Javier <gonzalo@novell.com> * - Gonzalo Paniagua Javier <gonzalo@novell.com>
*/ */
#endregion #endregion
using System; using System;
using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Text; using System.Text;
namespace WebSocketSharp.Net namespace WebSocketSharp.Net
{ {
/// <summary> /// <summary>
/// Provides access to a request to the <see cref="HttpListener"/>. /// Provides the access to a request to the <see cref="HttpListener"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The HttpListenerRequest class cannot be inherited. /// The HttpListenerRequest class cannot be inherited.
@ -105,9 +105,8 @@ namespace WebSocketSharp.Net
/// Gets the media types which are acceptable for the response. /// Gets the media types which are acceptable for the response.
/// </summary> /// </summary>
/// <value> /// <value>
/// An array of <see cref="string"/> that contains the media type names in /// An array of <see cref="string"/> that contains the media type names in the Accept
/// the Accept request-header or <see langword="null"/> if the request didn't /// request-header or <see langword="null"/> if the request didn't include an Accept header.
/// include an Accept header.
/// </value> /// </value>
public string [] AcceptTypes { public string [] AcceptTypes {
get { get {
@ -116,8 +115,7 @@ namespace WebSocketSharp.Net
} }
/// <summary> /// <summary>
/// Gets an error code that identifies a problem with the client's /// Gets an error code that identifies a problem with the client's certificate.
/// certificate.
/// </summary> /// </summary>
/// <value> /// <value>
/// Always returns <c>0</c>. /// Always returns <c>0</c>.
@ -140,9 +138,9 @@ namespace WebSocketSharp.Net
/// Gets the encoding used with the entity body data included in the request. /// Gets the encoding used with the entity body data included in the request.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="Encoding"/> that represents the encoding used with the entity /// A <see cref="Encoding"/> that represents the encoding used with the entity body data or
/// body data or <see cref="Encoding.Default"/> if the request didn't include /// <see cref="Encoding.Default"/> if the request didn't include the information about the
/// the information about the encoding. /// encoding.
/// </value> /// </value>
public Encoding ContentEncoding { public Encoding ContentEncoding {
get { get {
@ -154,9 +152,8 @@ namespace WebSocketSharp.Net
/// Gets the size of the entity body data included in the request. /// Gets the size of the entity body data included in the request.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="long"/> that represents the value of the Content-Length /// A <see cref="long"/> that represents the value of the Content-Length entity-header. The
/// entity-header. The value is a number of bytes in the entity body data. /// value is a number of bytes in the entity body data. <c>-1</c> if the size isn't known.
/// <c>-1</c> if the size isn't known.
/// </value> /// </value>
public long ContentLength64 { public long ContentLength64 {
get { get {
@ -168,8 +165,7 @@ namespace WebSocketSharp.Net
/// Gets the media type of the entity body included in the request. /// Gets the media type of the entity body included in the request.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="string"/> that represents the value of the Content-Type /// A <see cref="string"/> that represents the value of the Content-Type entity-header.
/// entity-header.
/// </value> /// </value>
public string ContentType { public string ContentType {
get { get {
@ -181,8 +177,7 @@ namespace WebSocketSharp.Net
/// Gets the cookies included in the request. /// Gets the cookies included in the request.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="CookieCollection"/> that contains the cookies included in /// A <see cref="CookieCollection"/> that contains the cookies included in the request.
/// the request.
/// </value> /// </value>
public CookieCollection Cookies { public CookieCollection Cookies {
get { get {
@ -206,8 +201,7 @@ namespace WebSocketSharp.Net
/// Gets the HTTP headers used in the request. /// Gets the HTTP headers used in the request.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="NameValueCollection"/> that contains the HTTP headers used /// A <see cref="NameValueCollection"/> that contains the HTTP headers used in the request.
/// in the request.
/// </value> /// </value>
public NameValueCollection Headers { public NameValueCollection Headers {
get { get {
@ -228,26 +222,22 @@ namespace WebSocketSharp.Net
} }
/// <summary> /// <summary>
/// Gets a <see cref="Stream"/> that contains the entity body data included /// Gets a <see cref="Stream"/> that contains the entity body data included in the request.
/// in the request.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="Stream"/> that contains the entity body data included in the /// A <see cref="Stream"/> that contains the entity body data included in the request.
/// request.
/// </value> /// </value>
public Stream InputStream { public Stream InputStream {
get { get {
return _inputStream ?? return _inputStream ??
(_inputStream = HasEntityBody (_inputStream = HasEntityBody
? _context.Connection.GetRequestStream ( ? _context.Connection.GetRequestStream (_chunked, _contentLength)
_chunked, _contentLength)
: Stream.Null); : Stream.Null);
} }
} }
/// <summary> /// <summary>
/// Gets a value indicating whether the client that sent the request is /// Gets a value indicating whether the client that sent the request is authenticated.
/// authenticated.
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if the client is authenticated; otherwise, <c>false</c>. /// <c>true</c> if the client is authenticated; otherwise, <c>false</c>.
@ -260,12 +250,10 @@ namespace WebSocketSharp.Net
} }
/// <summary> /// <summary>
/// Gets a value indicating whether the request is sent from the local /// Gets a value indicating whether the request is sent from the local computer.
/// computer.
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if the request is sent from the local computer; otherwise, /// <c>true</c> if the request is sent from the local computer; otherwise, <c>false</c>.
/// <c>false</c>.
/// </value> /// </value>
public bool IsLocal { public bool IsLocal {
get { get {
@ -274,8 +262,7 @@ namespace WebSocketSharp.Net
} }
/// <summary> /// <summary>
/// Gets a value indicating whether the HTTP connection is secured using the /// Gets a value indicating whether the HTTP connection is secured using the SSL protocol.
/// SSL protocol.
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if the HTTP connection is secured; otherwise, <c>false</c>. /// <c>true</c> if the HTTP connection is secured; otherwise, <c>false</c>.
@ -287,12 +274,10 @@ namespace WebSocketSharp.Net
} }
/// <summary> /// <summary>
/// Gets a value indicating whether the request is a WebSocket connection /// Gets a value indicating whether the request is a WebSocket connection request.
/// request.
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if the request is a WebSocket connection request; otherwise, /// <c>true</c> if the request is a WebSocket connection request; otherwise, <c>false</c>.
/// <c>false</c>.
/// </value> /// </value>
public bool IsWebSocketRequest { public bool IsWebSocketRequest {
get { get {
@ -304,12 +289,10 @@ namespace WebSocketSharp.Net
} }
/// <summary> /// <summary>
/// Gets a value indicating whether the client requests a persistent /// Gets a value indicating whether the client requests a persistent connection.
/// connection.
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if the client requests a persistent connection; otherwise, /// <c>true</c> if the client requests a persistent connection; otherwise, <c>false</c>.
/// <c>false</c>.
/// </value> /// </value>
public bool KeepAlive { public bool KeepAlive {
get { get {
@ -344,8 +327,7 @@ namespace WebSocketSharp.Net
/// Gets the HTTP version used in the request. /// Gets the HTTP version used in the request.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="Version"/> that represents the HTTP version used in the /// A <see cref="Version"/> that represents the HTTP version used in the request.
/// request.
/// </value> /// </value>
public Version ProtocolVersion { public Version ProtocolVersion {
get { get {
@ -357,8 +339,8 @@ namespace WebSocketSharp.Net
/// Gets the collection of query string variables used in the request. /// Gets the collection of query string variables used in the request.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="NameValueCollection"/> that contains the collection of query /// A <see cref="NameValueCollection"/> that contains the collection of query string variables
/// string variables used in the request. /// used in the request.
/// </value> /// </value>
public NameValueCollection QueryString { public NameValueCollection QueryString {
get { get {
@ -367,12 +349,10 @@ namespace WebSocketSharp.Net
} }
/// <summary> /// <summary>
/// Gets the raw URL (without the scheme, host and port) requested by the /// Gets the raw URL (without the scheme, host, and port) requested by the client.
/// client.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="string"/> that represents the raw URL requested by the /// A <see cref="string"/> that represents the raw URL requested by the client.
/// client.
/// </value> /// </value>
public string RawUrl { public string RawUrl {
get { get {
@ -420,9 +400,8 @@ namespace WebSocketSharp.Net
/// Gets the URL of the resource from which the requested URL was obtained. /// Gets the URL of the resource from which the requested URL was obtained.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="Uri"/> that represents the value of the Referer /// A <see cref="Uri"/> that represents the value of the Referer request-header or
/// request-header or <see langword="null"/> if the request didn't include /// <see langword="null"/> if the request didn't include an Referer header.
/// an Referer header.
/// </value> /// </value>
public Uri UrlReferrer { public Uri UrlReferrer {
get { get {
@ -434,8 +413,7 @@ namespace WebSocketSharp.Net
/// Gets the information about the user agent originating the request. /// Gets the information about the user agent originating the request.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="string"/> that represents the value of the User-Agent /// A <see cref="string"/> that represents the value of the User-Agent request-header.
/// request-header.
/// </value> /// </value>
public string UserAgent { public string UserAgent {
get { get {
@ -456,12 +434,10 @@ namespace WebSocketSharp.Net
} }
/// <summary> /// <summary>
/// Gets the internet host name and port number (if present) specified by the /// Gets the internet host name and port number (if present) specified by the client.
/// client.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="string"/> that represents the value of the Host /// A <see cref="string"/> that represents the value of the Host request-header.
/// request-header.
/// </value> /// </value>
public string UserHostName { public string UserHostName {
get { get {
@ -473,9 +449,9 @@ namespace WebSocketSharp.Net
/// Gets the natural languages which are preferred for the response. /// Gets the natural languages which are preferred for the response.
/// </summary> /// </summary>
/// <value> /// <value>
/// An array of <see cref="string"/> that contains the natural language names /// An array of <see cref="string"/> that contains the natural language names in the
/// in the Accept-Language request-header or <see langword="null"/> if the /// Accept-Language request-header or <see langword="null"/> if the request didn't
/// request didn't include an Accept-Language header. /// include an Accept-Language header.
/// </value> /// </value>
public string [] UserLanguages { public string [] UserLanguages {
get { get {
@ -512,6 +488,18 @@ namespace WebSocketSharp.Net
} }
} }
private static bool tryCreateVersion (string version, out Version result)
{
result = null;
try {
result = new Version (version);
return true;
}
catch {
return false;
}
}
#endregion #endregion
#region Internal Methods #region Internal Methods
@ -526,11 +514,11 @@ namespace WebSocketSharp.Net
var name = header.Substring (0, colon).Trim (); var name = header.Substring (0, colon).Trim ();
var val = header.Substring (colon + 1).Trim (); var val = header.Substring (colon + 1).Trim ();
var lower = name.ToLower (CultureInfo.InvariantCulture);
_headers.SetInternally (name, val, false); _headers.SetInternally (name, val, false);
var lower = name.ToLower (CultureInfo.InvariantCulture);
if (lower == "accept") { if (lower == "accept") {
_acceptTypes = val.SplitHeaderValue (',').ToArray (); _acceptTypes = new List<string> (val.SplitHeaderValue (',')).ToArray ();
return; return;
} }
@ -591,13 +579,14 @@ namespace WebSocketSharp.Net
host = UserHostAddress; host = UserHostAddress;
string path; string path;
var rawUri = _rawUrl.ToUri (); Uri rawUri;
if (rawUri != null && rawUri.IsAbsoluteUri) { if (_rawUrl.MaybeUri () && Uri.TryCreate (_rawUrl, UriKind.Absolute, out rawUri)) {
host = rawUri.Host; host = rawUri.Host;
path = rawUri.PathAndQuery; path = rawUri.PathAndQuery;
} }
else else {
path = HttpUtility.UrlDecode (_rawUrl); path = HttpUtility.UrlDecode (_rawUrl);
}
var colon = host.IndexOf (':'); var colon = host.IndexOf (':');
if (colon != -1) if (colon != -1)
@ -619,9 +608,7 @@ namespace WebSocketSharp.Net
createQueryString (_url.Query); createQueryString (_url.Query);
var encoding = Headers ["Transfer-Encoding"]; var encoding = Headers ["Transfer-Encoding"];
if (_version >= HttpVersion.Version11 && if (_version >= HttpVersion.Version11 && encoding != null && encoding.Length > 0) {
encoding != null &&
encoding.Length > 0) {
_chunked = encoding.ToLower () == "chunked"; _chunked = encoding.ToLower () == "chunked";
if (!_chunked) { if (!_chunked) {
_context.ErrorMessage = String.Empty; _context.ErrorMessage = String.Empty;
@ -642,9 +629,7 @@ namespace WebSocketSharp.Net
} }
var expect = Headers ["Expect"]; var expect = Headers ["Expect"];
if (expect != null && if (expect != null && expect.Length > 0 && expect.ToLower () == "100-continue") {
expect.Length > 0 &&
expect.ToLower () == "100-continue") {
var output = _context.Connection.GetResponseStream (); var output = _context.Connection.GetResponseStream ();
output.WriteInternally (_100continue, 0, _100continue.Length); output.WriteInternally (_100continue, 0, _100continue.Length);
} }
@ -660,11 +645,11 @@ namespace WebSocketSharp.Net
if (_contentLength > 0) if (_contentLength > 0)
length = (int) Math.Min (_contentLength, (long) length); length = (int) Math.Min (_contentLength, (long) length);
var buffer = new byte [length]; var buff = new byte [length];
while (true) { while (true) {
// TODO: Test if MS has a timeout when doing this. // TODO: Test if MS has a timeout when doing this.
try { try {
var ares = InputStream.BeginRead (buffer, 0, length, null, null); var ares = InputStream.BeginRead (buff, 0, length, null, null);
if (!ares.IsCompleted && !ares.AsyncWaitHandle.WaitOne (100)) if (!ares.IsCompleted && !ares.AsyncWaitHandle.WaitOne (100))
return false; return false;
@ -679,7 +664,7 @@ namespace WebSocketSharp.Net
internal void SetRequestLine (string requestLine) internal void SetRequestLine (string requestLine)
{ {
var parts = requestLine.Split (new char [] { ' ' }, 3); var parts = requestLine.Split (new [] { ' ' }, 3);
if (parts.Length != 3) { if (parts.Length != 3) {
_context.ErrorMessage = "Invalid request line (parts)"; _context.ErrorMessage = "Invalid request line (parts)";
return; return;
@ -693,19 +678,11 @@ namespace WebSocketSharp.Net
_rawUrl = parts [1]; _rawUrl = parts [1];
if (parts [2].Length != 8 || !parts [2].StartsWith ("HTTP/")) { if (parts [2].Length != 8 ||
!parts [2].StartsWith ("HTTP/") ||
!tryCreateVersion (parts [2].Substring (5), out _version) ||
_version.Major < 1)
_context.ErrorMessage = "Invalid request line (version)"; _context.ErrorMessage = "Invalid request line (version)";
return;
}
try {
_version = new Version (parts [2].Substring (5));
if (_version.Major < 1)
throw new Exception ();
}
catch {
_context.ErrorMessage = "Invalid request line (version)";
}
} }
#endregion #endregion
@ -717,26 +694,24 @@ namespace WebSocketSharp.Net
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This asynchronous operation must be completed by calling the /// This asynchronous operation must be completed by calling the
/// <see cref="EndGetClientCertificate"/> method. Typically, that method is /// <see cref="EndGetClientCertificate"/> method. Typically, that method is invoked by the
/// invoked by the <paramref name="requestCallback"/> delegate. /// <paramref name="requestCallback"/> delegate.
/// </remarks> /// </remarks>
/// <returns> /// <returns>
/// An <see cref="IAsyncResult"/> that contains the status of the /// An <see cref="IAsyncResult"/> that contains the status of the asynchronous operation.
/// asynchronous operation.
/// </returns> /// </returns>
/// <param name="requestCallback"> /// <param name="requestCallback">
/// An <see cref="AsyncCallback"/> delegate that references the method(s) /// An <see cref="AsyncCallback"/> delegate that references the method(s) called when the
/// called when the asynchronous operation completes. /// asynchronous operation completes.
/// </param> /// </param>
/// <param name="state"> /// <param name="state">
/// An <see cref="object"/> that contains a user defined object to pass to /// An <see cref="object"/> that contains a user defined object to pass to the
/// the <paramref name="requestCallback"/> delegate. /// <paramref name="requestCallback"/> delegate.
/// </param> /// </param>
/// <exception cref="NotImplementedException"> /// <exception cref="NotImplementedException">
/// This method is not implemented. /// This method isn't implemented.
/// </exception> /// </exception>
public IAsyncResult BeginGetClientCertificate ( public IAsyncResult BeginGetClientCertificate (AsyncCallback requestCallback, object state)
AsyncCallback requestCallback, Object state)
{ {
// TODO: Not Implemented. // TODO: Not Implemented.
throw new NotImplementedException (); throw new NotImplementedException ();
@ -750,15 +725,14 @@ namespace WebSocketSharp.Net
/// <see cref="BeginGetClientCertificate"/> method. /// <see cref="BeginGetClientCertificate"/> method.
/// </remarks> /// </remarks>
/// <returns> /// <returns>
/// A <see cref="X509Certificate2"/> that contains the client's X.509 v.3 /// A <see cref="X509Certificate2"/> that contains the client's X.509 v.3 certificate.
/// certificate.
/// </returns> /// </returns>
/// <param name="asyncResult"> /// <param name="asyncResult">
/// An <see cref="IAsyncResult"/> obtained by calling the /// An <see cref="IAsyncResult"/> obtained by calling the
/// <see cref="BeginGetClientCertificate"/> method. /// <see cref="BeginGetClientCertificate"/> method.
/// </param> /// </param>
/// <exception cref="NotImplementedException"> /// <exception cref="NotImplementedException">
/// This method is not implemented. /// This method isn't implemented.
/// </exception> /// </exception>
public X509Certificate2 EndGetClientCertificate (IAsyncResult asyncResult) public X509Certificate2 EndGetClientCertificate (IAsyncResult asyncResult)
{ {
@ -770,11 +744,10 @@ namespace WebSocketSharp.Net
/// Gets the client's X.509 v.3 certificate. /// Gets the client's X.509 v.3 certificate.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A <see cref="X509Certificate2"/> that contains the client's X.509 v.3 /// A <see cref="X509Certificate2"/> that contains the client's X.509 v.3 certificate.
/// certificate.
/// </returns> /// </returns>
/// <exception cref="NotImplementedException"> /// <exception cref="NotImplementedException">
/// This method is not implemented. /// This method isn't implemented.
/// </exception> /// </exception>
public X509Certificate2 GetClientCertificate () public X509Certificate2 GetClientCertificate ()
{ {
@ -787,18 +760,15 @@ namespace WebSocketSharp.Net
/// <see cref="HttpListenerRequest"/>. /// <see cref="HttpListenerRequest"/>.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A <see cref="string"/> that represents the current /// A <see cref="string"/> that represents the current <see cref="HttpListenerRequest"/>.
/// <see cref="HttpListenerRequest"/>.
/// </returns> /// </returns>
public override string ToString () public override string ToString ()
{ {
var buffer = new StringBuilder (64); var buff = new StringBuilder (64);
buffer.AppendFormat ("{0} {1} HTTP/{2}\r\n", _method, _rawUrl, _version); buff.AppendFormat ("{0} {1} HTTP/{2}\r\n", _method, _rawUrl, _version);
foreach (var key in _headers.AllKeys) buff.Append (_headers.ToString ());
buffer.AppendFormat ("{0}: {1}\r\n", key, _headers [key]);
buffer.Append ("\r\n"); return buff.ToString ();
return buffer.ToString ();
} }
#endregion #endregion