Modified opening and closing handshake

This commit is contained in:
sta 2013-08-06 21:31:21 +09:00
parent 7deddda2f9
commit 69c9be3eb5
9 changed files with 514 additions and 502 deletions

View File

@ -56,7 +56,7 @@ namespace WebSocketSharp
{ {
_code = getCodeFrom (data); _code = getCodeFrom (data);
_reason = getReasonFrom (data); _reason = getReasonFrom (data);
_clean = true; _clean = false;
} }
#endregion #endregion

View File

@ -225,6 +225,11 @@ namespace WebSocketSharp {
: stream.ToByteArray(); : stream.ToByteArray();
} }
internal static bool Equals (this string value, CompressionMethod method)
{
return value == method.ToCompressionExtension ();
}
// <summary> // <summary>
// Determines whether the specified <see cref="int"/> equals the specified <see cref="char"/>, // Determines whether the specified <see cref="int"/> equals the specified <see cref="char"/>,
// and invokes the specified Action&lt;int&gt; delegate at the same time. // and invokes the specified Action&lt;int&gt; delegate at the same time.
@ -289,6 +294,11 @@ namespace WebSocketSharp {
return new TcpListenerWebSocketContext(client, secure, cert); return new TcpListenerWebSocketContext(client, secure, cert);
} }
internal static bool IsCompressionExtension (this string value)
{
return value.StartsWith ("permessage-");
}
// <summary> // <summary>
// Determines whether the specified object is <see langword="null"/>. // Determines whether the specified object is <see langword="null"/>.
// </summary> // </summary>
@ -514,6 +524,22 @@ namespace WebSocketSharp {
} }
} }
internal static string ToCompressionExtension (this CompressionMethod method)
{
return method != CompressionMethod.NONE
? String.Format ("permessage-{0}", method.ToString ().ToLower ())
: String.Empty;
}
internal static CompressionMethod ToCompressionMethod (this string value)
{
foreach (CompressionMethod method in Enum.GetValues (typeof (CompressionMethod)))
if (value.Equals (method))
return method;
return CompressionMethod.NONE;
}
internal static string Unquote(this string value) internal static string Unquote(this string value)
{ {
var start = value.IndexOf('\"'); var start = value.IndexOf('\"');

View File

@ -78,19 +78,10 @@ namespace WebSocketSharp
public bool IsWebSocketRequest { public bool IsWebSocketRequest {
get { get {
return HttpMethod != "GET" return HttpMethod == "GET" &&
? false ProtocolVersion >= HttpVersion.Version11 &&
: ProtocolVersion < HttpVersion.Version11 Headers.Contains ("Upgrade", "websocket") &&
? false Headers.Contains ("Connection", "Upgrade");
: !ContainsHeader ("Upgrade", "websocket")
? false
: !ContainsHeader ("Connection", "Upgrade")
? false
: !ContainsHeader ("Host")
? false
: !ContainsHeader ("Sec-WebSocket-Key")
? false
: ContainsHeader ("Sec-WebSocket-Version");
} }
} }
@ -158,16 +149,6 @@ namespace WebSocketSharp
}; };
} }
public static HandshakeRequest Parse (WebSocketContext context)
{
return new HandshakeRequest {
Headers = context.Headers,
HttpMethod = "GET",
RequestUri = context.RequestUri,
ProtocolVersion = HttpVersion.Version11
};
}
public void SetCookies (CookieCollection cookies) public void SetCookies (CookieCollection cookies)
{ {
if (cookies == null || cookies.Count == 0) if (cookies == null || cookies.Count == 0)

View File

@ -57,8 +57,9 @@ namespace WebSocketSharp
public AuthenticationChallenge AuthChallenge { public AuthenticationChallenge AuthChallenge {
get { get {
return ContainsHeader ("WWW-Authenticate") var challenge = Headers ["WWW-Authenticate"];
? AuthenticationChallenge.Parse (Headers ["WWW-Authenticate"]) return !challenge.IsNullOrEmpty ()
? AuthenticationChallenge.Parse (challenge)
: null; : null;
} }
} }
@ -77,15 +78,11 @@ namespace WebSocketSharp
public bool IsWebSocketResponse { public bool IsWebSocketResponse {
get { get {
return ProtocolVersion < HttpVersion.Version11 return ProtocolVersion >= HttpVersion.Version11 &&
? false StatusCode == "101" &&
: StatusCode != "101" Headers.Contains ("Upgrade", "websocket") &&
? false Headers.Contains ("Connection", "Upgrade") &&
: !ContainsHeader ("Upgrade", "websocket") !Headers ["Sec-WebSocket-Accept"].IsNullOrEmpty ();
? false
: !ContainsHeader ("Connection", "Upgrade")
? false
: ContainsHeader ("Sec-WebSocket-Accept");
} }
} }

View File

@ -7,7 +7,7 @@
// Gonzalo Paniagua Javier (gonzalo@novell.com) // Gonzalo Paniagua Javier (gonzalo@novell.com)
// //
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com) // Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
// Copyright (c) 2012-2013 sta.blockhead (sta.blockhead@gmail.com) // Copyright (c) 2012-2013 sta.blockhead
// //
// Permission is hereby granted, free of charge, to any person obtaining // Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the // a copy of this software and associated documentation files (the
@ -40,16 +40,16 @@ using System.Net;
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 a <see cref="HttpListener"/> instance. /// Provides access to a request to a <see cref="HttpListener"/> instance.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The HttpListenerRequest class cannot be inherited. /// The HttpListenerRequest class cannot be inherited.
/// </remarks> /// </remarks>
public sealed class HttpListenerRequest { public sealed class HttpListenerRequest
{
#region Private Static Fields #region Private Static Fields
private static byte [] _100continue = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n"); private static byte [] _100continue = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n");
@ -282,19 +282,10 @@ namespace WebSocketSharp.Net {
/// </value> /// </value>
public bool IsWebSocketRequest { public bool IsWebSocketRequest {
get { get {
return _method != "GET" return _method == "GET" &&
? false _version >= HttpVersion.Version11 &&
: _version < HttpVersion.Version11 _headers.Contains ("Upgrade", "websocket") &&
? false _headers.Contains ("Connection", "Upgrade");
: !_headers.Contains("Upgrade", "websocket")
? false
: !_headers.Contains("Connection", "Upgrade")
? false
: !_headers.Contains("Host")
? false
: !_headers.Contains("Sec-WebSocket-Key")
? false
: _headers.Contains("Sec-WebSocket-Version");
} }
} }
@ -744,6 +735,23 @@ namespace WebSocketSharp.Net {
throw new NotImplementedException (); throw new NotImplementedException ();
} }
/// <summary>
/// Returns a <see cref="string"/> that represents the current <see cref="HttpListenerRequest"/>.
/// </summary>
/// <returns>
/// A <see cref="string"/> that represents the current <see cref="HttpListenerRequest"/>.
/// </returns>
public override string ToString ()
{
var buffer = new StringBuilder (64);
buffer.AppendFormat ("{0} {1} HTTP/{2}\r\n", _method, _rawUrl, _version);
foreach (string key in _headers.AllKeys)
buffer.AppendFormat ("{0}: {1}\r\n", key, _headers [key]);
buffer.Append ("\r\n");
return buffer.ToString ();
}
#endregion #endregion
} }
} }

View File

@ -31,10 +31,10 @@ using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Security.Principal; using System.Security.Principal;
namespace WebSocketSharp.Net.WebSockets { namespace WebSocketSharp.Net.WebSockets
{
/// <summary> /// <summary>
/// Provides access to the WebSocket connection request objects received by the <see cref="HttpListener"/> class. /// Provides access to the WebSocket connection request objects received by the <see cref="HttpListener"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// </remarks> /// </remarks>
@ -44,7 +44,7 @@ namespace WebSocketSharp.Net.WebSockets {
private HttpListenerContext _context; private HttpListenerContext _context;
private WebSocket _websocket; private WebSocket _websocket;
private WsStream _wsStream; private WsStream _stream;
#endregion #endregion
@ -53,7 +53,7 @@ namespace WebSocketSharp.Net.WebSockets {
internal HttpListenerWebSocketContext (HttpListenerContext context) internal HttpListenerWebSocketContext (HttpListenerContext context)
{ {
_context = context; _context = context;
_wsStream = WsStream.CreateServerStream(context); _stream = WsStream.CreateServerStream (context);
_websocket = new WebSocket (this); _websocket = new WebSocket (this);
} }
@ -63,7 +63,7 @@ namespace WebSocketSharp.Net.WebSockets {
internal WsStream Stream { internal WsStream Stream {
get { get {
return _wsStream; return _stream;
} }
} }
@ -95,6 +95,18 @@ namespace WebSocketSharp.Net.WebSockets {
} }
} }
/// <summary>
/// Gets the value of the Host header field used in the WebSocket opening handshake.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the value of the Host header field.
/// </value>
public override string Host {
get {
return _context.Request.Headers ["Host"];
}
}
/// <summary> /// <summary>
/// Gets a value indicating whether the client is authenticated. /// Gets a value indicating whether the client is authenticated.
/// </summary> /// </summary>
@ -132,18 +144,14 @@ namespace WebSocketSharp.Net.WebSockets {
} }
/// <summary> /// <summary>
/// Gets a value indicating whether the WebSocket connection request is valid. /// Gets a value indicating whether the request is a WebSocket connection request.
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if the WebSocket connection request is valid; otherwise, <c>false</c>. /// <c>true</c> if the request is a WebSocket connection request; otherwise, <c>false</c>.
/// </value> /// </value>
public override bool IsValid { public override bool IsWebSocketRequest {
get { get {
return !_context.Request.IsWebSocketRequest return _context.Request.IsWebSocketRequest;
? false
: SecWebSocketKey.IsNullOrEmpty()
? false
: !SecWebSocketVersion.IsNullOrEmpty();
} }
} }
@ -155,7 +163,7 @@ namespace WebSocketSharp.Net.WebSockets {
/// </value> /// </value>
public override string Origin { public override string Origin {
get { get {
return Headers["Origin"]; return _context.Request.Headers ["Origin"];
} }
} }
@ -206,7 +214,7 @@ namespace WebSocketSharp.Net.WebSockets {
/// </value> /// </value>
public override string SecWebSocketKey { public override string SecWebSocketKey {
get { get {
return Headers["Sec-WebSocket-Key"]; return _context.Request.Headers ["Sec-WebSocket-Key"];
} }
} }
@ -221,7 +229,7 @@ namespace WebSocketSharp.Net.WebSockets {
/// </value> /// </value>
public override IEnumerable<string> SecWebSocketProtocols { public override IEnumerable<string> SecWebSocketProtocols {
get { get {
return Headers.GetValues("Sec-WebSocket-Protocol"); return _context.Request.Headers.GetValues ("Sec-WebSocket-Protocol");
} }
} }
@ -236,7 +244,7 @@ namespace WebSocketSharp.Net.WebSockets {
/// </value> /// </value>
public override string SecWebSocketVersion { public override string SecWebSocketVersion {
get { get {
return Headers["Sec-WebSocket-Version"]; return _context.Request.Headers ["Sec-WebSocket-Version"];
} }
} }
@ -246,7 +254,7 @@ namespace WebSocketSharp.Net.WebSockets {
/// <value> /// <value>
/// A <see cref="System.Net.IPEndPoint"/> that contains the server endpoint. /// A <see cref="System.Net.IPEndPoint"/> that contains the server endpoint.
/// </value> /// </value>
public virtual System.Net.IPEndPoint ServerEndPoint { public override System.Net.IPEndPoint ServerEndPoint {
get { get {
return _context.Connection.LocalEndPoint; return _context.Connection.LocalEndPoint;
} }
@ -270,7 +278,7 @@ namespace WebSocketSharp.Net.WebSockets {
/// <value> /// <value>
/// A <see cref="System.Net.IPEndPoint"/> that contains the client endpoint. /// A <see cref="System.Net.IPEndPoint"/> that contains the client endpoint.
/// </value> /// </value>
public virtual System.Net.IPEndPoint UserEndPoint { public override System.Net.IPEndPoint UserEndPoint {
get { get {
return _context.Connection.RemoteEndPoint; return _context.Connection.RemoteEndPoint;
} }
@ -298,5 +306,20 @@ namespace WebSocketSharp.Net.WebSockets {
} }
#endregion #endregion
#region Public Methods
/// <summary>
/// Returns a <see cref="string"/> that represents the current <see cref="HttpListenerWebSocketContext"/>.
/// </summary>
/// <returns>
/// A <see cref="string"/> that represents the current <see cref="HttpListenerWebSocketContext"/>.
/// </returns>
public override string ToString ()
{
return _context.Request.ToString ();
}
#endregion
} }
} }

View File

@ -105,6 +105,18 @@ namespace WebSocketSharp.Net.WebSockets
} }
} }
/// <summary>
/// Gets the value of the Host header field used in the WebSocket opening handshake.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the value of the Host header field.
/// </value>
public override string Host {
get {
return _request.Headers ["Host"];
}
}
/// <summary> /// <summary>
/// Gets a value indicating whether the client is authenticated. /// Gets a value indicating whether the client is authenticated.
/// </summary> /// </summary>
@ -145,18 +157,14 @@ namespace WebSocketSharp.Net.WebSockets
} }
/// <summary> /// <summary>
/// Gets a value indicating whether the WebSocket connection request is valid. /// Gets a value indicating whether the request is a WebSocket connection request.
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if the WebSocket connection request is valid; otherwise, <c>false</c>. /// <c>true</c> if the request is a WebSocket connection request; otherwise, <c>false</c>.
/// </value> /// </value>
public override bool IsValid { public override bool IsWebSocketRequest {
get { get {
return !_request.IsWebSocketRequest return _request.IsWebSocketRequest;
? false
: SecWebSocketKey.IsNullOrEmpty ()
? false
: !SecWebSocketVersion.IsNullOrEmpty ();
} }
} }
@ -168,7 +176,7 @@ namespace WebSocketSharp.Net.WebSockets
/// </value> /// </value>
public override string Origin { public override string Origin {
get { get {
return Headers ["Origin"]; return _request.Headers ["Origin"];
} }
} }
@ -219,7 +227,7 @@ namespace WebSocketSharp.Net.WebSockets
/// </value> /// </value>
public override string SecWebSocketKey { public override string SecWebSocketKey {
get { get {
return Headers ["Sec-WebSocket-Key"]; return _request.Headers ["Sec-WebSocket-Key"];
} }
} }
@ -227,14 +235,14 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets the values of the Sec-WebSocket-Protocol header field used in the WebSocket opening handshake. /// Gets the values of the Sec-WebSocket-Protocol header field used in the WebSocket opening handshake.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The SecWebSocketProtocols property indicates the subprotocols of the WebSocket connection. /// This property indicates the subprotocols of the WebSocket connection.
/// </remarks> /// </remarks>
/// <value> /// <value>
/// An IEnumerable&lt;string&gt; that contains the values of the Sec-WebSocket-Protocol header field. /// An IEnumerable&lt;string&gt; that contains the values of the Sec-WebSocket-Protocol header field.
/// </value> /// </value>
public override IEnumerable<string> SecWebSocketProtocols { public override IEnumerable<string> SecWebSocketProtocols {
get { get {
return Headers.GetValues ("Sec-WebSocket-Protocol"); return _request.Headers.GetValues ("Sec-WebSocket-Protocol");
} }
} }
@ -249,7 +257,7 @@ namespace WebSocketSharp.Net.WebSockets
/// </value> /// </value>
public override string SecWebSocketVersion { public override string SecWebSocketVersion {
get { get {
return Headers ["Sec-WebSocket-Version"]; return _request.Headers ["Sec-WebSocket-Version"];
} }
} }
@ -259,7 +267,7 @@ namespace WebSocketSharp.Net.WebSockets
/// <value> /// <value>
/// A <see cref="System.Net.IPEndPoint"/> that contains the server endpoint. /// A <see cref="System.Net.IPEndPoint"/> that contains the server endpoint.
/// </value> /// </value>
public virtual System.Net.IPEndPoint ServerEndPoint { public override System.Net.IPEndPoint ServerEndPoint {
get { get {
return (System.Net.IPEndPoint) _client.Client.LocalEndPoint; return (System.Net.IPEndPoint) _client.Client.LocalEndPoint;
} }
@ -286,7 +294,7 @@ namespace WebSocketSharp.Net.WebSockets
/// <value> /// <value>
/// A <see cref="System.Net.IPEndPoint"/> that contains the client endpoint. /// A <see cref="System.Net.IPEndPoint"/> that contains the client endpoint.
/// </value> /// </value>
public virtual System.Net.IPEndPoint UserEndPoint { public override System.Net.IPEndPoint UserEndPoint {
get { get {
return (System.Net.IPEndPoint) _client.Client.RemoteEndPoint; return (System.Net.IPEndPoint) _client.Client.RemoteEndPoint;
} }
@ -315,5 +323,20 @@ namespace WebSocketSharp.Net.WebSockets
} }
#endregion #endregion
#region Public Methods
/// <summary>
/// Returns a <see cref="string"/> that represents the current <see cref="TcpListenerWebSocketContext"/>.
/// </summary>
/// <returns>
/// A <see cref="string"/> that represents the current <see cref="TcpListenerWebSocketContext"/>.
/// </returns>
public override string ToString ()
{
return _request.ToString ();
}
#endregion
} }
} }

View File

@ -31,16 +31,16 @@ using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Security.Principal; using System.Security.Principal;
namespace WebSocketSharp.Net.WebSockets { namespace WebSocketSharp.Net.WebSockets
{
/// <summary> /// <summary>
/// Provides access to the WebSocket connection request objects. /// Provides access to the WebSocket connection request objects.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The WebSocketContext class is an abstract class. /// The WebSocketContext class is an abstract class.
/// </remarks> /// </remarks>
public abstract class WebSocketContext { public abstract class WebSocketContext
{
#region Protected Constructors #region Protected Constructors
/// <summary> /// <summary>
@ -70,6 +70,14 @@ namespace WebSocketSharp.Net.WebSockets {
/// </value> /// </value>
public abstract NameValueCollection Headers { get; } public abstract NameValueCollection Headers { get; }
/// <summary>
/// Gets the value of the Host header field used in the WebSocket opening handshake.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the value of the Host header field.
/// </value>
public abstract string Host { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether the client is authenticated. /// Gets a value indicating whether the client is authenticated.
/// </summary> /// </summary>
@ -95,12 +103,12 @@ namespace WebSocketSharp.Net.WebSockets {
public abstract bool IsSecureConnection { get; } public abstract bool IsSecureConnection { get; }
/// <summary> /// <summary>
/// Gets a value indicating whether the WebSocket connection request is valid. /// Gets a value indicating whether the request is a WebSocket connection request.
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if the WebSocket connection request is valid; otherwise, <c>false</c>. /// <c>true</c> if the request is a WebSocket connection request; otherwise, <c>false</c>.
/// </value> /// </value>
public abstract bool IsValid { get; } public abstract bool IsWebSocketRequest { get; }
/// <summary> /// <summary>
/// Gets the value of the Origin header field used in the WebSocket opening handshake. /// Gets the value of the Origin header field used in the WebSocket opening handshake.
@ -167,6 +175,14 @@ namespace WebSocketSharp.Net.WebSockets {
/// </value> /// </value>
public abstract string SecWebSocketVersion { get; } public abstract string SecWebSocketVersion { get; }
/// <summary>
/// Gets the server endpoint as an IP address and a port number.
/// </summary>
/// <value>
/// A <see cref="System.Net.IPEndPoint"/> that contains the server endpoint.
/// </value>
public abstract System.Net.IPEndPoint ServerEndPoint { get; }
/// <summary> /// <summary>
/// Gets the client information (identity, authentication information and security roles). /// Gets the client information (identity, authentication information and security roles).
/// </summary> /// </summary>
@ -175,6 +191,14 @@ namespace WebSocketSharp.Net.WebSockets {
/// </value> /// </value>
public abstract IPrincipal User { get; } public abstract IPrincipal User { get; }
/// <summary>
/// Gets the client endpoint as an IP address and a port number.
/// </summary>
/// <value>
/// A <see cref="System.Net.IPEndPoint"/> that contains the client endpoint.
/// </value>
public abstract System.Net.IPEndPoint UserEndPoint { get; }
/// <summary> /// <summary>
/// Gets the WebSocket instance used for two-way communication between client and server. /// Gets the WebSocket instance used for two-way communication between client and server.
/// </summary> /// </summary>

View File

@ -45,8 +45,8 @@ using System.Threading;
using WebSocketSharp.Net; using WebSocketSharp.Net;
using WebSocketSharp.Net.WebSockets; using WebSocketSharp.Net.WebSockets;
namespace WebSocketSharp { namespace WebSocketSharp
{
/// <summary> /// <summary>
/// Implements the WebSocket interface. /// Implements the WebSocket interface.
/// </summary> /// </summary>
@ -499,9 +499,29 @@ namespace WebSocketSharp {
// As server // As server
private bool acceptHandshake () private bool acceptHandshake ()
{ {
return processHandshakeRequest () _logger.Debug (String.Format ("A WebSocket connection request from {0}:\n{1}",
? send (createHandshakeResponse ()) _context.UserEndPoint, _context));
: false;
if (!validateConnectionRequest (_context))
{
var msg = "Invalid WebSocket connection request.";
_logger.Error (msg);
error (msg);
Close (HttpStatusCode.BadRequest);
return false;
}
_base64key = _context.SecWebSocketKey;
if (_protocol.Length > 0 && !_context.Headers.Contains ("Sec-WebSocket-Protocol", _protocol))
_protocol = String.Empty;
var extensions = _context.Headers ["Sec-WebSocket-Extensions"];
if (extensions != null && extensions.Length > 0)
processRequestedExtensions (extensions);
return send (createHandshakeResponse ());
} }
private void close (CloseEventArgs eventArgs) private void close (CloseEventArgs eventArgs)
@ -532,7 +552,10 @@ namespace WebSocketSharp {
if (state == WsState.CONNECTING) if (state == WsState.CONNECTING)
{ {
if (!_client) if (!_client)
args.WasClean = send (createHandshakeResponse (HttpStatusCode.BadRequest)); {
close (HttpStatusCode.BadRequest);
return;
}
} }
else else
{ {
@ -545,6 +568,14 @@ namespace WebSocketSharp {
_logger.Trace ("Exit close method."); _logger.Trace ("Exit close method.");
} }
// As server
private void close (HttpStatusCode code)
{
send (createHandshakeResponse (code));
closeResources ();
_readyState = WsState.CLOSED;
}
private void close (ushort code, string reason) private void close (ushort code, string reason)
{ {
var data = code.Append (reason); var data = code.Append (reason);
@ -672,11 +703,6 @@ namespace WebSocketSharp {
return Convert.ToBase64String (src); return Convert.ToBase64String (src);
} }
private static string createCompressionExtension(CompressionMethod method)
{
return createCurrentCompressionExtension(method);
}
private static WsFrame createControlFrame (Opcode opcode, PayloadData payloadData, bool client) private static WsFrame createControlFrame (Opcode opcode, PayloadData payloadData, bool client)
{ {
var mask = client ? Mask.MASK : Mask.UNMASK; var mask = client ? Mask.MASK : Mask.UNMASK;
@ -685,20 +711,6 @@ namespace WebSocketSharp {
return frame; return frame;
} }
private static string createCurrentCompressionExtension(CompressionMethod method)
{
return method != CompressionMethod.NONE
? String.Format("permessage-{0}", method.ToString().ToLower())
: String.Empty;
}
private static string createDeprecatedCompressionExtension(CompressionMethod method)
{
return method != CompressionMethod.NONE
? String.Format("permessage-compress; method={0}", method.ToString().ToLower())
: String.Empty;
}
private static WsFrame createFrame ( private static WsFrame createFrame (
Fin fin, Opcode opcode, PayloadData payloadData, bool compressed, bool client) Fin fin, Opcode opcode, PayloadData payloadData, bool compressed, bool client)
{ {
@ -747,6 +759,10 @@ namespace WebSocketSharp {
{ {
var res = new HandshakeResponse (); var res = new HandshakeResponse ();
res.AddHeader ("Sec-WebSocket-Accept", createResponseKey ()); res.AddHeader ("Sec-WebSocket-Accept", createResponseKey ());
if (_protocol.Length > 0)
res.AddHeader ("Sec-WebSocket-Protocol", _protocol);
if (_extensions.Length > 0) if (_extensions.Length > 0)
res.AddHeader ("Sec-WebSocket-Extensions", _extensions); res.AddHeader ("Sec-WebSocket-Extensions", _extensions);
@ -769,9 +785,8 @@ namespace WebSocketSharp {
private string createRequestExtensions () private string createRequestExtensions ()
{ {
var extensions = new StringBuilder (64); var extensions = new StringBuilder (64);
var comp = createCompressionExtension (_compression); if (_compression != CompressionMethod.NONE)
if (comp.Length > 0) extensions.Append (_compression.ToCompressionExtension ());
extensions.Append (comp);
return extensions.Length > 0 return extensions.Length > 0
? extensions.ToString () ? extensions.ToString ()
@ -792,7 +807,33 @@ namespace WebSocketSharp {
private bool doHandshake () private bool doHandshake ()
{ {
setClientStream (); setClientStream ();
return processHandshakeResponse (sendHandshakeRequest ()); var res = sendHandshakeRequest ();
var msg = res.IsUnauthorized
? String.Format ("An HTTP {0} authorization is required.", res.AuthChallenge.Scheme)
: !validateConnectionResponse (res)
? "Invalid response to this WebSocket connection request."
: String.Empty;
if (msg.Length > 0)
{
_logger.Error (msg);
error (msg);
Close (CloseStatusCode.ABNORMAL, msg);
return false;
}
var protocol = res.Headers ["Sec-WebSocket-Protocol"];
if (protocol != null && protocol.Length > 0)
_protocol = protocol;
processRespondedExtensions (res.Headers ["Sec-WebSocket-Extensions"]);
var cookies = res.Cookies;
if (cookies.Count > 0)
_cookies.SetOrRemove (cookies);
return true;
} }
private void error (string message) private void error (string message)
@ -800,19 +841,6 @@ namespace WebSocketSharp {
OnError.Emit (this, new ErrorEventArgs (message)); OnError.Emit (this, new ErrorEventArgs (message));
} }
private static CompressionMethod getCompressionMethod(string value)
{
var deprecated = createDeprecatedCompressionExtension(CompressionMethod.DEFLATE);
if (value.Equals(deprecated))
return CompressionMethod.DEFLATE;
foreach (CompressionMethod method in Enum.GetValues(typeof(CompressionMethod)))
if (isCompressionExtension(value, method))
return method;
return CompressionMethod.NONE;
}
// As server // As server
private void init (WebSocketContext context) private void init (WebSocketContext context)
{ {
@ -822,60 +850,6 @@ namespace WebSocketSharp {
_client = false; _client = false;
} }
private static bool isCompressionExtension(string value)
{
return value.StartsWith("permessage-");
}
private static bool isCompressionExtension(string value, CompressionMethod method)
{
var expected = createCompressionExtension(method);
return expected.Length > 0
? value.Equals(expected)
: false;
}
// As server
private bool isValidHostHeader()
{
var authority = _context.Headers["Host"];
if (authority.IsNullOrEmpty() || !_uri.IsAbsoluteUri)
return true;
var i = authority.IndexOf(':');
var host = i > 0
? authority.Substring(0, i)
: authority;
var type = Uri.CheckHostName(host);
return type != UriHostNameType.Dns
? true
: Uri.CheckHostName(_uri.DnsSafeHost) != UriHostNameType.Dns
? true
: host == _uri.DnsSafeHost;
}
// As server
private bool isValidRequesHandshake()
{
return !_context.IsValid
? false
: !isValidHostHeader()
? false
: _context.Headers.Contains("Sec-WebSocket-Version", _version);
}
// As client
private bool isValidHandshakeResponse (HandshakeResponse response)
{
return !response.IsWebSocketResponse
? false
: !response.ContainsHeader ("Sec-WebSocket-Accept", createResponseKey ())
? false
: !response.ContainsHeader ("Sec-WebSocket-Version") ||
response.ContainsHeader ("Sec-WebSocket-Version", _version);
}
private void open () private void open ()
{ {
_readyState = WsState.OPEN; _readyState = WsState.OPEN;
@ -1025,25 +999,22 @@ namespace WebSocketSharp {
} }
// As server // As server
private void processRequestExtensions(string extensions) private void processRequestedExtensions (string extensions)
{ {
if (extensions.IsNullOrEmpty())
return;
var comp = false; var comp = false;
var buffer = new List<string> (); var buffer = new List<string> ();
foreach (var extension in extensions.SplitHeaderValue(',')) foreach (var e in extensions.SplitHeaderValue (','))
{ {
var e = extension.Trim(); var extension = e.Trim ();
var tmp = e.RemovePrefix("x-webkit-"); var tmp = extension.RemovePrefix ("x-webkit-");
if (!comp && isCompressionExtension(tmp)) if (!comp && tmp.IsCompressionExtension ())
{ {
var method = getCompressionMethod(tmp); var method = tmp.ToCompressionMethod ();
if (method != CompressionMethod.NONE) if (method != CompressionMethod.NONE)
{ {
_compression = method; _compression = method;
comp = true; comp = true;
buffer.Add(e); buffer.Add (extension);
} }
} }
} }
@ -1052,102 +1023,32 @@ namespace WebSocketSharp {
_extensions = buffer.ToArray ().ToString (", "); _extensions = buffer.ToArray ().ToString (", ");
} }
// As server
private bool processHandshakeRequest ()
{
var req = HandshakeRequest.Parse (_context);
_logger.Debug ("A handshake request from a client:\n" + req.ToString ());
if (!isValidRequesHandshake ())
{
var msg = "Invalid WebSocket connection request.";
_logger.Error (msg);
error (msg);
Close (HttpStatusCode.BadRequest);
return false;
}
_base64key = _context.SecWebSocketKey;
var protocols = _context.Headers ["Sec-WebSocket-Protocol"];
if (!protocols.IsNullOrEmpty ())
_protocols = protocols;
processRequestExtensions (_context.Headers ["Sec-WebSocket-Extensions"]);
return true;
}
// As client // As client
private void processResponseCookies(CookieCollection cookies) private void processRespondedExtensions (string extensions)
{ {
if (cookies.Count > 0) var comp = _compression != CompressionMethod.NONE ? true : false;
_cookies.SetOrRemove(cookies); var hasComp = false;
} if (extensions != null && extensions.Length > 0)
// As client
private void processResponseExtensions(string extensions)
{ {
var checkComp = _compression != CompressionMethod.NONE foreach (var e in extensions.SplitHeaderValue (','))
? true
: false;
var comp = false;
if (!extensions.IsNullOrEmpty())
{ {
foreach (var extension in extensions.SplitHeaderValue(',')) var extension = e.Trim ();
{ if (comp && !hasComp && extension.Equals (_compression))
var e = extension.Trim(); hasComp = true;
if (checkComp &&
!comp &&
isCompressionExtension(e, _compression))
comp = true;
} }
_extensions = extensions; _extensions = extensions;
} }
if (checkComp && !comp) if (comp && !hasComp)
_compression = CompressionMethod.NONE; _compression = CompressionMethod.NONE;
} }
// As client
private bool processHandshakeResponse (HandshakeResponse response)
{
var msg = response.IsUnauthorized
? String.Format ("An HTTP {0} authorization is required.", response.AuthChallenge.Scheme)
: !isValidHandshakeResponse (response)
? "Invalid response to this WebSocket connection request."
: String.Empty;
if (msg.Length > 0)
{
_logger.Error (msg);
error (msg);
Close (CloseStatusCode.ABNORMAL, msg);
return false;
}
processResponseProtocol (response.Headers ["Sec-WebSocket-Protocol"]);
processResponseExtensions (response.Headers ["Sec-WebSocket-Extensions"]);
processResponseCookies (response.Cookies);
return true;
}
// As client
private void processResponseProtocol(string protocol)
{
if (!protocol.IsNullOrEmpty())
_protocol = protocol;
}
// As client // As client
private HandshakeResponse receiveHandshakeResponse () private HandshakeResponse receiveHandshakeResponse ()
{ {
var res = HandshakeResponse.Parse (_stream.ReadHandshake ()); var res = HandshakeResponse.Parse (_stream.ReadHandshake ());
_logger.Debug ("A handshake response from the server:\n" + res.ToString ()); _logger.Debug ("A response to this WebSocket connection request:\n" + res.ToString ());
return res; return res;
} }
@ -1155,14 +1056,15 @@ namespace WebSocketSharp {
// As client // As client
private void send (HandshakeRequest request) private void send (HandshakeRequest request)
{ {
_logger.Debug ("A handshake Request to the server:\n" + request.ToString ()); _logger.Debug (String.Format ("A WebSocket connection request to {0}:\n{1}",
_uri, request));
_stream.WriteHandshake (request); _stream.WriteHandshake (request);
} }
// As server // As server
private bool send (HandshakeResponse response) private bool send (HandshakeResponse response)
{ {
_logger.Debug ("A handshake response to a client:\n" + response.ToString ()); _logger.Debug ("A response to a WebSocket connection request:\n" + response.ToString ());
return _stream.WriteHandshake (response); return _stream.WriteHandshake (response);
} }
@ -1195,8 +1097,7 @@ namespace WebSocketSharp {
{ {
var data = stream; var data = stream;
var compressed = false; var compressed = false;
try try {
{
if (_readyState != WsState.OPEN) if (_readyState != WsState.OPEN)
{ {
var msg = "The WebSocket connection isn't established or has been closed."; var msg = "The WebSocket connection isn't established or has been closed.";
@ -1221,13 +1122,11 @@ namespace WebSocketSharp {
sendFragmented (opcode, data, compressed); sendFragmented (opcode, data, compressed);
} }
} }
catch (Exception ex) catch (Exception ex) {
{
_logger.Fatal (ex.Message); _logger.Fatal (ex.Message);
error ("An exception has occured."); error ("An exception has occured.");
} }
finally finally {
{
if (compressed) if (compressed)
data.Dispose (); data.Dispose ();
@ -1246,8 +1145,7 @@ namespace WebSocketSharp {
Action<Opcode, Stream> sender = send; Action<Opcode, Stream> sender = send;
AsyncCallback callback = ar => AsyncCallback callback = ar =>
{ {
try try {
{
sender.EndInvoke (ar); sender.EndInvoke (ar);
if (completed != null) if (completed != null)
completed (); completed ();
@ -1359,6 +1257,42 @@ namespace WebSocketSharp {
_stream.ReadFrameAsync (completed); _stream.ReadFrameAsync (completed);
} }
// As server
private bool validateConnectionRequest (WebSocketContext context)
{
return context.IsWebSocketRequest &&
validateHostHeader (context.Host) &&
!context.SecWebSocketKey.IsNullOrEmpty () &&
context.Headers.Contains ("Sec-WebSocket-Version", _version);
}
// As client
private bool validateConnectionResponse (HandshakeResponse response)
{
return response.IsWebSocketResponse &&
response.Headers.Contains ("Sec-WebSocket-Accept", createResponseKey ()) &&
(!response.Headers.Contains ("Sec-WebSocket-Version") ||
response.Headers.Contains ("Sec-WebSocket-Version", _version));
}
// As server
private bool validateHostHeader (string value)
{
if (value == null || value.Length == 0)
return false;
if (!_uri.IsAbsoluteUri)
return true;
var i = value.IndexOf (':');
var host = i > 0 ? value.Substring (0, i) : value;
var type = Uri.CheckHostName (host);
return type != UriHostNameType.Dns ||
Uri.CheckHostName (_uri.DnsSafeHost) != UriHostNameType.Dns ||
host == _uri.DnsSafeHost;
}
#endregion #endregion
#region Internal Methods #region Internal Methods
@ -1367,9 +1301,7 @@ namespace WebSocketSharp {
internal void Close (HttpStatusCode code) internal void Close (HttpStatusCode code)
{ {
_readyState = WsState.CLOSING; _readyState = WsState.CLOSING;
send (createHandshakeResponse (code)); close (code);
closeResources ();
_readyState = WsState.CLOSED;
} }
#endregion #endregion
@ -1469,13 +1401,11 @@ namespace WebSocketSharp {
return; return;
} }
try try {
{
if (connect ()) if (connect ())
open (); open ();
} }
catch (Exception ex) catch (Exception ex) {
{
_logger.Fatal (ex.Message); _logger.Fatal (ex.Message);
var msg = "An exception has occured."; var msg = "An exception has occured.";
error (msg); error (msg);