Fix for HTTP Basic/Digest Authentication

master
sta 12 years ago
parent 0a263622f0
commit 537229902f

@ -122,6 +122,7 @@ namespace Example {
//};
//ws.SetCookie(new Cookie("nobita", "\"idiot, gunfighter\""));
//ws.SetCookie(new Cookie("dora", "tanuki"));
//ws.SetCredentials ("nobita", "password", false);
ws.Connect();
//Console.WriteLine("Compression: {0}", ws.Compression);

@ -2,6 +2,7 @@ using System;
using System.Configuration;
using System.Security.Cryptography.X509Certificates;
using WebSocketSharp;
using WebSocketSharp.Net;
using WebSocketSharp.Server;
namespace Example2
@ -17,10 +18,28 @@ namespace Example2
#if DEBUG
wssv.Log.Level = LogLevel.TRACE;
#endif
//var cert = ConfigurationManager.AppSettings ["ServerCertFile"];
//var password = ConfigurationManager.AppSettings ["CertFilePassword"];
//wssv.Certificate = new X509Certificate2 (cert, password);
// HTTP Basic/Digest Authentication
/*
wssv.AuthenticationSchemes = AuthenticationSchemes.Digest;
wssv.Realm = "WebSocket Test";
wssv.UserCredentialsFinder = identity => {
var name = identity.Name;
return name == "nobita"
? new NetworkCredential (name, "password")
: null;
};
*/
// Secure Connection
/*
var cert = ConfigurationManager.AppSettings ["ServerCertFile"];
var password = ConfigurationManager.AppSettings ["CertFilePassword"];
wssv.Certificate = new X509Certificate2 (cert, password);
*/
//wssv.KeepClean = false;
wssv.AddWebSocketService<Echo> ("/Echo");
wssv.AddWebSocketService<Chat> ("/Chat");
//wssv.AddWebSocketService<Chat> ("/Chat", () => new Chat ("Anon#"));
@ -28,8 +47,7 @@ namespace Example2
//wssv.AddWebSocketService<Chat> ("/チャット");
wssv.Start ();
if (wssv.IsListening)
{
if (wssv.IsListening) {
Console.WriteLine (
"A WebSocket Server listening on port: {0} service paths:", wssv.Port);

@ -19,24 +19,39 @@ namespace Example3
_httpsv.Log.Level = LogLevel.TRACE;
#endif
_httpsv.RootPath = ConfigurationManager.AppSettings ["RootPath"];
//var cert = ConfigurationManager.AppSettings ["ServerCertFile"];
//var password = ConfigurationManager.AppSettings ["CertFilePassword"];
//_httpsv.Certificate = new X509Certificate2 (cert, password);
// HTTP Basic/Digest Authentication
/*
_httpsv.AuthenticationSchemes = AuthenticationSchemes.Digest;
_httpsv.Realm = "WebSocket Test";
_httpsv.UserCredentialsFinder = identity => {
var name = identity.Name;
return name == "nobita"
? new NetworkCredential (name, "password")
: null;
};
*/
// Secure Connection
/*
var cert = ConfigurationManager.AppSettings ["ServerCertFile"];
var password = ConfigurationManager.AppSettings ["CertFilePassword"];
_httpsv.Certificate = new X509Certificate2 (cert, password);
*/
//_httpsv.KeepClean = false;
_httpsv.AddWebSocketService<Echo> ("/Echo");
_httpsv.AddWebSocketService<Chat> ("/Chat");
//_httpsv.AddWebSocketService<Chat> ("/Chat", () => new Chat ("Anon#"));
_httpsv.OnGet += (sender, e) =>
{
onGet (e);
};
_httpsv.OnGet += (sender, e) => onGet (e);
_httpsv.Start ();
if (_httpsv.IsListening)
{
if (_httpsv.IsListening) {
Console.WriteLine (
"An HTTP Server listening on port: {0} WebSocket service paths:", _httpsv.Port);
"An HTTP Server listening on port: {0} WebSocket service paths:",
_httpsv.Port);
foreach (var path in _httpsv.WebSocketServices.ServicePaths)
Console.WriteLine (" {0}", path);
@ -61,8 +76,7 @@ namespace Example3
var request = eventArgs.Request;
var response = eventArgs.Response;
var content = getContent (request.RawUrl);
if (content != null)
{
if (content != null) {
response.WriteContent (content);
return;
}

@ -4,7 +4,7 @@
*
* The MIT License
*
* Copyright (c) 2013 sta.blockhead
* Copyright (c) 2013-2014 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -27,112 +27,87 @@
#endregion
using System;
using System.Collections.Specialized;
using System.Text;
namespace WebSocketSharp {
internal class AuthenticationChallenge {
namespace WebSocketSharp
{
internal class AuthenticationChallenge
{
#region Private Fields
private string _algorithm;
private string _domain;
private string _nonce;
private string _opaque;
private string _qop;
private string _realm;
private string _scheme;
private string _stale;
private NameValueCollection _params;
private string _scheme;
#endregion
#region Private Constructors
#region Internal Constructors
private AuthenticationChallenge()
internal AuthenticationChallenge (string authScheme, string authParams)
{
_scheme = authScheme;
_params = authParams.ParseAuthParams ();
}
#endregion
#region Public Properties
#region Internal Properties
public string Algorithm {
internal NameValueCollection Params {
get {
return _algorithm ?? String.Empty;
return _params;
}
}
#endregion
private set {
_algorithm = value;
#region Public Properties
public string Algorithm {
get {
return _params ["algorithm"];
}
}
public string Domain {
get {
return _domain ?? String.Empty;
}
private set {
_domain = value;
return _params ["domain"];
}
}
public string Nonce {
get {
return _nonce ?? String.Empty;
}
private set {
_nonce = value;
return _params ["nonce"];
}
}
public string Opaque {
get {
return _opaque ?? String.Empty;
}
private set {
_opaque = value;
return _params ["opaque"];
}
}
public string Qop {
get {
return _qop ?? String.Empty;
}
private set {
_qop = value;
return _params ["qop"];
}
}
public string Realm {
get {
return _realm ?? String.Empty;
}
private set {
_realm = value;
return _params ["realm"];
}
}
public string Scheme {
get {
return _scheme ?? String.Empty;
}
private set {
_scheme = value;
return _scheme;
}
}
public string Stale {
get {
return _stale ?? String.Empty;
}
private set {
_stale = value;
return _params ["stale"];
}
}
@ -140,64 +115,13 @@ namespace WebSocketSharp {
#region Public Methods
public static AuthenticationChallenge Parse(string challenge)
public static AuthenticationChallenge Parse (string value)
{
var authChallenge = new AuthenticationChallenge();
if (challenge.StartsWith("basic", StringComparison.OrdinalIgnoreCase))
{
authChallenge.Scheme = "Basic";
authChallenge.Realm = challenge.Substring(6).GetValueInternal("=").Trim('"');
return authChallenge;
}
foreach (var p in challenge.SplitHeaderValue(','))
{
var param = p.Trim();
if (param.StartsWith("digest", StringComparison.OrdinalIgnoreCase))
{
authChallenge.Scheme = "Digest";
authChallenge.Realm = param.Substring(7).GetValueInternal("=").Trim('"');
continue;
}
var value = param.GetValueInternal("=").Trim('"');
if (param.StartsWith("domain", StringComparison.OrdinalIgnoreCase))
{
authChallenge.Domain = value;
continue;
}
if (param.StartsWith("nonce", StringComparison.OrdinalIgnoreCase))
{
authChallenge.Nonce = value;
continue;
}
if (param.StartsWith("opaque", StringComparison.OrdinalIgnoreCase))
{
authChallenge.Opaque = value;
continue;
}
if (param.StartsWith("stale", StringComparison.OrdinalIgnoreCase))
{
authChallenge.Stale = value;
continue;
}
if (param.StartsWith("algorithm", StringComparison.OrdinalIgnoreCase))
{
authChallenge.Algorithm = value;
continue;
}
if (param.StartsWith("qop", StringComparison.OrdinalIgnoreCase))
authChallenge.Qop = value;
}
return authChallenge;
var challenge = value.Split (new char [] {' '}, 2);
var scheme = challenge [0].ToLower ();
return scheme == "basic" || scheme == "digest"
? new AuthenticationChallenge (scheme, challenge [1])
: null;
}
#endregion

@ -27,169 +27,156 @@
#endregion
using System;
using System.Security.Cryptography;
using System.Collections.Specialized;
using System.Security.Principal;
using System.Text;
using WebSocketSharp.Net;
namespace WebSocketSharp {
internal class AuthenticationResponse {
namespace WebSocketSharp
{
internal class AuthenticationResponse
{
#region Private Fields
private string _algorithm;
private string _cnonce;
private string _method;
private string _nc;
private string _nonce;
private string _opaque;
private string _password;
private string _qop;
private string _realm;
private string _response;
private string _scheme;
private string _uri;
private string _userName;
private uint _nonceCount;
private NameValueCollection _params;
private string _scheme;
#endregion
#region Private Constructors
private AuthenticationResponse()
private AuthenticationResponse (
string authScheme, NameValueCollection authParams)
{
_scheme = authScheme;
_params = authParams;
}
#endregion
#region Public Constructors
#region Internal Constructors
internal AuthenticationResponse (NetworkCredential credentials)
: this ("Basic", new NameValueCollection (), credentials, 0)
{
}
public AuthenticationResponse(WsCredential credential)
internal AuthenticationResponse (
AuthenticationChallenge challenge,
NetworkCredential credentials,
uint nonceCount)
: this (challenge.Scheme, challenge.Params, credentials, nonceCount)
{
_userName = credential.UserName;
_password = credential.Password;
_scheme = "Basic";
}
public AuthenticationResponse(WsCredential credential, AuthenticationChallenge challenge)
internal AuthenticationResponse (
string authScheme,
NameValueCollection authParams,
NetworkCredential credentials,
uint nonceCount)
{
_userName = credential.UserName;
_password = credential.Password;
_scheme = challenge.Scheme;
_realm = challenge.Realm;
if (_scheme == "Digest")
initForDigest(credential, challenge);
_scheme = authScheme.ToLower ();
_params = authParams;
_params ["username"] = credentials.UserName;
_params ["password"] = credentials.Password;
_params ["uri"] = credentials.Domain;
_nonceCount = nonceCount;
if (_scheme == "digest")
initAsDigest ();
}
#endregion
#region Public Properties
#region Internal Properties
public string Algorithm {
internal uint NonceCount {
get {
return _algorithm ?? String.Empty;
return _nonceCount < UInt32.MaxValue
? _nonceCount
: 0;
}
}
private set {
_algorithm = value;
internal NameValueCollection Params {
get {
return _params;
}
}
public string Cnonce {
#endregion
#region Public Properties
public string Algorithm {
get {
return _cnonce ?? String.Empty;
return _params ["algorithm"];
}
}
private set {
_cnonce = value;
public string Cnonce {
get {
return _params ["cnonce"];
}
}
public string Nc {
get {
return _nc ?? String.Empty;
}
private set {
_nc = value;
return _params ["nc"];
}
}
public string Nonce {
get {
return _nonce ?? String.Empty;
}
private set {
_nonce = value;
return _params ["nonce"];
}
}
public string Opaque {
get {
return _opaque ?? String.Empty;
return _params ["opaque"];
}
}
private set {
_opaque = value;
public string Password {
get {
return _params ["password"];
}
}
public string Qop {
get {
return _qop ?? String.Empty;
}
private set {
_qop = value;
return _params ["qop"];
}
}
public string Realm {
get {
return _realm ?? String.Empty;
}
private set {
_realm = value;
return _params ["realm"];
}
}
public string Response {
get {
return _response ?? String.Empty;
}
private set {
_response = value;
return _params ["response"];
}
}
public string Scheme {
get {
return _scheme ?? String.Empty;
}
private set {
_scheme = value;
return _scheme;
}
}
public string Uri {
get {
return _uri ?? String.Empty;
}
private set {
_uri = value;
return _params ["uri"];
}
}
public string UserName {
get {
return _userName ?? String.Empty;
}
private set {
_userName = value;
return _params ["username"];
}
}
@ -197,130 +184,77 @@ namespace WebSocketSharp {
#region Private Methods
private string a1()
private static bool contains (string [] array, string item)
{
var result = String.Format("{0}:{1}:{2}", _userName, _realm, _password);
return _algorithm != null && _algorithm.ToLower() == "md5-sess"
? String.Format("{0}:{1}:{2}", hash(result), _nonce, _cnonce)
: result;
}
foreach (var i in array)
if (i.Trim ().ToLower () == item)
return true;
private string a2()
{
return String.Format("{0}:{1}", _method, _uri);
return false;
}
private static string createNonceValue()
private void initAsDigest ()
{
var src = new byte[16];
var rand = new Random();
rand.NextBytes(src);
var nonce = new StringBuilder(32);
foreach (var b in src)
nonce.Append(b.ToString("x2"));
return nonce.ToString();
}
private string createRequestDigest()
{
if (Qop == "auth")
{
var data = String.Format("{0}:{1}:{2}:{3}:{4}",
_nonce, _nc, _cnonce, _qop, hash(a2()));
return kd(hash(a1()), data);
}
return kd(hash(a1()), String.Format("{0}:{1}", _nonce, hash(a2())));
}
private static string hash(string value)
{
var md5 = MD5.Create();
var src = Encoding.UTF8.GetBytes(value);
var hashed = md5.ComputeHash(src);
var result = new StringBuilder(64);
foreach (var b in hashed)
result.Append(b.ToString("x2"));
return result.ToString();
}
private void initForDigest(WsCredential credential, AuthenticationChallenge challenge)
{
_nonce = challenge.Nonce;
_method = "GET";
_uri = credential.Domain;
_algorithm = challenge.Algorithm;
_opaque = challenge.Opaque;
foreach (var qop in challenge.Qop.Split(','))
{
if (qop.Trim().ToLower() == "auth")
{
_qop = "auth";
_nc = "00000001";
break;
var qops = _params ["qop"];
if (qops != null) {
var qop = "auth";
if (contains (qops.Split (','), qop)) {
_params ["qop"] = qop;
_params ["nc"] = String.Format ("{0:x8}", ++_nonceCount);
_params ["cnonce"] = HttpUtility.CreateNonceValue ();
}
else
_params ["qop"] = null;
}
_cnonce = createNonceValue();
_response = createRequestDigest();
_params ["method"] = "GET";
_params ["response"] = HttpUtility.CreateRequestDigest (_params);
}
private static string kd(string secret, string data)
{
var concatenated = String.Format("{0}:{1}", secret, data);
return hash(concatenated);
}
private string toBasicCredentials()
{
var userPass = String.Format("{0}:{1}", _userName, _password);
var base64UserPass = Convert.ToBase64String(Encoding.UTF8.GetBytes(userPass));
#endregion
return "Basic " + base64UserPass;
}
#region Public Methods
private string toDigestCredentials()
public static AuthenticationResponse Parse (string value)
{
var digestResponse = new StringBuilder(64);
digestResponse.AppendFormat("username={0}", _userName.Quote());
digestResponse.AppendFormat(", realm={0}", _realm.Quote());
digestResponse.AppendFormat(", nonce={0}", _nonce.Quote());
digestResponse.AppendFormat(", uri={0}", _uri.Quote());
digestResponse.AppendFormat(", response={0}", _response.Quote());
if (!_algorithm.IsNullOrEmpty())
digestResponse.AppendFormat(", algorithm={0}", _algorithm);
if (!_opaque.IsNullOrEmpty())
digestResponse.AppendFormat(", opaque={0}", _opaque.Quote());
if (!_qop.IsNullOrEmpty())
digestResponse.AppendFormat(", qop={0}", _qop);
if (!_nc.IsNullOrEmpty())
digestResponse.AppendFormat(", nc={0}", _nc);
if (!_qop.IsNullOrEmpty())
digestResponse.AppendFormat(", cnonce={0}", _cnonce.Quote());
try {
var credentials = value.Split (new char [] { ' ' }, 2);
if (credentials.Length != 2)
return null;
var scheme = credentials [0].ToLower ();
return scheme == "basic"
? new AuthenticationResponse (
scheme, credentials [1].ParseBasicAuthResponseParams ())
: scheme == "digest"
? new AuthenticationResponse (
scheme, credentials [1].ParseAuthParams ())
: null;
}
catch {
}
return "Digest " + digestResponse.ToString();
return null;
}
#endregion
#region Public Methods
public static AuthenticationResponse Parse(string response)
public IIdentity ToIdentity ()
{
throw new NotImplementedException();
return _scheme == "basic"
? new HttpBasicIdentity (
_params ["username"], _params ["password"]) as IIdentity
: _scheme == "digest"
? new HttpDigestIdentity (_params)
: null;
}
public override string ToString()
public override string ToString ()
{
return _scheme == "Basic"
? toBasicCredentials()
: toDigestCredentials();
return _scheme == "basic"
? HttpUtility.CreateBasicAuthCredentials (
_params ["username"], _params ["password"])
: _scheme == "digest"
? HttpUtility.CreateDigestAuthCredentials (_params)
: String.Empty;
}
#endregion

@ -1,22 +1,21 @@
#region License
/*
* Ext.cs
* IsPredefinedScheme and MaybeUri methods are derived from System.Uri.cs
* GetStatusDescription method is derived from System.Net.HttpListenerResponse.cs
*
* The MIT License
* Some part of this code are derived from Mono (http://www.mono-project.com).
*
* Copyright (c) 2010-2013 sta.blockhead
* ParseBasicAuthResponseParams is derived from System.Net.HttpListenerContext.cs
* GetStatusDescription is derived from System.Net.HttpListenerResponse.cs
* IsPredefinedScheme and MaybeUri are derived from System.Uri.cs
*
* System.Uri.cs
* (C) 2001 Garrett Rooney
* (C) 2003 Ian MacLean
* (C) 2003 Ben Maurer
* Copyright (C) 2003, 2005, 2009 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2009 Stephane Delcroix
* The MIT License
*
* System.Net.HttpListenerResponse.cs
* Copyright (C) 2003, 2005, 2009 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2001 Garrett Rooney
* Copyright (c) 2003 Ian MacLean
* Copyright (c) 2003 Ben Maurer
* Copyright (c) 2003, 2005, 2009 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2009 Stephane Delcroix
* Copyright (c) 2010-2013 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -279,6 +278,20 @@ namespace WebSocketSharp
: null;
}
internal static void Close (
this HttpListenerResponse response, HttpStatusCode code)
{
response.StatusCode = (int) code;
response.OutputStream.Close ();
}
internal static void CloseWithAuthChallenge (
this HttpListenerResponse response, string challenge)
{
response.Headers.SetInternal ("WWW-Authenticate", challenge, true);
response.Close (HttpStatusCode.Unauthorized);
}
internal static byte [] Compress (this byte [] value, CompressionMethod method)
{
return method == CompressionMethod.DEFLATE
@ -447,9 +460,9 @@ namespace WebSocketSharp
}
internal static TcpListenerWebSocketContext GetWebSocketContext (
this TcpClient client, bool secure, X509Certificate cert)
this TcpClient client, X509Certificate cert, bool secure, Logger logger)
{
return new TcpListenerWebSocketContext (client, secure, cert);
return new TcpListenerWebSocketContext (client, cert, secure, logger);
}
internal static bool IsCompressionExtension (this string value)
@ -517,6 +530,57 @@ namespace WebSocketSharp
: String.Format ("\"{0}\"", value.Replace ("\"", "\\\""));
}
internal static NameValueCollection ParseBasicAuthResponseParams (
this string value)
{
// HTTP Basic Authentication response is a formatted Base64 string.
var userPass = Encoding.Default.GetString (
Convert.FromBase64String (value));
// The format is [<domain>\]<username>:<password>.
// 'domain' is optional.
var i = userPass.IndexOf (':');
var username = userPass.Substring (0, i);
var password = i < userPass.Length - 1
? userPass.Substring (i + 1)
: String.Empty;
// Check if 'domain' exists.
i = username.IndexOf ('\\');
if (i > 0)
username = username.Substring (i + 1);
var result = new NameValueCollection ();
result ["username"] = username;
result ["password"] = password;
return result;
}
internal static NameValueCollection ParseAuthParams (this string value)
{
var result = new NameValueCollection ();
var i = 0;
string name, val;
foreach (var param in value.SplitHeaderValue (',')) {
i = param.IndexOf ('=');
if (i > 0) {
name = param.Substring (0, i).Trim ();
val = i < param.Length - 1
? param.Substring (i + 1).Trim ().Trim ('"')
: String.Empty;
}
else {
name = param;
val = String.Empty;
}
result.Add (name, val);
}
return result;
}
internal static byte [] ReadBytes (this Stream stream, int length)
{
return stream.readBytes (new byte [length], 0, length);
@ -1642,8 +1706,9 @@ namespace WebSocketSharp
/// Converts the specified <see cref="string"/> to a <see cref="Uri"/>.
/// </summary>
/// <returns>
/// A <see cref="Uri"/> converted from <paramref name="uriString"/>, or <see langword="null"/>
/// if <paramref name="uriString"/> is <see langword="null"/> or <see cref="String.Empty"/>.
/// A <see cref="Uri"/> converted from <paramref name="uriString"/>, or
/// <see langword="null"/> if <paramref name="uriString"/> is
/// <see langword="null"/> or empty.
/// </returns>
/// <param name="uriString">
/// A <see cref="string"/> to convert.

@ -35,6 +35,13 @@ namespace WebSocketSharp
{
internal abstract class HandshakeBase
{
#region Private Fields
private NameValueCollection _headers;
private Version _version;
#endregion
#region Protected Const Fields
protected const string CrLf = "\r\n";
@ -45,8 +52,8 @@ namespace WebSocketSharp
protected HandshakeBase ()
{
ProtocolVersion = HttpVersion.Version11;
Headers = new NameValueCollection ();
_version = HttpVersion.Version11;
_headers = new NameValueCollection ();
}
#endregion
@ -54,37 +61,29 @@ namespace WebSocketSharp
#region Public Properties
public NameValueCollection Headers {
get; protected set;
get {
return _headers;
}
protected set {
_headers = value;
}
}
public Version ProtocolVersion {
get; protected set;
get {
return _version;
}
protected set {
_version = value;
}
}
#endregion
#region Public Methods
public void AddHeader (string name, string value)
{
Headers.Add (name, value);
}
public bool ContainsHeader (string name)
{
return Headers.Contains (name);
}
public bool ContainsHeader (string name, string value)
{
return Headers.Contains (name, value);
}
public string [] GetHeaderValues (string name)
{
return Headers.GetValues (name);
}
public byte [] ToByteArray ()
{
return Encoding.UTF8.GetBytes (ToString ());

@ -4,7 +4,7 @@
*
* The MIT License
*
* Copyright (c) 2012-2013 sta.blockhead
* Copyright (c) 2012-2014 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -31,7 +31,6 @@ using System.Collections.Specialized;
using System.Linq;
using System.Text;
using WebSocketSharp.Net;
using WebSocketSharp.Net.WebSockets;
namespace WebSocketSharp
{
@ -39,7 +38,10 @@ namespace WebSocketSharp
{
#region Private Fields
private string _method;
private NameValueCollection _queryString;
private string _rawUrl;
private Uri _uri;
#endregion
@ -55,17 +57,31 @@ namespace WebSocketSharp
public HandshakeRequest (string uriString)
{
HttpMethod = "GET";
RequestUri = uriString.ToUri ();
AddHeader ("User-Agent", "websocket-sharp/1.0");
AddHeader ("Upgrade", "websocket");
AddHeader ("Connection", "Upgrade");
_method = "GET";
_uri = uriString.ToUri ();
_rawUrl = _uri.IsAbsoluteUri
? _uri.PathAndQuery
: uriString;
var headers = Headers;
headers ["User-Agent"] = "websocket-sharp/1.0";
headers ["Upgrade"] = "websocket";
headers ["Connection"] = "Upgrade";
}
#endregion
#region Public Properties
public AuthenticationResponse AuthResponse {
get {
var response = Headers ["Authorization"];
return !response.IsNullOrEmpty ()
? AuthenticationResponse.Parse (response)
: null;
}
}
public CookieCollection Cookies {
get {
return Headers.GetCookies (false);
@ -73,33 +89,36 @@ namespace WebSocketSharp
}
public string HttpMethod {
get; private set;
get {
return _method;
}
private set {
_method = value;
}
}
public bool IsWebSocketRequest {
get {
return HttpMethod == "GET" &&
var headers = Headers;
return _method == "GET" &&
ProtocolVersion >= HttpVersion.Version11 &&
Headers.Contains ("Upgrade", "websocket") &&
Headers.Contains ("Connection", "Upgrade");
headers.Contains ("Upgrade", "websocket") &&
headers.Contains ("Connection", "Upgrade");
}
}
public NameValueCollection QueryString {
get {
if (_queryString == null)
{
if (_queryString == null) {
_queryString = new NameValueCollection ();
var i = RawUrl.IndexOf ('?');
if (i > 0)
{
if (i > 0) {
var query = RawUrl.Substring (i + 1);
var components = query.Split ('&');
foreach (var c in components)
{
foreach (var c in components) {
var nv = c.GetNameAndValue ("=");
if (nv.Key != null)
{
if (nv.Key != null) {
var name = nv.Key.UrlDecode ();
var val = nv.Value.UrlDecode ();
_queryString.Add (name, val);
@ -114,14 +133,22 @@ namespace WebSocketSharp
public string RawUrl {
get {
return RequestUri.IsAbsoluteUri
? RequestUri.PathAndQuery
: RequestUri.OriginalString;
return _rawUrl;
}
private set {
_rawUrl = value;
}
}
public Uri RequestUri {
get; private set;
get {
return _uri;
}
private set {
_uri = value;
}
}
#endregion
@ -132,10 +159,8 @@ namespace WebSocketSharp
{
var requestLine = request [0].Split (' ');
if (requestLine.Length != 3)
{
var msg = "Invalid HTTP Request-Line: " + request [0];
throw new ArgumentException (msg, "request");
}
throw new ArgumentException (
"Invalid HTTP Request-Line: " + request [0], "request");
var headers = new WebHeaderCollection ();
for (int i = 1; i < request.Length; i++)
@ -144,8 +169,9 @@ namespace WebSocketSharp
return new HandshakeRequest {
Headers = headers,
HttpMethod = requestLine [0],
RequestUri = requestLine [1].ToUri (),
ProtocolVersion = new Version (requestLine [2].Substring (5))
ProtocolVersion = new Version (requestLine [2].Substring (5)),
RawUrl = requestLine [1],
RequestUri = requestLine [1].ToUri ()
};
}
@ -160,21 +186,17 @@ namespace WebSocketSharp
if (!sorted [i].Expired)
header.AppendFormat ("; {0}", sorted [i].ToString ());
AddHeader ("Cookie", header.ToString ());
}
public void SetAuthorization (AuthenticationResponse response)
{
var credentials = response.ToString ();
AddHeader ("Authorization", credentials);
Headers ["Cookie"] = header.ToString ();
}
public override string ToString ()
{
var buffer = new StringBuilder (64);
buffer.AppendFormat ("{0} {1} HTTP/{2}{3}", HttpMethod, RawUrl, ProtocolVersion, CrLf);
foreach (string key in Headers.AllKeys)
buffer.AppendFormat ("{0}: {1}{2}", key, Headers [key], CrLf);
buffer.AppendFormat ("{0} {1} HTTP/{2}{3}", _method, _rawUrl, ProtocolVersion, CrLf);
var headers = Headers;
foreach (var key in headers.AllKeys)
buffer.AppendFormat ("{0}: {1}{2}", key, headers [key], CrLf);
buffer.Append (CrLf);
return buffer.ToString ();

@ -4,7 +4,7 @@
*
* The MIT License
*
* Copyright (c) 2012-2013 sta.blockhead
* Copyright (c) 2012-2014 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -35,20 +35,34 @@ namespace WebSocketSharp
{
internal class HandshakeResponse : HandshakeBase
{
#region Public Constructors
#region Private Fields
private string _code;
private string _reason;
#endregion
#region Private Constructors
public HandshakeResponse ()
: this (HttpStatusCode.SwitchingProtocols)
private HandshakeResponse ()
{
AddHeader ("Upgrade", "websocket");
AddHeader ("Connection", "Upgrade");
}
#endregion
#region Public Constructors
public HandshakeResponse (HttpStatusCode code)
{
StatusCode = ((int) code).ToString ();
Reason = code.GetDescription ();
AddHeader ("Server", "websocket-sharp/1.0");
_code = ((int) code).ToString ();
_reason = code.GetDescription ();
var headers = Headers;
headers ["Server"] = "websocket-sharp/1.0";
if (code == HttpStatusCode.SwitchingProtocols) {
headers ["Upgrade"] = "websocket";
headers ["Connection"] = "Upgrade";
}
}
#endregion
@ -72,25 +86,38 @@ namespace WebSocketSharp
public bool IsUnauthorized {
get {
return StatusCode == "401";
return _code == "401";
}
}
public bool IsWebSocketResponse {
get {
var headers = Headers;
return ProtocolVersion >= HttpVersion.Version11 &&
StatusCode == "101" &&
Headers.Contains ("Upgrade", "websocket") &&
Headers.Contains ("Connection", "Upgrade");
_code == "101" &&
headers.Contains ("Upgrade", "websocket") &&
headers.Contains ("Connection", "Upgrade");
}
}
public string Reason {
get; private set;
get {
return _reason;
}
private set {
_reason = value;
}
}
public string StatusCode {
get; private set;
get {
return _code;
}
private set {
_code = value;
}
}
#endregion
@ -100,7 +127,7 @@ namespace WebSocketSharp
public static HandshakeResponse CreateCloseResponse (HttpStatusCode code)
{
var res = new HandshakeResponse (code);
res.AddHeader ("Connection", "close");
res.Headers ["Connection"] = "close";
return res;
}
@ -132,16 +159,20 @@ namespace WebSocketSharp
if (cookies == null || cookies.Count == 0)
return;
var headers = Headers;
foreach (var cookie in cookies.Sorted)
AddHeader ("Set-Cookie", cookie.ToResponseString ());
headers.Add ("Set-Cookie", cookie.ToResponseString ());
}
public override string ToString ()
{
var buffer = new StringBuilder (64);
buffer.AppendFormat ("HTTP/{0} {1} {2}{3}", ProtocolVersion, StatusCode, Reason, CrLf);
foreach (string key in Headers.AllKeys)
buffer.AppendFormat ("{0}: {1}{2}", key, Headers [key], CrLf);
buffer.AppendFormat (
"HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf);
var headers = Headers;
foreach (var key in headers.AllKeys)
buffer.AppendFormat ("{0}: {1}{2}", key, headers [key], CrLf);
buffer.Append (CrLf);
return buffer.ToString ();

@ -1,69 +1,67 @@
//
// AuthenticationSchemes.cs
// Copied from System.Net.AuthenticationSchemes.cs
//
// Author:
// Atsushi Enomoto <atsushi@ximian.com>
//
// Copyright (C) 2005 Novell, Inc. (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#region License
/*
* AuthenticationSchemes.cs
*
* This code is derived from System.Net.AuthenticationSchemes.cs of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2013 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
namespace WebSocketSharp.Net {
#region Authors
/*
* Authors:
* Atsushi Enomoto <atsushi@ximian.com>
*/
#endregion
/// <summary>
/// Contains the values of the schemes for authentication.
/// </summary>
[Flags]
public enum AuthenticationSchemes {
using System;
/// <summary>
/// Indicates that no authentication is allowed.
/// </summary>
None,
/// <summary>
/// Indicates digest authentication.
/// </summary>
Digest = 1,
/// <summary>
/// Indicates negotiating with the client to determine the authentication scheme.
/// </summary>
Negotiate = 2,
/// <summary>
/// Indicates NTLM authentication.
/// </summary>
Ntlm = 4,
/// <summary>
/// Indicates Windows authentication.
/// </summary>
IntegratedWindowsAuthentication = 6,
/// <summary>
/// Indicates basic authentication.
/// </summary>
Basic = 8,
/// <summary>
/// Indicates anonymous authentication.
/// </summary>
Anonymous = 0x8000,
}
namespace WebSocketSharp.Net
{
/// <summary>
/// Contains the values of the schemes for authentication.
/// </summary>
[Flags]
public enum AuthenticationSchemes
{
/// <summary>
/// Indicates that no authentication is allowed.
/// </summary>
None,
/// <summary>
/// Indicates digest authentication.
/// </summary>
Digest = 1,
/// <summary>
/// Indicates basic authentication.
/// </summary>
Basic = 8,
/// <summary>
/// Indicates anonymous authentication.
/// </summary>
Anonymous = 0x8000,
}
}

@ -1,32 +1,41 @@
//
// EndPointListener.cs
// Copied from System.Net.EndPointListener.cs
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
// Copyright (c) 2012-2013 sta.blockhead
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#region License
/*
* EndPointListener.cs
*
* This code is derived from System.Net.EndPointListener.cs of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2013 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion
using System;
using System.Collections;
@ -40,411 +49,405 @@ using System.Threading;
namespace WebSocketSharp.Net
{
internal sealed class EndPointListener
{
#region Private Fields
List<ListenerPrefix> _all; // host = '+'
X509Certificate2 _cert;
IPEndPoint _endpoint;
Dictionary<ListenerPrefix, HttpListener> _prefixes;
bool _secure;
Socket _socket;
List<ListenerPrefix> _unhandled; // host = '*'
Dictionary<HttpConnection, HttpConnection> _unregistered;
#endregion
#region Public Constructors
public EndPointListener (
IPAddress address,
int port,
bool secure,
string certFolderPath,
X509Certificate2 defaultCert
)
{
if (secure) {
_secure = secure;
_cert = getCertificate (port, certFolderPath, defaultCert);
if (_cert == null)
throw new ArgumentException ("Server certificate not found.");
}
_endpoint = new IPEndPoint (address, port);
_socket = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_socket.Bind (_endpoint);
_socket.Listen (500);
var args = new SocketAsyncEventArgs ();
args.UserToken = this;
args.Completed += onAccept;
_socket.AcceptAsync (args);
_prefixes = new Dictionary<ListenerPrefix, HttpListener> ();
_unregistered = new Dictionary<HttpConnection, HttpConnection> ();
}
#endregion
#region Private Methods
private static void addSpecial (List<ListenerPrefix> prefixes, ListenerPrefix prefix)
{
if (prefixes == null)
return;
foreach (var p in prefixes)
if (p.Path == prefix.Path) // TODO: code
throw new HttpListenerException (400, "Prefix already in use.");
prefixes.Add (prefix);
}
private void checkIfRemove ()
{
if (_prefixes.Count > 0)
return;
if (_unhandled != null && _unhandled.Count > 0)
return;
if (_all != null && _all.Count > 0)
return;
EndPointManager.RemoveEndPoint (this, _endpoint);
}
private static RSACryptoServiceProvider createRSAFromFile (string filename)
{
var rsa = new RSACryptoServiceProvider ();
byte[] pvk = null;
using (var fs = File.Open (filename, FileMode.Open, FileAccess.Read, FileShare.Read))
{
pvk = new byte [fs.Length];
fs.Read (pvk, 0, pvk.Length);
}
rsa.ImportCspBlob (pvk);
return rsa;
}
private static X509Certificate2 getCertificate (
int port, string certFolderPath, X509Certificate2 defaultCert)
{
try {
var cer = Path.Combine (certFolderPath, String.Format ("{0}.cer", port));
var key = Path.Combine (certFolderPath, String.Format ("{0}.key", port));
if (File.Exists (cer) && File.Exists (key))
{
var cert = new X509Certificate2 (cer);
cert.PrivateKey = createRSAFromFile (key);
return cert;
}
}
catch {
}
return defaultCert;
}
private static HttpListener matchFromList (
string host, string path, List<ListenerPrefix> list, out ListenerPrefix prefix)
{
prefix = null;
if (list == null)
return null;
HttpListener best_match = null;
var best_length = -1;
foreach (var p in list)
{
var ppath = p.Path;
if (ppath.Length < best_length)
continue;
if (path.StartsWith (ppath))
{
best_length = ppath.Length;
best_match = p.Listener;
prefix = p;
}
}
return best_match;
}
private static void onAccept (object sender, EventArgs e)
{
var args = (SocketAsyncEventArgs) e;
var epListener = (EndPointListener) args.UserToken;
Socket accepted = null;
if (args.SocketError == SocketError.Success)
{
accepted = args.AcceptSocket;
args.AcceptSocket = null;
}
try {
epListener._socket.AcceptAsync (args);
}
catch {
if (accepted != null)
accepted.Close ();
return;
}
if (accepted == null)
return;
HttpConnection conn = null;
try {
conn = new HttpConnection (accepted, epListener, epListener._secure, epListener._cert);
lock (((ICollection) epListener._unregistered).SyncRoot)
{
epListener._unregistered [conn] = conn;
}
conn.BeginReadRequest ();
}
catch {
if (conn != null)
{
conn.Close (true);
return;
}
accepted.Close ();
}
}
private static bool removeSpecial (List<ListenerPrefix> prefixes, ListenerPrefix prefix)
{
if (prefixes == null)
return false;
var count = prefixes.Count;
for (int i = 0; i < count; i++)
{
if (prefixes [i].Path == prefix.Path)
{
prefixes.RemoveAt (i);
return true;
}
}
return false;
}
private HttpListener searchListener (Uri uri, out ListenerPrefix prefix)
{
prefix = null;
if (uri == null)
return null;
var host = uri.Host;
var port = uri.Port;
var path = HttpUtility.UrlDecode (uri.AbsolutePath);
var path_slash = path [path.Length - 1] == '/' ? path : path + "/";
HttpListener best_match = null;
var best_length = -1;
if (host != null && host.Length > 0)
{
foreach (var p in _prefixes.Keys)
{
var ppath = p.Path;
if (ppath.Length < best_length)
continue;
if (p.Host != host || p.Port != port)
continue;
if (path.StartsWith (ppath) || path_slash.StartsWith (ppath))
{
best_length = ppath.Length;
best_match = _prefixes [p];
prefix = p;
}
}
if (best_length != -1)
return best_match;
}
var list = _unhandled;
best_match = matchFromList (host, path, list, out prefix);
if (path != path_slash && best_match == null)
best_match = matchFromList (host, path_slash, list, out prefix);
if (best_match != null)
return best_match;
list = _all;
best_match = matchFromList (host, path, list, out prefix);
if (path != path_slash && best_match == null)
best_match = matchFromList (host, path_slash, list, out prefix);
if (best_match != null)
return best_match;
return null;
}
#endregion
#region Internal Methods
internal static bool CertificateExists (int port, string certFolderPath)
{
var cer = Path.Combine (certFolderPath, String.Format ("{0}.cer", port));
var key = Path.Combine (certFolderPath, String.Format ("{0}.key", port));
return File.Exists (cer) && File.Exists (key);
}
internal void RemoveConnection (HttpConnection connection)
{
lock (((ICollection) _unregistered).SyncRoot)
{
_unregistered.Remove (connection);
}
}
#endregion
#region Public Methods
public void AddPrefix (ListenerPrefix prefix, HttpListener listener)
{
List<ListenerPrefix> current, future;
if (prefix.Host == "*")
{
do {
current = _unhandled;
future = current != null
? new List<ListenerPrefix> (current)
: new List<ListenerPrefix> ();
prefix.Listener = listener;
addSpecial (future, prefix);
} while (Interlocked.CompareExchange (ref _unhandled, future, current) != current);
return;
}
if (prefix.Host == "+")
{
do {
current = _all;
future = current != null
? new List<ListenerPrefix> (current)
: new List<ListenerPrefix> ();
prefix.Listener = listener;
addSpecial (future, prefix);
} while (Interlocked.CompareExchange (ref _all, future, current) != current);
return;
}
Dictionary<ListenerPrefix, HttpListener> prefs, p2;
do {
prefs = _prefixes;
if (prefs.ContainsKey (prefix))
{
HttpListener other = prefs [prefix];
if (other != listener) // TODO: code.
throw new HttpListenerException (400, "There's another listener for " + prefix);
return;
}
p2 = new Dictionary<ListenerPrefix, HttpListener> (prefs);
p2 [prefix] = listener;
} while (Interlocked.CompareExchange (ref _prefixes, p2, prefs) != prefs);
}
public bool BindContext (HttpListenerContext context)
{
var req = context.Request;
ListenerPrefix prefix;
var listener = searchListener (req.Url, out prefix);
if (listener == null)
return false;
context.Listener = listener;
context.Connection.Prefix = prefix;
return true;
}
public void Close ()
{
_socket.Close ();
lock (((ICollection) _unregistered).SyncRoot)
{
var copy = new Dictionary<HttpConnection, HttpConnection> (_unregistered);
foreach (var conn in copy.Keys)
conn.Close (true);
copy.Clear ();
_unregistered.Clear ();
}
}
public void RemovePrefix (ListenerPrefix prefix, HttpListener listener)
{
List<ListenerPrefix> current, future;
if (prefix.Host == "*")
{
do {
current = _unhandled;
future = current != null
? new List<ListenerPrefix> (current)
: new List<ListenerPrefix> ();
if (!removeSpecial (future, prefix))
break; // Prefix not found.
} while (Interlocked.CompareExchange (ref _unhandled, future, current) != current);
checkIfRemove ();
return;
}
if (prefix.Host == "+")
{
do {
current = _all;
future = current != null
? new List<ListenerPrefix> (current)
: new List<ListenerPrefix> ();
if (!removeSpecial (future, prefix))
break; // Prefix not found.
} while (Interlocked.CompareExchange (ref _all, future, current) != current);
checkIfRemove ();
return;
}
Dictionary<ListenerPrefix, HttpListener> prefs, p2;
do {
prefs = _prefixes;
if (!prefs.ContainsKey (prefix))
break;
p2 = new Dictionary<ListenerPrefix, HttpListener> (prefs);
p2.Remove (prefix);
} while (Interlocked.CompareExchange (ref _prefixes, p2, prefs) != prefs);
checkIfRemove ();
}
public void UnbindContext (HttpListenerContext context)
{
if (context == null || context.Request == null)
return;
context.Listener.UnregisterContext (context);
}
#endregion
}
internal sealed class EndPointListener
{
#region Private Fields
private List<ListenerPrefix> _all; // host = '+'
private X509Certificate2 _cert;
private IPEndPoint _endpoint;
private Dictionary<ListenerPrefix, HttpListener> _prefixes;
private bool _secure;
private Socket _socket;
private List<ListenerPrefix> _unhandled; // host = '*'
private Dictionary<HttpConnection, HttpConnection> _unregistered;
#endregion
#region Public Constructors
public EndPointListener (
IPAddress address,
int port,
bool secure,
string certFolderPath,
X509Certificate2 defaultCert)
{
if (secure) {
_secure = secure;
_cert = getCertificate (port, certFolderPath, defaultCert);
if (_cert == null)
throw new ArgumentException ("Server certificate not found.");
}
_endpoint = new IPEndPoint (address, port);
_socket = new Socket (
address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_socket.Bind (_endpoint);
_socket.Listen (500);
var args = new SocketAsyncEventArgs ();
args.UserToken = this;
args.Completed += onAccept;
_socket.AcceptAsync (args);
_prefixes = new Dictionary<ListenerPrefix, HttpListener> ();
_unregistered = new Dictionary<HttpConnection, HttpConnection> ();
}
#endregion
#region Private Methods
private static void addSpecial (
List<ListenerPrefix> prefixes, ListenerPrefix prefix)
{
if (prefixes == null)
return;
foreach (var p in prefixes)
if (p.Path == prefix.Path) // TODO: code
throw new HttpListenerException (400, "Prefix already in use.");
prefixes.Add (prefix);
}
private void checkIfRemove ()
{
if (_prefixes.Count > 0)
return;
if (_unhandled != null && _unhandled.Count > 0)
return;
if (_all != null && _all.Count > 0)
return;
EndPointManager.RemoveEndPoint (this, _endpoint);
}
private static RSACryptoServiceProvider createRSAFromFile (string filename)
{
var rsa = new RSACryptoServiceProvider ();
byte[] pvk = null;
using (var fs = File.Open (filename, FileMode.Open, FileAccess.Read, FileShare.Read)) {
pvk = new byte [fs.Length];
fs.Read (pvk, 0, pvk.Length);
}
rsa.ImportCspBlob (pvk);
return rsa;
}
private static X509Certificate2 getCertificate (
int port, string certFolderPath, X509Certificate2 defaultCert)
{
try {
var cer = Path.Combine (certFolderPath, String.Format ("{0}.cer", port));
var key = Path.Combine (certFolderPath, String.Format ("{0}.key", port));
if (File.Exists (cer) && File.Exists (key)) {
var cert = new X509Certificate2 (cer);
cert.PrivateKey = createRSAFromFile (key);
return cert;
}
}
catch {
}
return defaultCert;
}
private static HttpListener matchFromList (
string host, string path, List<ListenerPrefix> list, out ListenerPrefix prefix)
{
prefix = null;
if (list == null)
return null;
HttpListener bestMatch = null;
var bestLength = -1;
foreach (var p in list) {
var ppath = p.Path;
if (ppath.Length < bestLength)
continue;
if (path.StartsWith (ppath)) {
bestLength = ppath.Length;
bestMatch = p.Listener;
prefix = p;
}
}
return bestMatch;
}
private static void onAccept (object sender, EventArgs e)
{
var args = (SocketAsyncEventArgs) e;
var epListener = (EndPointListener) args.UserToken;
Socket accepted = null;
if (args.SocketError == SocketError.Success) {
accepted = args.AcceptSocket;
args.AcceptSocket = null;
}
try {
epListener._socket.AcceptAsync (args);
}
catch {
if (accepted != null)
accepted.Close ();
return;
}
if (accepted == null)
return;
HttpConnection conn = null;
try {
conn = new HttpConnection (
accepted, epListener, epListener._secure, epListener._cert);
lock (((ICollection) epListener._unregistered).SyncRoot) {
epListener._unregistered [conn] = conn;
}
conn.BeginReadRequest ();
}
catch {
if (conn != null) {
conn.Close (true);
return;
}
accepted.Close ();
}
}
private static bool removeSpecial (
List<ListenerPrefix> prefixes, ListenerPrefix prefix)
{
if (prefixes == null)
return false;
var count = prefixes.Count;
for (int i = 0; i < count; i++) {
if (prefixes [i].Path == prefix.Path) {
prefixes.RemoveAt (i);
return true;
}
}
return false;
}
private HttpListener searchListener (Uri uri, out ListenerPrefix prefix)
{
prefix = null;
if (uri == null)
return null;
var host = uri.Host;
var port = uri.Port;
var path = HttpUtility.UrlDecode (uri.AbsolutePath);
var pathSlash = path [path.Length - 1] == '/' ? path : path + "/";
HttpListener bestMatch = null;
var bestLength = -1;
if (host != null && host.Length > 0) {
foreach (var p in _prefixes.Keys) {
var ppath = p.Path;
if (ppath.Length < bestLength)
continue;
if (p.Host != host || p.Port != port)
continue;
if (path.StartsWith (ppath) || pathSlash.StartsWith (ppath)) {
bestLength = ppath.Length;
bestMatch = _prefixes [p];
prefix = p;
}
}
if (bestLength != -1)
return bestMatch;
}
var list = _unhandled;
bestMatch = matchFromList (host, path, list, out prefix);
if (path != pathSlash && bestMatch == null)
bestMatch = matchFromList (host, pathSlash, list, out prefix);
if (bestMatch != null)
return bestMatch;
list = _all;
bestMatch = matchFromList (host, path, list, out prefix);
if (path != pathSlash && bestMatch == null)
bestMatch = matchFromList (host, pathSlash, list, out prefix);
if (bestMatch != null)
return bestMatch;
return null;
}
#endregion
#region Internal Methods
internal static bool CertificateExists (int port, string certFolderPath)
{
var cer = Path.Combine (certFolderPath, String.Format ("{0}.cer", port));
var key = Path.Combine (certFolderPath, String.Format ("{0}.key", port));
return File.Exists (cer) && File.Exists (key);
}
internal void RemoveConnection (HttpConnection connection)
{
lock (((ICollection) _unregistered).SyncRoot)
_unregistered.Remove (connection);
}
#endregion
#region Public Methods
public void AddPrefix (ListenerPrefix prefix, HttpListener listener)
{
List<ListenerPrefix> current, future;
if (prefix.Host == "*") {
do {
current = _unhandled;
future = current != null
? new List<ListenerPrefix> (current)
: new List<ListenerPrefix> ();
prefix.Listener = listener;
addSpecial (future, prefix);
}
while (Interlocked.CompareExchange (ref _unhandled, future, current) != current);
return;
}
if (prefix.Host == "+") {
do {
current = _all;
future = current != null
? new List<ListenerPrefix> (current)
: new List<ListenerPrefix> ();
prefix.Listener = listener;
addSpecial (future, prefix);
}
while (Interlocked.CompareExchange (ref _all, future, current) != current);
return;
}
Dictionary<ListenerPrefix, HttpListener> prefs, p2;
do {
prefs = _prefixes;
if (prefs.ContainsKey (prefix)) {
var other = prefs [prefix];
if (other != listener) // TODO: code.
throw new HttpListenerException (
400, "There's another listener for " + prefix);
return;
}
p2 = new Dictionary<ListenerPrefix, HttpListener> (prefs);
p2 [prefix] = listener;
}
while (Interlocked.CompareExchange (ref _prefixes, p2, prefs) != prefs);
}
public bool BindContext (HttpListenerContext context)
{
var req = context.Request;
ListenerPrefix prefix;
var listener = searchListener (req.Url, out prefix);
if (listener == null)
return false;
context.Listener = listener;
context.Connection.Prefix = prefix;
return true;
}
public void Close ()
{
_socket.Close ();
lock (((ICollection) _unregistered).SyncRoot) {
var copy = new Dictionary<HttpConnection, HttpConnection> (_unregistered);
foreach (var conn in copy.Keys)
conn.Close (true);
copy.Clear ();
_unregistered.Clear ();
}
}
public void RemovePrefix (ListenerPrefix prefix, HttpListener listener)
{
List<ListenerPrefix> current, future;
if (prefix.Host == "*") {
do {
current = _unhandled;
future = current != null
? new List<ListenerPrefix> (current)
: new List<ListenerPrefix> ();
if (!removeSpecial (future, prefix))
break; // Prefix not found.
}
while (Interlocked.CompareExchange (ref _unhandled, future, current) != current);
checkIfRemove ();
return;
}
if (prefix.Host == "+") {
do {
current = _all;
future = current != null
? new List<ListenerPrefix> (current)
: new List<ListenerPrefix> ();
if (!removeSpecial (future, prefix))
break; // Prefix not found.
}
while (Interlocked.CompareExchange (ref _all, future, current) != current);
checkIfRemove ();
return;
}
Dictionary<ListenerPrefix, HttpListener> prefs, p2;
do {
prefs = _prefixes;
if (!prefs.ContainsKey (prefix))
break;
p2 = new Dictionary<ListenerPrefix, HttpListener> (prefs);
p2.Remove (prefix);
}
while (Interlocked.CompareExchange (ref _prefixes, p2, prefs) != prefs);
checkIfRemove ();
}
public void UnbindContext (HttpListenerContext context)
{
if (context == null || context.Listener == null)
return;
context.Listener.UnregisterContext (context);
}
#endregion
}
}

@ -0,0 +1,81 @@
#region License
/*
* HttpBasicIdentity.cs
*
* This code is derived from System.Net.HttpListenerBasicIdentity.cs of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2014 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion
using System.Security.Principal;
namespace WebSocketSharp.Net
{
/// <summary>
/// Holds the user name and password from an HTTP Basic authentication request.
/// </summary>
public class HttpBasicIdentity : GenericIdentity
{
#region Private Fields
private string _password;
#endregion
#region internal Constructors
internal HttpBasicIdentity (string username, string password)
: base (username, "Basic")
{
_password = password;
}
#endregion
#region Public Properties
/// <summary>
/// Gets the password from an HTTP Basic authentication request.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the password.
/// </value>
public virtual string Password {
get {
return _password;
}
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,185 @@
#region License
/*
* HttpDigestIdentity.cs
*
* The MIT License
*
* Copyright (c) 2014 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
using System.Collections.Specialized;
using System.Security.Principal;
namespace WebSocketSharp.Net
{
/// <summary>
/// Holds the user name and other authentication parameters from an HTTP Digest
/// authentication request.
/// </summary>
public class HttpDigestIdentity : GenericIdentity
{
#region Private Fields
private NameValueCollection _params;
#endregion
#region Internal Constructors
internal HttpDigestIdentity (NameValueCollection authParams)
: base (authParams ["username"], "Digest")
{
_params = authParams;
}
#endregion
#region Public Properties
/// <summary>
/// Gets the algorithm parameter from an HTTP Digest authentication request.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the algorithm parameter.
/// </value>
public string Algorithm {
get {
return _params ["algorithm"];
}
}
/// <summary>
/// Gets the cnonce parameter from an HTTP Digest authentication request.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the cnonce parameter.
/// </value>
public string Cnonce {
get {
return _params ["cnonce"];
}
}
/// <summary>
/// Gets the nc parameter from an HTTP Digest authentication request.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the nc parameter.
/// </value>
public string Nc {
get {
return _params ["nc"];
}
}
/// <summary>
/// Gets the nonce parameter from an HTTP Digest authentication request.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the nonce parameter.
/// </value>
public string Nonce {
get {
return _params ["nonce"];
}
}
/// <summary>
/// Gets the opaque parameter from an HTTP Digest authentication request.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the opaque parameter.
/// </value>
public string Opaque {
get {
return _params ["opaque"];
}
}
/// <summary>
/// Gets the qop parameter from an HTTP Digest authentication request.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the qop parameter.
/// </value>
public string Qop {
get {
return _params ["qop"];
}
}
/// <summary>
/// Gets the realm parameter from an HTTP Digest authentication request.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the realm parameter.
/// </value>
public string Realm {
get {
return _params ["realm"];
}
}
/// <summary>
/// Gets the response parameter from an HTTP Digest authentication request.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the response parameter.
/// </value>
public string Response {
get {
return _params ["response"];
}
}
/// <summary>
/// Gets the uri parameter from an HTTP Digest authentication request.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the uri parameter.
/// </value>
public string Uri {
get {
return _params ["uri"];
}
}
#endregion
#region Internal Methods
internal bool IsValid (
string password, string realm, string method, string entity)
{
var parameters = new NameValueCollection (_params);
parameters ["password"] = password;
parameters ["realm"] = realm;
parameters ["method"] = method;
parameters ["entity"] = entity;
return _params ["response"] == HttpUtility.CreateRequestDigest (parameters);
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

@ -1,33 +1,40 @@
#region License
//
// HttpListenerContext.cs
// Copied from System.Net.HttpListenerContext.cs
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
// Copyright (c) 2012-2013 sta.blockhead (sta.blockhead@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
/*
* HttpListenerContext.cs
*
* This code is derived from System.Net.HttpListenerContext.cs of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2013 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion
using System;
@ -37,182 +44,184 @@ using System.Security.Principal;
using System.Text;
using WebSocketSharp.Net.WebSockets;
namespace WebSocketSharp.Net {
/// <summary>
/// Provides access to the HTTP request and response objects used by the <see cref="HttpListener"/> class.
/// </summary>
/// <remarks>
/// The HttpListenerContext class cannot be inherited.
/// </remarks>
public sealed class HttpListenerContext {
#region Private Fields
private HttpConnection _connection;
private string _error;
private int _errorStatus;
private HttpListenerRequest _request;
private HttpListenerResponse _response;
private IPrincipal _user;
#endregion
#region Internal Fields
internal HttpListener Listener;
#endregion
#region Constructor
internal HttpListenerContext (HttpConnection connection)
{
_connection = connection;
_errorStatus = 400;
_request = new HttpListenerRequest (this);
_response = new HttpListenerResponse (this);
}
#endregion
#region Internal Properties
internal HttpConnection Connection {
get {
return _connection;
}
}
internal string ErrorMessage {
get {
return _error;
}
set {
_error = value;
}
}
internal int ErrorStatus {
get {
return _errorStatus;
}
set {
_errorStatus = value;
}
}
internal bool HaveError {
get {
return _error != null;
}
}
#endregion
#region Public Properties
/// <summary>
/// Gets the <see cref="HttpListenerRequest"/> that contains the HTTP request from a client.
/// </summary>
/// <value>
/// A <see cref="HttpListenerRequest"/> that contains the HTTP request objects.
/// </value>
public HttpListenerRequest Request {
get {
return _request;
}
}
/// <summary>
/// Gets the <see cref="HttpListenerResponse"/> that contains the HTTP response to send to
/// the client in response to the client's request.
/// </summary>
/// <value>
/// A <see cref="HttpListenerResponse"/> that contains the HTTP response objects.
/// </value>
public HttpListenerResponse Response {
get {
return _response;
}
}
/// <summary>
/// Gets the client information (identity, authentication information and security roles).
/// </summary>
/// <value>
/// A <see cref="IPrincipal"/> contains the client information.
/// </value>
public IPrincipal User {
get {
return _user;
}
}
#endregion
#region Internal Methods
internal void ParseAuthentication (AuthenticationSchemes expectedSchemes)
{
if (expectedSchemes == AuthenticationSchemes.Anonymous)
return;
// TODO: Handle NTLM/Digest modes.
var header = _request.Headers ["Authorization"];
if (header == null || header.Length < 2)
return;
var authData = header.Split (new char [] {' '}, 2);
if (authData [0].ToLower () == "basic")
_user = ParseBasicAuthentication (authData [1]);
// TODO: Throw if malformed -> 400 bad request.
}
internal IPrincipal ParseBasicAuthentication (string authData)
{
try {
// HTTP Basic Authentication data is a formatted Base64 string.
var authString = Encoding.Default.GetString (Convert.FromBase64String (authData));
// The format is domain\username:password.
// Domain is optional.
var pos = authString.IndexOf (':');
var user = authString.Substring (0, pos);
var password = authString.Substring (pos + 1);
// Check if there is a domain.
pos = user.IndexOf ('\\');
if (pos > 0)
user = user.Substring (pos + 1);
var identity = new System.Net.HttpListenerBasicIdentity (user, password);
// TODO: What are the roles MS sets?
return new GenericPrincipal (identity, new string [0]);
} catch {
return null;
}
}
#endregion
#region Public Method
/// <summary>
/// Accepts a WebSocket connection by the <see cref="HttpListener"/>.
/// </summary>
/// <returns>
/// A <see cref="HttpListenerWebSocketContext"/> that contains a WebSocket connection.
/// </returns>
public HttpListenerWebSocketContext AcceptWebSocket ()
{
return new HttpListenerWebSocketContext (this);
}
#endregion
}
namespace WebSocketSharp.Net
{
/// <summary>
/// Provides access to the HTTP request and response information used by the
/// <see cref="HttpListener"/>.
/// </summary>
/// <remarks>
/// The HttpListenerContext class cannot be inherited.
/// </remarks>
public sealed class HttpListenerContext
{
#region Private Fields
private HttpConnection _connection;
private string _error;
private int _errorStatus;
private HttpListenerRequest _request;
private HttpListenerResponse _response;
private IPrincipal _user;
#endregion
#region Internal Fields
internal HttpListener Listener;
#endregion
#region Internal Constructors
internal HttpListenerContext (HttpConnection connection)
{
_connection = connection;
_errorStatus = 400;
_request = new HttpListenerRequest (this);
_response = new HttpListenerResponse (this);
}
#endregion
#region Internal Properties
internal HttpConnection Connection {
get {
return _connection;
}
}
internal string ErrorMessage {
get {
return _error;
}
set {
_error = value;
}
}
internal int ErrorStatus {
get {
return _errorStatus;
}
set {
_errorStatus = value;
}
}
internal bool HaveError {
get {
return _error != null && _error.Length > 0;
}
}
#endregion
#region Public Properties
/// <summary>
/// Gets the <see cref="HttpListenerRequest"/> that contains the HTTP
/// request information from a client.
/// </summary>
/// <value>
/// A <see cref="HttpListenerRequest"/> that contains the HTTP request
/// information.
/// </value>
public HttpListenerRequest Request {
get {
return _request;
}
}
/// <summary>
/// Gets the <see cref="HttpListenerResponse"/> that contains the HTTP
/// response information to send to the client in response to the client's
/// request.
/// </summary>
/// <value>
/// A <see cref="HttpListenerResponse"/> that contains the HTTP response
/// information.
/// </value>
public HttpListenerResponse Response {
get {
return _response;
}
}
/// <summary>
/// Gets the client information (identity, authentication information, and
/// security roles).
/// </summary>
/// <value>
/// A <see cref="IPrincipal"/> contains the client information.
/// </value>
public IPrincipal User {
get {
return _user;
}
}
#endregion
#region Internal Methods
internal void SetUser (
AuthenticationSchemes expectedScheme,
string realm,
Func<IIdentity, NetworkCredential> credentialsFinder)
{
var authRes = AuthenticationResponse.Parse (_request.Headers ["Authorization"]);
if (authRes == null)
return;
var identity = authRes.ToIdentity ();
if (identity == null)
return;
NetworkCredential credentials = null;
try {
credentials = credentialsFinder (identity);
}
catch {
}
if (credentials == null)
return;
var valid = expectedScheme == AuthenticationSchemes.Basic
? ((HttpBasicIdentity) identity).Password == credentials.Password
: expectedScheme == AuthenticationSchemes.Digest
? ((HttpDigestIdentity) identity).IsValid (
credentials.Password, realm, _request.HttpMethod, null)
: false;
if (valid)
_user = new GenericPrincipal (identity, credentials.Roles);
}
#endregion
#region Public Method
/// <summary>
/// Accepts a WebSocket connection request.
/// </summary>
/// <returns>
/// A <see cref="HttpListenerWebSocketContext"/> that contains a WebSocket
/// connection request information.
/// </returns>
/// <param name="logger">
/// A <see cref="Logger"/> that provides the logging functions used in the
/// WebSocket attempts.
/// </param>
public HttpListenerWebSocketContext AcceptWebSocket (Logger logger)
{
return new HttpListenerWebSocketContext (this, logger);
}
#endregion
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,222 +1,200 @@
//
// ListenerAsyncResult.cs
// Copied from System.Net.ListenerAsyncResult.cs
//
// Authors:
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
//
// Copyright (c) 2005 Ximian, Inc (http://www.ximian.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#region License
/*
* ListenerAsyncResult.cs
*
* This code is derived from System.Net.ListenerAsyncResult.cs of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Ximian, Inc. (http://www.ximian.com)
* Copyright (c) 2012-2013 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* Gonzalo Paniagua Javier <gonzalo@ximian.com>
*/
#endregion
using System;
using System.Net;
using System.Threading;
namespace WebSocketSharp.Net {
class ListenerAsyncResult : IAsyncResult
{
#region Private Static Field
static WaitCallback InvokeCB = new WaitCallback (InvokeCallback);
#endregion
#region Private Fields
AsyncCallback cb;
bool completed;
HttpListenerContext context;
Exception exception;
ListenerAsyncResult forward;
ManualResetEvent handle;
object locker;
object state;
bool synch;
#endregion
#region Internal Fields
internal bool EndCalled;
internal bool InGet;
#endregion
#region Constructor
public ListenerAsyncResult (AsyncCallback cb, object state)
{
this.cb = cb;
this.state = state;
this.locker = new object();
}
#endregion
#region Properties
public object AsyncState {
get {
if (forward != null)
return forward.AsyncState;
return state;
}
}
public WaitHandle AsyncWaitHandle {
get {
if (forward != null)
return forward.AsyncWaitHandle;
lock (locker) {
if (handle == null)
handle = new ManualResetEvent (completed);
}
return handle;
}
}
public bool CompletedSynchronously {
get {
if (forward != null)
return forward.CompletedSynchronously;
return synch;
}
}
public bool IsCompleted {
get {
if (forward != null)
return forward.IsCompleted;
lock (locker) {
return completed;
}
}
}
#endregion
#region Private Method
static void InvokeCallback (object o)
{
ListenerAsyncResult ares = (ListenerAsyncResult) o;
if (ares.forward != null) {
InvokeCallback (ares.forward);
return;
}
try {
ares.cb (ares);
} catch {
}
}
#endregion
#region Internal Methods
internal void Complete (Exception exc)
{
if (forward != null) {
forward.Complete (exc);
return;
}
exception = exc;
if (InGet && (exc is ObjectDisposedException))
exception = new HttpListenerException (500, "Listener closed");
lock (locker) {
completed = true;
if (handle != null)
handle.Set ();
if (cb != null)
ThreadPool.UnsafeQueueUserWorkItem (InvokeCB, this);
}
}
internal void Complete (HttpListenerContext context)
{
Complete (context, false);
}
internal void Complete (HttpListenerContext context, bool synch)
{
if (forward != null) {
forward.Complete (context, synch);
return;
}
this.synch = synch;
this.context = context;
lock (locker) {
AuthenticationSchemes schemes = context.Listener.SelectAuthenticationScheme (context);
if ((schemes == AuthenticationSchemes.Basic || context.Listener.AuthenticationSchemes == AuthenticationSchemes.Negotiate) && context.Request.Headers ["Authorization"] == null) {
context.Response.StatusCode = 401;
context.Response.Headers ["WWW-Authenticate"] = schemes + " realm=\"" + context.Listener.Realm + "\"";
context.Response.OutputStream.Close ();
IAsyncResult ares = context.Listener.BeginGetContext (cb, state);
this.forward = (ListenerAsyncResult) ares;
lock (forward.locker) {
if (handle != null)
forward.handle = handle;
}
ListenerAsyncResult next = forward;
for (int i = 0; next.forward != null; i++) {
if (i > 20)
Complete (new HttpListenerException (400, "Too many authentication errors"));
next = next.forward;
}
} else {
completed = true;
if (handle != null)
handle.Set ();
if (cb != null)
ThreadPool.UnsafeQueueUserWorkItem (InvokeCB, this);
}
}
}
internal HttpListenerContext GetContext ()
{
if (forward != null)
return forward.GetContext ();
if (exception != null)
throw exception;
return context;
}
#endregion
}
namespace WebSocketSharp.Net
{
internal class ListenerAsyncResult : IAsyncResult
{
#region Private Fields
private AsyncCallback _callback;
private bool _completed;
private HttpListenerContext _context;
private Exception _exception;
private ManualResetEvent _waitHandle;
private object _state;
private object _sync;
private bool _syncCompleted;
#endregion
#region Internal Fields
internal bool EndCalled;
internal bool InGet;
#endregion
#region Public Constructors
public ListenerAsyncResult (AsyncCallback callback, object state)
{
_callback = callback;
_state = state;
_sync = new object ();
}
#endregion
#region Public Properties
public object AsyncState {
get {
return _state;
}
}
public WaitHandle AsyncWaitHandle {
get {
lock (_sync)
return _waitHandle ?? (_waitHandle = new ManualResetEvent (_completed));
}
}
public bool CompletedSynchronously {
get {
return _syncCompleted;
}
}
public bool IsCompleted {
get {
lock (_sync)
return _completed;
}
}
#endregion
#region Private Methods
private static void invokeCallback (object state)
{
try {
var ares = (ListenerAsyncResult) state;
ares._callback (ares);
}
catch {
}
}
#endregion
#region Internal Methods
internal void Complete (Exception exception)
{
_exception = InGet && (exception is ObjectDisposedException)
? new HttpListenerException (500, "Listener closed")
: exception;
lock (_sync) {
_completed = true;
if (_waitHandle != null)
_waitHandle.Set ();
if (_callback != null)
ThreadPool.UnsafeQueueUserWorkItem (invokeCallback, this);
}
}
internal void Complete (HttpListenerContext context)
{
Complete (context, false);
}
internal void Complete (HttpListenerContext context, bool syncCompleted)
{
var listener = context.Listener;
var scheme = listener.SelectAuthenticationScheme (context);
if (scheme == AuthenticationSchemes.None) {
context.Response.Close (HttpStatusCode.Forbidden);
listener.BeginGetContext (this);
return;
}
var header = context.Request.Headers ["Authorization"];
if (scheme == AuthenticationSchemes.Basic &&
(header == null ||
!header.StartsWith ("basic", StringComparison.OrdinalIgnoreCase))) {
context.Response.CloseWithAuthChallenge (
HttpUtility.CreateBasicAuthChallenge (listener.Realm));
listener.BeginGetContext (this);
return;
}
if (scheme == AuthenticationSchemes.Digest &&
(header == null ||
!header.StartsWith ("digest", StringComparison.OrdinalIgnoreCase))) {
context.Response.CloseWithAuthChallenge (
HttpUtility.CreateDigestAuthChallenge (listener.Realm));
listener.BeginGetContext (this);
return;
}
_context = context;
_syncCompleted = syncCompleted;
lock (_sync) {
_completed = true;
if (_waitHandle != null)
_waitHandle.Set ();
if (_callback != null)
ThreadPool.UnsafeQueueUserWorkItem (invokeCallback, this);
}
}
internal HttpListenerContext GetContext ()
{
if (_exception != null)
throw _exception;
return _context;
}
#endregion
}
}

@ -0,0 +1,179 @@
#region License
/*
* NetworkCredential.cs
*
* The MIT License
*
* Copyright (c) 2014 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
namespace WebSocketSharp.Net
{
/// <summary>
/// Provides the credentials for HTTP authentication (Basic/Digest).
/// </summary>
public class NetworkCredential
{
#region Private Fields
private string _domain;
private string _password;
private string [] _roles;
private string _username;
#endregion
#region Public Constructors
/// <summary>
/// Initializes a new instance of the <see cref="NetworkCredential"/> class
/// with the specified user name and password.
/// </summary>
/// <param name="username">
/// A <see cref="string"/> that represents the user name associated with the
/// credentials.
/// </param>
/// <param name="password">
/// A <see cref="string"/> that represents the password for the user name
/// associated with the credentials.
/// </param>
/// <exception cref="ArgumentException">
/// <paramref name="username"/> is <see langword="null"/> or empty.
/// </exception>
public NetworkCredential (string username, string password)
: this (username, password, null, new string [0])
{
}
/// <summary>
/// Initializes a new instance of the <see cref="NetworkCredential"/> class
/// with the specified user name, password, domain, and roles.
/// </summary>
/// <param name="username">
/// A <see cref="string"/> that represents the user name associated with the
/// credentials.
/// </param>
/// <param name="password">
/// A <see cref="string"/> that represents the password for the user name
/// associated with the credentials.
/// </param>
/// <param name="domain">
/// A <see cref="string"/> that represents the name of the user domain
/// associated with the credentials.
/// </param>
/// <param name="roles">
/// An array of <see cref="string"/> that contains the role names to which
/// the user associated with the credentials belongs if any.
/// </param>
/// <exception cref="ArgumentException">
/// <paramref name="username"/> is <see langword="null"/> or empty.
/// </exception>
public NetworkCredential (
string username, string password, string domain, params string [] roles)
{
if (username == null || username.Length == 0)
throw new ArgumentException ("Must not be null or empty.", "username");
_username = username;
_password = password;
_domain = domain;
_roles = roles;
}
#endregion
#region Public Properties
/// <summary>
/// Gets the name of the user domain associated with the credentials.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the name of the user domain
/// associated with the credentials.
/// </value>
public string Domain {
get {
return _domain ?? String.Empty;
}
internal set {
_domain = value;
}
}
/// <summary>
/// Gets the password for the user name associated with the credentials.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the password for the user name
/// associated with the credentials.
/// </value>
public string Password {
get {
return _password ?? String.Empty;
}
internal set {
_password = value;
}
}
/// <summary>
/// Gets the role names to which the user associated with the credentials
/// belongs.
/// </summary>
/// <value>
/// An array of <see cref="string"/> that contains the role names to which
/// the user associated with the credentials belongs.
/// </value>
public string [] Roles {
get {
return _roles;
}
internal set {
_roles = value;
}
}
/// <summary>
/// Gets the user name associated with the credentials.
/// </summary>
/// <value>
/// A <see cref="string"/> that represents the user name associated with the
/// credentials.
/// </value>
public string UserName {
get {
return _username;
}
internal set {
_username = value;
}
}
#endregion
}
}

@ -1,253 +1,281 @@
//
// RequestStream.cs
// Copied from System.Net.RequestStream.cs
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#region License
/*
* RequestStream.cs
*
* This code is derived from System.Net.RequestStream.cs of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2013 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion
using System;
using System.IO;
using System.Runtime.InteropServices;
namespace WebSocketSharp.Net {
class RequestStream : Stream {
#region Fields
byte [] buffer;
bool disposed;
int length;
int offset;
long remaining_body;
Stream stream;
#endregion
#region Constructors
internal RequestStream (Stream stream, byte [] buffer, int offset, int length)
: this (stream, buffer, offset, length, -1)
{
}
internal RequestStream (Stream stream, byte [] buffer, int offset, int length, long contentlength)
{
this.stream = stream;
this.buffer = buffer;
this.offset = offset;
this.length = length;
this.remaining_body = contentlength;
}
#endregion
#region Properties
public override bool CanRead {
get { return true; }
}
public override bool CanSeek {
get { return false; }
}
public override bool CanWrite {
get { return false; }
}
public override long Length {
get { throw new NotSupportedException (); }
}
public override long Position {
get { throw new NotSupportedException (); }
set { throw new NotSupportedException (); }
}
#endregion
#region Private Method
// Returns 0 if we can keep reading from the base stream,
// > 0 if we read something from the buffer.
// -1 if we had a content length set and we finished reading that many bytes.
int FillFromBuffer (byte [] buffer, int offset, int count)
{
if (buffer == null)
throw new ArgumentNullException ("buffer");
if (offset < 0)
throw new ArgumentOutOfRangeException ("offset", "< 0");
if (count < 0)
throw new ArgumentOutOfRangeException ("count", "< 0");
int len = buffer.Length;
if (offset > len)
throw new ArgumentException ("Destination offset is beyond array size.");
if (offset > len - count)
throw new ArgumentException ("Reading would overrun buffer.");
if (this.remaining_body == 0)
return -1;
if (this.length == 0)
return 0;
int size = Math.Min (this.length, count);
if (this.remaining_body > 0)
size = (int) Math.Min (size, this.remaining_body);
if (this.offset > this.buffer.Length - size) {
size = Math.Min (size, this.buffer.Length - this.offset);
}
if (size == 0)
return 0;
Buffer.BlockCopy (this.buffer, this.offset, buffer, offset, size);
this.offset += size;
this.length -= size;
if (this.remaining_body > 0)
remaining_body -= size;
return size;
}
#endregion
#region Public Methods
public override IAsyncResult BeginRead (
byte [] buffer, int offset, int count, AsyncCallback cback, object state)
{
if (disposed)
throw new ObjectDisposedException (GetType ().ToString ());
int nread = FillFromBuffer (buffer, offset, count);
if (nread > 0 || nread == -1) {
var ares = new HttpStreamAsyncResult ();
ares.Buffer = buffer;
ares.Offset = offset;
ares.Count = count;
ares.Callback = cback;
ares.State = state;
ares.SyncRead = nread;
ares.Complete ();
return ares;
}
// Avoid reading past the end of the request to allow
// for HTTP pipelining
if (remaining_body >= 0 && count > remaining_body)
count = (int) Math.Min (Int32.MaxValue, remaining_body);
return stream.BeginRead (buffer, offset, count, cback, state);
}
public override IAsyncResult BeginWrite (
byte [] buffer, int offset, int count, AsyncCallback cback, object state)
{
throw new NotSupportedException ();
}
public override void Close ()
{
disposed = true;
}
public override int EndRead (IAsyncResult ares)
{
if (disposed)
throw new ObjectDisposedException (GetType ().ToString ());
if (ares == null)
throw new ArgumentNullException ("ares");
if (ares is HttpStreamAsyncResult) {
var ares_ = (HttpStreamAsyncResult) ares;
if (!ares.IsCompleted)
ares.AsyncWaitHandle.WaitOne ();
return ares_.SyncRead;
}
// Close on exception?
int nread = stream.EndRead (ares);
if (remaining_body > 0 && nread > 0)
remaining_body -= nread;
return nread;
}
public override void EndWrite (IAsyncResult async_result)
{
throw new NotSupportedException ();
}
public override void Flush ()
{
}
public override int Read ([In,Out] byte[] buffer, int offset, int count)
{
if (disposed)
throw new ObjectDisposedException (GetType () .ToString ());
// Call FillFromBuffer to check for buffer boundaries even when remaining_body is 0
int nread = FillFromBuffer (buffer, offset, count);
if (nread == -1) { // No more bytes available (Content-Length)
return 0;
} else if (nread > 0) {
return nread;
}
nread = stream.Read (buffer, offset, count);
if (nread > 0 && remaining_body > 0)
remaining_body -= nread;
return nread;
}
public override long Seek (long offset, SeekOrigin origin)
{
throw new NotSupportedException ();
}
public override void SetLength (long value)
{
throw new NotSupportedException ();
}
public override void Write (byte[] buffer, int offset, int count)
{
throw new NotSupportedException ();
}
#endregion
}
namespace WebSocketSharp.Net
{
internal class RequestStream : Stream
{
#region Private Fields
private byte [] _buffer;
private bool _disposed;
private int _length;
private int _offset;
private long _remainingBody;
private Stream _stream;
#endregion
#region Internal Constructors
internal RequestStream (
Stream stream, byte [] buffer, int offset, int length)
: this (stream, buffer, offset, length, -1)
{
}
internal RequestStream (
Stream stream, byte [] buffer, int offset, int length, long contentlength)
{
_stream = stream;
_buffer = buffer;
_offset = offset;
_length = length;
_remainingBody = contentlength;
}
#endregion
#region Public Properties
public override bool CanRead {
get {
return true;
}
}
public override bool CanSeek {
get {
return false;
}
}
public override bool CanWrite {
get {
return false;
}
}
public override long Length {
get {
throw new NotSupportedException ();
}
}
public override long Position {
get {
throw new NotSupportedException ();
}
set {
throw new NotSupportedException ();
}
}
#endregion
#region Private Methods
// Returns 0 if we can keep reading from the base stream,
// > 0 if we read something from the buffer.
// -1 if we had a content length set and we finished reading that many bytes.
private int fillFromBuffer (byte [] buffer, int offset, int count)
{
if (buffer == null)
throw new ArgumentNullException ("buffer");
if (offset < 0)
throw new ArgumentOutOfRangeException ("offset", "Less than zero.");
if (count < 0)
throw new ArgumentOutOfRangeException ("count", "Less than zero.");
var len = buffer.Length;
if (offset > len)
throw new ArgumentException ("'offset' is greater than 'buffer' size.");
if (offset > len - count)
throw new ArgumentException ("Reading would overrun 'buffer'.");
if (_remainingBody == 0)
return -1;
if (_length == 0)
return 0;
var size = _length < count ? _length : count;
if (_remainingBody > 0 && _remainingBody < size)
size = (int) _remainingBody;
var remainingBuffer = _buffer.Length - _offset;
if (remainingBuffer < size)
size = remainingBuffer;
if (size == 0)
return 0;
Buffer.BlockCopy (_buffer, _offset, buffer, offset, size);
_offset += size;
_length -= size;
if (_remainingBody > 0)
_remainingBody -= size;
return size;
}
#endregion
#region Public Methods
public override IAsyncResult BeginRead (
byte [] buffer,
int offset,
int count,
AsyncCallback callback,
object state)
{
if (_disposed)
throw new ObjectDisposedException (GetType ().ToString ());
var read = fillFromBuffer (buffer, offset, count);
if (read > 0 || read == -1) {
var ares = new HttpStreamAsyncResult ();
ares.Buffer = buffer;
ares.Offset = offset;
ares.Count = count;
ares.Callback = callback;
ares.State = state;
ares.SyncRead = read;
ares.Complete ();
return ares;
}
// Avoid reading past the end of the request to allow for HTTP pipelining.
if (_remainingBody >= 0 && _remainingBody < count)
count = (int) _remainingBody;
return _stream.BeginRead (buffer, offset, count, callback, state);
}
public override IAsyncResult BeginWrite (
byte [] buffer, int offset, int count, AsyncCallback callback, object state)
{
throw new NotSupportedException ();
}
public override void Close ()
{
_disposed = true;
}
public override int EndRead (IAsyncResult asyncResult)
{
if (_disposed)
throw new ObjectDisposedException (GetType ().ToString ());
if (asyncResult == null)
throw new ArgumentNullException ("asyncResult");
if (asyncResult is HttpStreamAsyncResult) {
var ares = (HttpStreamAsyncResult) asyncResult;
if (!ares.IsCompleted)
ares.AsyncWaitHandle.WaitOne ();
return ares.SyncRead;
}
// Close on exception?
var read = _stream.EndRead (asyncResult);
if (read > 0 && _remainingBody > 0)
_remainingBody -= read;
return read;
}
public override void EndWrite (IAsyncResult asyncResult)
{
throw new NotSupportedException ();
}
public override void Flush ()
{
}
public override int Read (byte [] buffer, int offset, int count)
{
if (_disposed)
throw new ObjectDisposedException (GetType ().ToString ());
// Call fillFromBuffer to check for buffer boundaries even when
// _remainingBody is 0.
var read = fillFromBuffer (buffer, offset, count);
if (read == -1) // No more bytes available (Content-Length).
return 0;
else if (read > 0)
return read;
read = _stream.Read (buffer, offset, count);
if (read > 0 && _remainingBody > 0)
_remainingBody -= read;
return read;
}
public override long Seek (long offset, SeekOrigin origin)
{
throw new NotSupportedException ();
}
public override void SetLength (long value)
{
throw new NotSupportedException ();
}
public override void Write (byte [] buffer, int offset, int count)
{
throw new NotSupportedException ();
}
#endregion
}
}

@ -1,284 +1,322 @@
//
// ResponseStream.cs
// Copied from System.Net.ResponseStream.cs
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#region License
/*
* ResponseStream.cs
*
* This code is derived from System.Net.ResponseStream.cs of Mono
* (http://www.mono-project.com).
*
* The MIT License
*
* Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
* Copyright (c) 2012-2014 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Authors
/*
* Authors:
* Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Runtime.InteropServices;
namespace WebSocketSharp.Net {
// FIXME: Does this buffer the response until Close?
// Update: we send a single packet for the first non-chunked Write
// What happens when we set content-length to X and write X-1 bytes then close?
// what if we don't set content-length at all?
class ResponseStream : Stream {
#region Private Static Field
static byte [] crlf = new byte [] { 13, 10 };
#endregion
#region Private Fields
bool disposed;
bool ignore_errors;
HttpListenerResponse response;
Stream stream;
bool trailer_sent;
#endregion
#region Constructor
internal ResponseStream (System.IO.Stream stream, HttpListenerResponse response, bool ignore_errors)
{
this.stream = stream;
this.response = response;
this.ignore_errors = ignore_errors;
}
#endregion
#region Properties
public override bool CanRead {
get { return false; }
}
public override bool CanSeek {
get { return false; }
}
public override bool CanWrite {
get { return true; }
}
public override long Length {
get { throw new NotSupportedException (); }
}
public override long Position {
get { throw new NotSupportedException (); }
set { throw new NotSupportedException (); }
}
#endregion
#region Private Methods
static byte [] GetChunkSizeBytes (int size, bool final)
{
string str = String.Format ("{0:x}\r\n{1}", size, final ? "\r\n" : "");
return Encoding.ASCII.GetBytes (str);
}
MemoryStream GetHeaders (bool closing)
{
if (response.HeadersSent)
return null;
MemoryStream ms = new MemoryStream ();
response.SendHeaders (closing, ms);
return ms;
}
#endregion
#region Internal Method
internal void InternalWrite (byte [] buffer, int offset, int count)
{
if (ignore_errors) {
try {
stream.Write (buffer, offset, count);
} catch {
}
} else {
stream.Write (buffer, offset, count);
}
}
#endregion
#region Public Methods
public override IAsyncResult BeginRead (
byte [] buffer,
int offset,
int count,
AsyncCallback cback,
object state)
{
throw new NotSupportedException ();
}
public override IAsyncResult BeginWrite (
byte [] buffer,
int offset,
int count,
AsyncCallback cback,
object state)
{
if (disposed)
throw new ObjectDisposedException (GetType ().ToString ());
byte [] bytes = null;
MemoryStream ms = GetHeaders (false);
bool chunked = response.SendChunked;
if (ms != null) {
long start = ms.Position;
ms.Position = ms.Length;
if (chunked) {
bytes = GetChunkSizeBytes (count, false);
ms.Write (bytes, 0, bytes.Length);
}
ms.Write (buffer, offset, count);
buffer = ms.GetBuffer ();
offset = (int) start;
count = (int) (ms.Position - start);
} else if (chunked) {
bytes = GetChunkSizeBytes (count, false);
InternalWrite (bytes, 0, bytes.Length);
}
return stream.BeginWrite (buffer, offset, count, cback, state);
}
public override void Close ()
{
if (disposed == false) {
disposed = true;
byte [] bytes = null;
MemoryStream ms = GetHeaders (true);
bool chunked = response.SendChunked;
if (ms != null) {
long start = ms.Position;
if (chunked && !trailer_sent) {
bytes = GetChunkSizeBytes (0, true);
ms.Position = ms.Length;
ms.Write (bytes, 0, bytes.Length);
}
InternalWrite (ms.GetBuffer (), (int) start, (int) (ms.Length - start));
trailer_sent = true;
} else if (chunked && !trailer_sent) {
bytes = GetChunkSizeBytes (0, true);
InternalWrite (bytes, 0, bytes.Length);
trailer_sent = true;
}
response.Close ();
}
}
public override int EndRead (IAsyncResult ares)
{
throw new NotSupportedException ();
}
public override void EndWrite (IAsyncResult ares)
{
if (disposed)
throw new ObjectDisposedException (GetType ().ToString ());
if (ignore_errors) {
try {
stream.EndWrite (ares);
if (response.SendChunked)
stream.Write (crlf, 0, 2);
} catch {
}
} else {
stream.EndWrite (ares);
if (response.SendChunked)
stream.Write (crlf, 0, 2);
}
}
public override void Flush ()
{
}
public override int Read ([In,Out] byte[] buffer, int offset, int count)
{
throw new NotSupportedException ();
}
public override long Seek (long offset, SeekOrigin origin)
{
throw new NotSupportedException ();
}
public override void SetLength (long value)
{
throw new NotSupportedException ();
}
public override void Write (byte [] buffer, int offset, int count)
{
if (disposed)
throw new ObjectDisposedException (GetType ().ToString ());
byte [] bytes = null;
MemoryStream ms = GetHeaders (false);
bool chunked = response.SendChunked;
if (ms != null) {
long start = ms.Position; // After the possible preamble for the encoding
ms.Position = ms.Length;
if (chunked) {
bytes = GetChunkSizeBytes (count, false);
ms.Write (bytes, 0, bytes.Length);
}
int new_count = Math.Min (count, 16384 - (int) ms.Position + (int) start);
ms.Write (buffer, offset, new_count);
count -= new_count;
offset += new_count;
InternalWrite (ms.GetBuffer (), (int) start, (int) (ms.Length - start));
ms.SetLength (0);
ms.Capacity = 0; // 'dispose' the buffer in ms.
} else if (chunked) {
bytes = GetChunkSizeBytes (count, false);
InternalWrite (bytes, 0, bytes.Length);
}
if (count > 0)
InternalWrite (buffer, offset, count);
if (chunked)
InternalWrite (crlf, 0, 2);
}
#endregion
}
namespace WebSocketSharp.Net
{
// FIXME: Does this buffer the response until Close?
// Update: we send a single packet for the first non-chunked Write
// What happens when we set content-length to X and write X-1 bytes then close?
// what if we don't set content-length at all?
internal class ResponseStream : Stream
{
#region Private Static Fields
private static byte [] _crlf = new byte [] { 13, 10 };
#endregion
#region Private Fields
private bool _disposed;
private bool _ignoreErrors;
private HttpListenerResponse _response;
private Stream _stream;
private bool _trailerSent;
#endregion
#region Internal Constructors
internal ResponseStream (
Stream stream, HttpListenerResponse response, bool ignoreErrors)
{
_stream = stream;
_response = response;
_ignoreErrors = ignoreErrors;
}
#endregion
#region Public Properties
public override bool CanRead {
get {
return false;
}
}
public override bool CanSeek {
get {
return false;
}
}
public override bool CanWrite {
get {
return true;
}
}
public override long Length {
get {
throw new NotSupportedException ();
}
}
public override long Position {
get {
throw new NotSupportedException ();
}
set {
throw new NotSupportedException ();
}
}
#endregion
#region Private Methods
private static byte [] getChunkSizeBytes (int size, bool final)
{
return Encoding.ASCII.GetBytes (
String.Format ("{0:x}\r\n{1}", size, final ? "\r\n" : ""));
}
private MemoryStream getHeaders (bool closing)
{
if (_response.HeadersSent)
return null;
var stream = new MemoryStream ();
_response.SendHeaders (closing, stream);
return stream;
}
#endregion
#region Internal Methods
internal void InternalWrite (byte [] buffer, int offset, int count)
{
if (_ignoreErrors) {
try {
_stream.Write (buffer, offset, count);
}
catch {
}
}
else {
_stream.Write (buffer, offset, count);
}
}
#endregion
#region Public Methods
public override IAsyncResult BeginRead (
byte [] buffer,
int offset,
int count,
AsyncCallback callback,
object state)
{
throw new NotSupportedException ();
}
public override IAsyncResult BeginWrite (
byte [] buffer,
int offset,
int count,
AsyncCallback callback,
object state)
{
if (_disposed)
throw new ObjectDisposedException (GetType ().ToString ());
var stream = getHeaders (false);
var chunked = _response.SendChunked;
byte [] bytes = null;
if (stream != null) {
var start = stream.Position;
stream.Position = stream.Length;
if (chunked) {
bytes = getChunkSizeBytes (count, false);
stream.Write (bytes, 0, bytes.Length);
}
stream.Write (buffer, offset, count);
buffer = stream.GetBuffer ();
offset = (int) start;
count = (int) (stream.Position - start);
}
else if (chunked) {
bytes = getChunkSizeBytes (count, false);
InternalWrite (bytes, 0, bytes.Length);
}
return _stream.BeginWrite (buffer, offset, count, callback, state);
}
public override void Close ()
{
if (_disposed)
return;
_disposed = true;
var stream = getHeaders (true);
var chunked = _response.SendChunked;
byte [] bytes = null;
if (stream != null) {
var start = stream.Position;
if (chunked && !_trailerSent) {
bytes = getChunkSizeBytes (0, true);
stream.Position = stream.Length;
stream.Write (bytes, 0, bytes.Length);
}
InternalWrite (
stream.GetBuffer (), (int) start, (int) (stream.Length - start));
_trailerSent = true;
}
else if (chunked && !_trailerSent) {
bytes = getChunkSizeBytes (0, true);
InternalWrite (bytes, 0, bytes.Length);
_trailerSent = true;
}
_response.Close ();
}
public override int EndRead (IAsyncResult asyncResult)
{
throw new NotSupportedException ();
}
public override void EndWrite (IAsyncResult asyncResult)
{
if (_disposed)
throw new ObjectDisposedException (GetType ().ToString ());
Action<IAsyncResult> endWrite = ares => {
_stream.EndWrite (ares);
if (_response.SendChunked)
_stream.Write (_crlf, 0, 2);
};
if (_ignoreErrors) {
try {
endWrite (asyncResult);
}
catch {
}
}
else {
endWrite (asyncResult);
}
}
public override void Flush ()
{
}
public override int Read (byte [] buffer, int offset, int count)
{
throw new NotSupportedException ();
}
public override long Seek (long offset, SeekOrigin origin)
{
throw new NotSupportedException ();
}
public override void SetLength (long value)
{
throw new NotSupportedException ();
}
public override void Write (byte [] buffer, int offset, int count)
{
if (_disposed)
throw new ObjectDisposedException (GetType ().ToString ());
var stream = getHeaders (false);
var chunked = _response.SendChunked;
byte [] bytes = null;
if (stream != null) {
// After the possible preamble for the encoding.
var start = stream.Position;
stream.Position = stream.Length;
if (chunked) {
bytes = getChunkSizeBytes (count, false);
stream.Write (bytes, 0, bytes.Length);
}
var newCount = Math.Min (
count, 16384 - (int) stream.Position + (int) start);
stream.Write (buffer, offset, newCount);
count -= newCount;
offset += newCount;
InternalWrite (
stream.GetBuffer (), (int) start, (int) (stream.Length - start));
stream.SetLength (0);
stream.Capacity = 0; // 'dispose' the buffer in stream.
}
else if (chunked) {
bytes = getChunkSizeBytes (count, false);
InternalWrite (bytes, 0, bytes.Length);
}
if (count > 0)
InternalWrite (buffer, offset, count);
if (chunked)
InternalWrite (_crlf, 0, 2);
}
#endregion
}
}

@ -34,7 +34,8 @@ using System.Security.Principal;
namespace WebSocketSharp.Net.WebSockets
{
/// <summary>
/// Provides access to the WebSocket connection request objects received by the <see cref="HttpListener"/>.
/// Provides access to the WebSocket connection request information received by
/// the <see cref="HttpListener"/>.
/// </summary>
/// <remarks>
/// </remarks>
@ -50,11 +51,12 @@ namespace WebSocketSharp.Net.WebSockets
#region Internal Constructors
internal HttpListenerWebSocketContext (HttpListenerContext context)
internal HttpListenerWebSocketContext (
HttpListenerContext context, Logger logger)
{
_context = context;
_stream = WsStream.CreateServerStream (context);
_websocket = new WebSocket (this);
_websocket = new WebSocket (this, logger ?? new Logger ());
}
#endregion
@ -72,10 +74,11 @@ namespace WebSocketSharp.Net.WebSockets
#region Public Properties
/// <summary>
/// Gets the cookies used in the WebSocket opening handshake.
/// Gets the cookies used in the WebSocket connection request.
/// </summary>
/// <value>
/// A <see cref="WebSocketSharp.Net.CookieCollection"/> that contains the cookies.
/// A <see cref="WebSocketSharp.Net.CookieCollection"/> that contains the
/// cookies.
/// </value>
public override CookieCollection CookieCollection {
get {
@ -84,10 +87,10 @@ namespace WebSocketSharp.Net.WebSockets
}
/// <summary>
/// Gets the HTTP headers used in the WebSocket opening handshake.
/// Gets the HTTP headers used in the WebSocket connection request.
/// </summary>
/// <value>
/// A <see cref="System.Collections.Specialized.NameValueCollection"/> that contains the HTTP headers.
/// A <see cref="NameValueCollection"/> that contains the HTTP headers.
/// </value>
public override NameValueCollection Headers {
get {
@ -96,10 +99,11 @@ namespace WebSocketSharp.Net.WebSockets
}
/// <summary>
/// Gets the value of the Host header field used in the WebSocket opening handshake.
/// Gets the value of the Host header field used in the WebSocket connection
/// request.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the value of the Host header field.
/// A <see cref="string"/> that represents the value of the Host header field.
/// </value>
public override string Host {
get {
@ -120,10 +124,12 @@ namespace WebSocketSharp.Net.WebSockets
}
/// <summary>
/// Gets a value indicating whether the client connected from the local computer.
/// Gets a value indicating whether the client connected from the local
/// computer.
/// </summary>
/// <value>
/// <c>true</c> if the client connected from the local computer; otherwise, <c>false</c>.
/// <c>true</c> if the client connected from the local computer; otherwise,
/// <c>false</c>.
/// </value>
public override bool IsLocal {
get {
@ -135,7 +141,8 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets a value indicating whether the WebSocket connection is secured.
/// </summary>
/// <value>
/// <c>true</c> if the WebSocket connection is secured; otherwise, <c>false</c>.
/// <c>true</c> if the WebSocket connection is secured; otherwise,
/// <c>false</c>.
/// </value>
public override bool IsSecureConnection {
get {
@ -144,10 +151,12 @@ namespace WebSocketSharp.Net.WebSockets
}
/// <summary>
/// Gets a value indicating whether the request is a WebSocket connection request.
/// Gets a value indicating whether the request is a WebSocket connection
/// request.
/// </summary>
/// <value>
/// <c>true</c> if the request is a WebSocket connection request; otherwise, <c>false</c>.
/// <c>true</c> if the request is a WebSocket connection request; otherwise,
/// <c>false</c>.
/// </value>
public override bool IsWebSocketRequest {
get {
@ -156,10 +165,12 @@ namespace WebSocketSharp.Net.WebSockets
}
/// <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
/// connection request.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the value of the Origin header field.
/// A <see cref="string"/> that represents the value of the Origin header
/// field.
/// </value>
public override string Origin {
get {
@ -171,19 +182,22 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets the absolute path of the requested WebSocket URI.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the absolute path of the requested WebSocket URI.
/// A <see cref="string"/> that represents the absolute path of the requested
/// WebSocket URI.
/// </value>
public override string Path {
get {
return RequestUri.GetAbsolutePath ();
return _context.Request.Url.GetAbsolutePath ();
}
}
/// <summary>
/// Gets the collection of query string variables used in the WebSocket opening handshake.
/// Gets the collection of query string variables used in the WebSocket
/// connection request.
/// </summary>
/// <value>
/// A <see cref="NameValueCollection"/> that contains the collection of query string variables.
/// A <see cref="NameValueCollection"/> that contains the collection of query
/// string variables.
/// </value>
public override NameValueCollection QueryString {
get {
@ -195,22 +209,26 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets the WebSocket URI requested by the client.
/// </summary>
/// <value>
/// A <see cref="Uri"/> that contains the WebSocket URI.
/// A <see cref="Uri"/> that represents the WebSocket URI requested by the
/// client.
/// </value>
public override Uri RequestUri {
get {
return _context.Request.RawUrl.ToUri ();
return _context.Request.Url;
}
}
/// <summary>
/// Gets the value of the Sec-WebSocket-Key header field used in the WebSocket opening handshake.
/// Gets the value of the Sec-WebSocket-Key header field used in the
/// WebSocket connection request.
/// </summary>
/// <remarks>
/// The SecWebSocketKey property provides a part of the information used by the server to prove that it received a valid WebSocket opening handshake.
/// This property provides a part of the information used by the server to
/// prove that it received a valid WebSocket connection request.
/// </remarks>
/// <value>
/// A <see cref="string"/> that contains the value of the Sec-WebSocket-Key header field.
/// A <see cref="string"/> that represents the value of the Sec-WebSocket-Key
/// header field.
/// </value>
public override string SecWebSocketKey {
get {
@ -219,13 +237,15 @@ namespace WebSocketSharp.Net.WebSockets
}
/// <summary>
/// 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 connection request.
/// </summary>
/// <remarks>
/// The SecWebSocketProtocols property indicates the subprotocols of the WebSocket connection.
/// This property represents the subprotocols of the WebSocket connection.
/// </remarks>
/// <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>
public override IEnumerable<string> SecWebSocketProtocols {
get {
@ -234,13 +254,15 @@ namespace WebSocketSharp.Net.WebSockets
}
/// <summary>
/// Gets the value of the Sec-WebSocket-Version header field used in the WebSocket opening handshake.
/// Gets the value of the Sec-WebSocket-Version header field used in the
/// WebSocket connection request.
/// </summary>
/// <remarks>
/// The SecWebSocketVersion property indicates the WebSocket protocol version of the connection.
/// This property represents the WebSocket protocol version of the connection.
/// </remarks>
/// <value>
/// A <see cref="string"/> that contains the value of the Sec-WebSocket-Version header field.
/// A <see cref="string"/> that represents the value of the
/// Sec-WebSocket-Version header field.
/// </value>
public override string SecWebSocketVersion {
get {
@ -252,7 +274,7 @@ namespace WebSocketSharp.Net.WebSockets
/// 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.
/// A <see cref="System.Net.IPEndPoint"/> that represents the server endpoint.
/// </value>
public override System.Net.IPEndPoint ServerEndPoint {
get {
@ -261,10 +283,11 @@ namespace WebSocketSharp.Net.WebSockets
}
/// <summary>
/// Gets the client information (identity, authentication information and security roles).
/// Gets the client information (identity, authentication information and
/// security roles).
/// </summary>
/// <value>
/// A <see cref="IPrincipal"/> that contains the client information.
/// A <see cref="IPrincipal"/> that represents the client information.
/// </value>
public override IPrincipal User {
get {
@ -276,7 +299,7 @@ namespace WebSocketSharp.Net.WebSockets
/// 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.
/// A <see cref="System.Net.IPEndPoint"/> that represents the client endpoint.
/// </value>
public override System.Net.IPEndPoint UserEndPoint {
get {
@ -285,7 +308,8 @@ namespace WebSocketSharp.Net.WebSockets
}
/// <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>
/// <value>
/// A <see cref="WebSocketSharp.WebSocket"/>.
@ -305,15 +329,22 @@ namespace WebSocketSharp.Net.WebSockets
_context.Connection.Close (true);
}
internal void Close (HttpStatusCode code)
{
_context.Response.Close (code);
}
#endregion
#region Public Methods
/// <summary>
/// Returns a <see cref="string"/> that represents the current <see cref="HttpListenerWebSocketContext"/>.
/// 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"/>.
/// A <see cref="string"/> that represents the current
/// <see cref="HttpListenerWebSocketContext"/>.
/// </returns>
public override string ToString ()
{

@ -36,7 +36,8 @@ using System.Security.Principal;
namespace WebSocketSharp.Net.WebSockets
{
/// <summary>
/// Provides access to the WebSocket connection request objects received by the <see cref="TcpListener"/>.
/// Provides access to the WebSocket connection request information received by
/// the <see cref="TcpListener"/>.
/// </summary>
/// <remarks>
/// </remarks>
@ -49,19 +50,22 @@ namespace WebSocketSharp.Net.WebSockets
private HandshakeRequest _request;
private bool _secure;
private WsStream _stream;
private Uri _uri;
private IPrincipal _user;
private WebSocket _websocket;
#endregion
#region Internal Constructors
internal TcpListenerWebSocketContext (TcpClient client, bool secure, X509Certificate cert)
internal TcpListenerWebSocketContext (
TcpClient client, X509Certificate cert, bool secure, Logger logger)
{
_client = client;
_secure = secure;
_stream = WsStream.CreateServerStream (client, secure, cert);
_stream = WsStream.CreateServerStream (client, cert, secure);
_request = HandshakeRequest.Parse (_stream.ReadHandshake ());
_websocket = new WebSocket (this);
_websocket = new WebSocket (this, logger);
}
#endregion
@ -79,25 +83,23 @@ namespace WebSocketSharp.Net.WebSockets
#region Public Properties
/// <summary>
/// Gets the cookies used in the WebSocket opening handshake.
/// Gets the cookies used in the WebSocket connection request.
/// </summary>
/// <value>
/// A <see cref="CookieCollection"/> that contains the cookies.
/// A <see cref="WebSocketSharp.Net.CookieCollection"/> that contains the
/// cookies.
/// </value>
public override CookieCollection CookieCollection {
get {
if (_cookies == null)
_cookies = _request.Cookies;
return _cookies;
return _cookies ?? (_cookies = _request.Cookies);
}
}
/// <summary>
/// Gets the HTTP headers used in the WebSocket opening handshake.
/// Gets the HTTP headers used in the WebSocket connection request.
/// </summary>
/// <value>
/// A <see cref="System.Collections.Specialized.NameValueCollection"/> that contains the HTTP headers.
/// A <see cref="NameValueCollection"/> that contains the HTTP headers.
/// </value>
public override NameValueCollection Headers {
get {
@ -106,10 +108,11 @@ namespace WebSocketSharp.Net.WebSockets
}
/// <summary>
/// Gets the value of the Host header field used in the WebSocket opening handshake.
/// Gets the value of the Host header field used in the WebSocket connection
/// request.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the value of the Host header field.
/// A <see cref="string"/> that represents the value of the Host header field.
/// </value>
public override string Host {
get {
@ -123,20 +126,19 @@ namespace WebSocketSharp.Net.WebSockets
/// <value>
/// <c>true</c> if the client is authenticated; otherwise, <c>false</c>.
/// </value>
/// <exception cref="NotImplementedException">
/// This property is not implemented.
/// </exception>
public override bool IsAuthenticated {
get {
throw new NotImplementedException ();
return _user != null && _user.Identity.IsAuthenticated;
}
}
/// <summary>
/// Gets a value indicating whether the client connected from the local computer.
/// Gets a value indicating whether the client connected from the local
/// computer.
/// </summary>
/// <value>
/// <c>true</c> if the client connected from the local computer; otherwise, <c>false</c>.
/// <c>true</c> if the client connected from the local computer; otherwise,
/// <c>false</c>.
/// </value>
public override bool IsLocal {
get {
@ -148,7 +150,8 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets a value indicating whether the WebSocket connection is secured.
/// </summary>
/// <value>
/// <c>true</c> if the WebSocket connection is secured; otherwise, <c>false</c>.
/// <c>true</c> if the WebSocket connection is secured; otherwise,
/// <c>false</c>.
/// </value>
public override bool IsSecureConnection {
get {
@ -157,10 +160,12 @@ namespace WebSocketSharp.Net.WebSockets
}
/// <summary>
/// Gets a value indicating whether the request is a WebSocket connection request.
/// Gets a value indicating whether the request is a WebSocket connection
/// request.
/// </summary>
/// <value>
/// <c>true</c> if the request is a WebSocket connection request; otherwise, <c>false</c>.
/// <c>true</c> if the request is a WebSocket connection request; otherwise,
/// <c>false</c>.
/// </value>
public override bool IsWebSocketRequest {
get {
@ -169,10 +174,12 @@ namespace WebSocketSharp.Net.WebSockets
}
/// <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
/// connection request.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the value of the Origin header field.
/// A <see cref="string"/> that represents the value of the Origin header
/// field.
/// </value>
public override string Origin {
get {
@ -184,7 +191,8 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets the absolute path of the requested WebSocket URI.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the absolute path of the requested WebSocket URI.
/// A <see cref="string"/> that represents the absolute path of the requested
/// WebSocket URI.
/// </value>
public override string Path {
get {
@ -193,10 +201,12 @@ namespace WebSocketSharp.Net.WebSockets
}
/// <summary>
/// Gets the collection of query string variables used in the WebSocket opening handshake.
/// Gets the collection of query string variables used in the WebSocket
/// connection request.
/// </summary>
/// <value>
/// A <see cref="NameValueCollection"/> that contains the collection of query string variables.
/// A <see cref="NameValueCollection"/> that contains the collection of query
/// string variables.
/// </value>
public override NameValueCollection QueryString {
get {
@ -208,22 +218,26 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets the WebSocket URI requested by the client.
/// </summary>
/// <value>
/// A <see cref="Uri"/> that contains the WebSocket URI.
/// A <see cref="Uri"/> that represents the WebSocket URI requested by the
/// client.
/// </value>
public override Uri RequestUri {
get {
return _request.RequestUri;
return _uri ?? (_uri = createRequestUri ());
}
}
/// <summary>
/// Gets the value of the Sec-WebSocket-Key header field used in the WebSocket opening handshake.
/// Gets the value of the Sec-WebSocket-Key header field used in the
/// WebSocket connection request.
/// </summary>
/// <remarks>
/// The SecWebSocketKey property provides a part of the information used by the server to prove that it received a valid WebSocket opening handshake.
/// This property provides a part of the information used by the server to
/// prove that it received a valid WebSocket connection request.
/// </remarks>
/// <value>
/// A <see cref="string"/> that contains the value of the Sec-WebSocket-Key header field.
/// A <see cref="string"/> that represents the value of the Sec-WebSocket-Key
/// header field.
/// </value>
public override string SecWebSocketKey {
get {
@ -232,13 +246,15 @@ namespace WebSocketSharp.Net.WebSockets
}
/// <summary>
/// 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 connection request.
/// </summary>
/// <remarks>
/// This property indicates the subprotocols of the WebSocket connection.
/// This property represents the subprotocols of the WebSocket connection.
/// </remarks>
/// <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>
public override IEnumerable<string> SecWebSocketProtocols {
get {
@ -247,13 +263,15 @@ namespace WebSocketSharp.Net.WebSockets
}
/// <summary>
/// Gets the value of the Sec-WebSocket-Version header field used in the WebSocket opening handshake.
/// Gets the value of the Sec-WebSocket-Version header field used in the
/// WebSocket connection request.
/// </summary>
/// <remarks>
/// The SecWebSocketVersion property indicates the WebSocket protocol version of the connection.
/// This property represents the WebSocket protocol version of the connection.
/// </remarks>
/// <value>
/// A <see cref="string"/> that contains the value of the Sec-WebSocket-Version header field.
/// A <see cref="string"/> that represents the value of the
/// Sec-WebSocket-Version header field.
/// </value>
public override string SecWebSocketVersion {
get {
@ -265,7 +283,7 @@ namespace WebSocketSharp.Net.WebSockets
/// 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.
/// A <see cref="System.Net.IPEndPoint"/> that represents the server endpoint.
/// </value>
public override System.Net.IPEndPoint ServerEndPoint {
get {
@ -274,17 +292,15 @@ namespace WebSocketSharp.Net.WebSockets
}
/// <summary>
/// Gets the client information (identity, authentication information and security roles).
/// Gets the client information (identity, authentication information and
/// security roles).
/// </summary>
/// <value>
/// A <see cref="IPrincipal"/> that contains the client information.
/// A <see cref="IPrincipal"/> that represents the client information.
/// </value>
/// <exception cref="NotImplementedException">
/// This property is not implemented.
/// </exception>
public override IPrincipal User {
get {
throw new NotImplementedException ();
return _user;
}
}
@ -292,7 +308,7 @@ namespace WebSocketSharp.Net.WebSockets
/// 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.
/// A <see cref="System.Net.IPEndPoint"/> that represents the client endpoint.
/// </value>
public override System.Net.IPEndPoint UserEndPoint {
get {
@ -301,7 +317,8 @@ namespace WebSocketSharp.Net.WebSockets
}
/// <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>
/// <value>
/// A <see cref="WebSocketSharp.WebSocket"/>.
@ -314,6 +331,22 @@ namespace WebSocketSharp.Net.WebSockets
#endregion
#region Private Methods
private Uri createRequestUri ()
{
var scheme = _secure ? "wss" : "ws";
var host = _request.Headers ["Host"];
var rawUri = _request.RequestUri;
var path = rawUri.IsAbsoluteUri
? rawUri.PathAndQuery
: HttpUtility.UrlDecode (_request.RawUrl);
return String.Format ("{0}://{1}{2}", scheme, host, path).ToUri ();
}
#endregion
#region Internal Methods
internal void Close ()
@ -322,15 +355,64 @@ namespace WebSocketSharp.Net.WebSockets
_client.Close ();
}
internal void Close (HttpStatusCode code)
{
_websocket.Close (HandshakeResponse.CreateCloseResponse (code));
}
internal void SendAuthChallenge (string challenge)
{
var res = new HandshakeResponse (HttpStatusCode.Unauthorized);
res.Headers ["WWW-Authenticate"] = challenge;
_stream.WriteHandshake (res);
_request = HandshakeRequest.Parse (_stream.ReadHandshake ());
}
internal void SetUser (
AuthenticationSchemes expectedScheme,
string realm,
Func<IIdentity, NetworkCredential> credentialsFinder)
{
var authRes = _request.AuthResponse;
if (authRes == null)
return;
var identity = authRes.ToIdentity ();
if (identity == null)
return;
NetworkCredential credentials = null;
try {
credentials = credentialsFinder (identity);
}
catch {
}
if (credentials == null)
return;
var valid = expectedScheme == AuthenticationSchemes.Basic
? ((HttpBasicIdentity) identity).Password == credentials.Password
: expectedScheme == AuthenticationSchemes.Digest
? ((HttpDigestIdentity) identity).IsValid (
credentials.Password, realm, _request.HttpMethod, null)
: false;
if (valid)
_user = new GenericPrincipal (identity, credentials.Roles);
}
#endregion
#region Public Methods
/// <summary>
/// Returns a <see cref="string"/> that represents the current <see cref="TcpListenerWebSocketContext"/>.
/// 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"/>.
/// A <see cref="string"/> that represents the current
/// <see cref="TcpListenerWebSocketContext"/>.
/// </returns>
public override string ToString ()
{

@ -34,7 +34,7 @@ using System.Security.Principal;
namespace WebSocketSharp.Net.WebSockets
{
/// <summary>
/// Provides access to the WebSocket connection request objects.
/// Provides access to the WebSocket connection request information.
/// </summary>
/// <remarks>
/// The WebSocketContext class is an abstract class.
@ -55,26 +55,28 @@ namespace WebSocketSharp.Net.WebSockets
#region Public Properties
/// <summary>
/// Gets the cookies used in the WebSocket opening handshake.
/// Gets the cookies used in the WebSocket connection request.
/// </summary>
/// <value>
/// A <see cref="WebSocketSharp.Net.CookieCollection"/> that contains the cookies.
/// A <see cref="WebSocketSharp.Net.CookieCollection"/> that contains the
/// cookies.
/// </value>
public abstract CookieCollection CookieCollection { get; }
/// <summary>
/// Gets the HTTP headers used in the WebSocket opening handshake.
/// Gets the HTTP headers used in the WebSocket connection request.
/// </summary>
/// <value>
/// A <see cref="System.Collections.Specialized.NameValueCollection"/> that contains the HTTP headers.
/// A <see cref="NameValueCollection"/> that contains the HTTP headers.
/// </value>
public abstract NameValueCollection Headers { get; }
/// <summary>
/// Gets the value of the Host header field used in the WebSocket opening handshake.
/// Gets the value of the Host header field used in the WebSocket connection
/// request.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the value of the Host header field.
/// A <see cref="string"/> that represents the value of the Host header field.
/// </value>
public abstract string Host { get; }
@ -87,10 +89,12 @@ namespace WebSocketSharp.Net.WebSockets
public abstract bool IsAuthenticated { get; }
/// <summary>
/// Gets a value indicating whether the client connected from the local computer.
/// Gets a value indicating whether the client connected from the local
/// computer.
/// </summary>
/// <value>
/// <c>true</c> if the client connected from the local computer; otherwise, <c>false</c>.
/// <c>true</c> if the client connected from the local computer; otherwise,
/// <c>false</c>.
/// </value>
public abstract bool IsLocal { get; }
@ -98,23 +102,28 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets a value indicating whether the WebSocket connection is secured.
/// </summary>
/// <value>
/// <c>true</c> if the WebSocket connection is secured; otherwise, <c>false</c>.
/// <c>true</c> if the WebSocket connection is secured; otherwise,
/// <c>false</c>.
/// </value>
public abstract bool IsSecureConnection { get; }
/// <summary>
/// Gets a value indicating whether the request is a WebSocket connection request.
/// Gets a value indicating whether the request is a WebSocket connection
/// request.
/// </summary>
/// <value>
/// <c>true</c> if the request is a WebSocket connection request; otherwise, <c>false</c>.
/// <c>true</c> if the request is a WebSocket connection request; otherwise,
/// <c>false</c>.
/// </value>
public abstract bool IsWebSocketRequest { get; }
/// <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
/// connection request.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the value of the Origin header field.
/// A <see cref="string"/> that represents the value of the Origin header
/// field.
/// </value>
public abstract string Origin { get; }
@ -122,15 +131,18 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets the absolute path of the requested WebSocket URI.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the absolute path of the requested WebSocket URI.
/// A <see cref="string"/> that represents the absolute path of the requested
/// WebSocket URI.
/// </value>
public abstract string Path { get; }
/// <summary>
/// Gets the collection of query string variables used in the WebSocket opening handshake.
/// Gets the collection of query string variables used in the WebSocket
/// connection request.
/// </summary>
/// <value>
/// A <see cref="NameValueCollection"/> that contains the collection of query string variables.
/// A <see cref="NameValueCollection"/> that contains the collection of query
/// string variables.
/// </value>
public abstract NameValueCollection QueryString { get; }
@ -138,40 +150,47 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets the WebSocket URI requested by the client.
/// </summary>
/// <value>
/// A <see cref="Uri"/> that contains the WebSocket URI.
/// A <see cref="Uri"/> that represents the WebSocket URI.
/// </value>
public abstract Uri RequestUri { get; }
/// <summary>
/// Gets the value of the Sec-WebSocket-Key header field used in the WebSocket opening handshake.
/// Gets the value of the Sec-WebSocket-Key header field used in the
/// WebSocket connection request.
/// </summary>
/// <remarks>
/// The SecWebSocketKey property provides a part of the information used by the server to prove that it received a valid WebSocket opening handshake.
/// This property provides a part of the information used by the server to
/// prove that it received a valid WebSocket connection request.
/// </remarks>
/// <value>
/// A <see cref="string"/> that contains the value of the Sec-WebSocket-Key header field.
/// A <see cref="string"/> that represents the value of the Sec-WebSocket-Key
/// header field.
/// </value>
public abstract string SecWebSocketKey { get; }
/// <summary>
/// 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 connection request.
/// </summary>
/// <remarks>
/// The SecWebSocketProtocols property indicates the subprotocols of the WebSocket connection.
/// This property represents the subprotocols of the WebSocket connection.
/// </remarks>
/// <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>
public abstract IEnumerable<string> SecWebSocketProtocols { get; }
/// <summary>
/// Gets the value of the Sec-WebSocket-Version header field used in the WebSocket opening handshake.
/// Gets the value of the Sec-WebSocket-Version header field used in the
/// WebSocket connection request.
/// </summary>
/// <remarks>
/// The SecWebSocketVersion property indicates the WebSocket protocol version of the connection.
/// This property represents the WebSocket protocol version of the connection.
/// </remarks>
/// <value>
/// A <see cref="string"/> that contains the value of the Sec-WebSocket-Version header field.
/// A <see cref="string"/> that represents the value of the
/// Sec-WebSocket-Version header field.
/// </value>
public abstract string SecWebSocketVersion { get; }
@ -179,15 +198,16 @@ namespace WebSocketSharp.Net.WebSockets
/// 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.
/// A <see cref="System.Net.IPEndPoint"/> that represents the server endpoint.
/// </value>
public abstract System.Net.IPEndPoint ServerEndPoint { get; }
/// <summary>
/// Gets the client information (identity, authentication information and security roles).
/// Gets the client information (identity, authentication information and
/// security roles).
/// </summary>
/// <value>
/// A <see cref="IPrincipal"/> that contains the client information.
/// A <see cref="IPrincipal"/> that represents the client information.
/// </value>
public abstract IPrincipal User { get; }
@ -195,12 +215,13 @@ namespace WebSocketSharp.Net.WebSockets
/// 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.
/// A <see cref="System.Net.IPEndPoint"/> that represents the client endpoint.
/// </value>
public abstract System.Net.IPEndPoint UserEndPoint { get; }
/// <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>
/// <value>
/// A <see cref="WebSocketSharp.WebSocket"/>.

@ -40,16 +40,19 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Threading;
using WebSocketSharp.Net;
using WebSocketSharp.Net.WebSockets;
namespace WebSocketSharp.Server
{
/// <summary>
/// Provides a simple HTTP server that allows to accept the WebSocket connection requests.
/// Provides a simple HTTP server that allows to accept the WebSocket
/// connection requests.
/// </summary>
/// <remarks>
/// The HttpServer instance can provide the multi WebSocket services.
/// The HttpServer class can provide the multi WebSocket services.
/// </remarks>
public class HttpServer
{
@ -71,8 +74,8 @@ namespace WebSocketSharp.Server
#region Public Constructors
/// <summary>
/// Initializes a new instance of the <see cref="HttpServer"/> class that listens for
/// incoming requests on port 80.
/// Initializes a new instance of the <see cref="HttpServer"/> class that
/// listens for incoming requests on port 80.
/// </summary>
public HttpServer ()
: this (80)
@ -80,8 +83,8 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Initializes a new instance of the <see cref="HttpServer"/> class that listens for
/// incoming requests on the specified <paramref name="port"/>.
/// Initializes a new instance of the <see cref="HttpServer"/> class that
/// listens for incoming requests on the specified <paramref name="port"/>.
/// </summary>
/// <param name="port">
/// An <see cref="int"/> that contains a port number.
@ -95,8 +98,9 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Initializes a new instance of the <see cref="HttpServer"/> class that listens for
/// incoming requests on the specified <paramref name="port"/> and <paramref name="secure"/>.
/// Initializes a new instance of the <see cref="HttpServer"/> class that
/// listens for incoming requests on the specified <paramref name="port"/>
/// and <paramref name="secure"/>.
/// </summary>
/// <param name="port">
/// An <see cref="int"/> that contains a port number.
@ -114,16 +118,28 @@ namespace WebSocketSharp.Server
public HttpServer (int port, bool secure)
{
if (!port.IsPortNumber ())
throw new ArgumentOutOfRangeException ("port", "Must be between 1 and 65535: " + port);
throw new ArgumentOutOfRangeException (
"port", "Must be between 1 and 65535: " + port);
if ((port == 80 && secure) || (port == 443 && !secure))
throw new ArgumentException (String.Format (
"Invalid pair of 'port' and 'secure': {0}, {1}", port, secure));
throw new ArgumentException (
String.Format (
"Invalid pair of 'port' and 'secure': {0}, {1}", port, secure));
_port = port;
_secure = secure;
_listener = new HttpListener ();
_logger = new Logger ();
_serviceHosts = new WebSocketServiceHostManager (_logger);
_state = ServerState.READY;
_sync = new object ();
var os = Environment.OSVersion;
if (os.Platform != PlatformID.Unix && os.Platform != PlatformID.MacOSX)
_windows = true;
init ();
var prefix = String.Format ("http{0}://*:{1}/", _secure ? "s" : "", _port);
_listener.Prefixes.Add (prefix);
}
#endregion
@ -131,7 +147,29 @@ namespace WebSocketSharp.Server
#region Public Properties
/// <summary>
/// Gets or sets the certificate used to authenticate the server on the secure connection.
/// Gets or sets the scheme used to authenticate the clients.
/// </summary>
/// <value>
/// One of the <see cref="WebSocketSharp.Net.AuthenticationSchemes"/> values
/// that indicates the scheme used to authenticate the clients. The default
/// value is <see cref="WebSocketSharp.Net.AuthenticationSchemes.Anonymous"/>.
/// </value>
public AuthenticationSchemes AuthenticationSchemes {
get {
return _listener.AuthenticationSchemes;
}
set {
if (!canSet ("AuthenticationSchemes"))
return;
_listener.AuthenticationSchemes = value;
}
}
/// <summary>
/// Gets or sets the certificate used to authenticate the server on the
/// secure connection.
/// </summary>
/// <value>
/// A <see cref="X509Certificate2"/> used to authenticate the server.
@ -142,16 +180,12 @@ namespace WebSocketSharp.Server
}
set {
if (_state == ServerState.START || _state == ServerState.SHUTDOWN)
{
_logger.Error (
"The value of Certificate property cannot be changed because the server has already been started.");
if (!canSet ("Certificate"))
return;
}
if (EndPointListener.CertificateExists (_port, _listener.CertificateFolderPath))
_logger.Warn ("The server certificate associated with the port number already exists.");
_logger.Warn (
"The server certificate associated with the port number already exists.");
_listener.DefaultCertificate = value;
}
@ -173,7 +207,8 @@ namespace WebSocketSharp.Server
/// Gets a value indicating whether the server provides secure connection.
/// </summary>
/// <value>
/// <c>true</c> if the server provides secure connection; otherwise, <c>false</c>.
/// <c>true</c> if the server provides secure connection; otherwise,
/// <c>false</c>.
/// </value>
public bool IsSecure {
get {
@ -182,12 +217,12 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Gets or sets a value indicating whether the server cleans up the inactive WebSocket sessions
/// periodically.
/// Gets or sets a value indicating whether the server cleans up the inactive
/// WebSocket sessions periodically.
/// </summary>
/// <value>
/// <c>true</c> if the server cleans up the inactive WebSocket sessions every 60 seconds;
/// otherwise, <c>false</c>. The default value is <c>true</c>.
/// <c>true</c> if the server cleans up the inactive WebSocket sessions every
/// 60 seconds; otherwise, <c>false</c>. The default value is <c>true</c>.
/// </value>
public bool KeepClean {
get {
@ -203,9 +238,9 @@ namespace WebSocketSharp.Server
/// Gets the logging functions.
/// </summary>
/// <remarks>
/// The default logging level is the <see cref="LogLevel.ERROR"/>.
/// If you want to change the current logging level, you set the <c>Log.Level</c> property
/// to one of the <see cref="LogLevel"/> values which you want.
/// The default logging level is the <see cref="LogLevel.ERROR"/>. If you
/// change the current logging level, you set the <c>Log.Level</c> property
/// to any of the <see cref="LogLevel"/> values.
/// </remarks>
/// <value>
/// A <see cref="Logger"/> that provides the logging functions.
@ -228,6 +263,27 @@ namespace WebSocketSharp.Server
}
}
/// <summary>
/// Gets or sets the name of the realm associated with the
/// <see cref="HttpServer"/>.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the name of the realm.
/// The default value is <c>SECRET AREA</c>.
/// </value>
public string Realm {
get {
return _listener.Realm;
}
set {
if (!canSet ("Realm"))
return;
_listener.Realm = value;
}
}
/// <summary>
/// Gets or sets the document root path of server.
/// </summary>
@ -243,23 +299,42 @@ namespace WebSocketSharp.Server
}
set {
if (_state == ServerState.START || _state == ServerState.SHUTDOWN)
{
_logger.Error (
"The value of RootPath property cannot be changed because the server has already been started.");
if (!canSet ("RootPath"))
return;
}
_rootPath = value;
}
}
/// <summary>
/// Gets the functions for the WebSocket services that the server provides.
/// Gets or sets the delegate called to find the credentials for an identity
/// used to authenticate a client.
/// </summary>
/// <value>
/// A <see cref="WebSocketServiceHostManager"/> that manages the WebSocket services.
/// A Func&lt;<see cref="IIdentity"/>, <see cref="NetworkCredential"/>&gt;
/// delegate that references the method(s) used to find the credentials. The
/// default value is a function that only returns <see langword="null"/>.
/// </value>
public Func<IIdentity, NetworkCredential> UserCredentialsFinder {
get {
return _listener.UserCredentialsFinder;
}
set {
if (!canSet ("UserCredentialsFinder"))
return;
_listener.UserCredentialsFinder = value;
}
}
/// <summary>
/// Gets the functions for the WebSocket services provided by the
/// <see cref="HttpServer"/>.
/// </summary>
/// <value>
/// A <see cref="WebSocketServiceHostManager"/> that manages the WebSocket
/// services.
/// </value>
public WebSocketServiceHostManager WebSocketServices {
get {
@ -322,8 +397,7 @@ namespace WebSocketSharp.Server
private void abort ()
{
lock (_sync)
{
lock (_sync) {
if (!IsListening)
return;
@ -331,152 +405,156 @@ namespace WebSocketSharp.Server
}
_serviceHosts.Stop (
((ushort) CloseStatusCode.SERVER_ERROR).ToByteArrayInternally (ByteOrder.BIG), true);
((ushort) CloseStatusCode.SERVER_ERROR).ToByteArrayInternally (ByteOrder.BIG),
true);
_listener.Abort ();
_state = ServerState.STOP;
}
private string checkIfCertExists ()
{
return _secure &&
!EndPointListener.CertificateExists (_port, _listener.CertificateFolderPath) &&
Certificate == null
? "The secure connection requires a server certificate."
: null;
}
private void init ()
{
_listener = new HttpListener ();
_logger = new Logger ();
_serviceHosts = new WebSocketServiceHostManager (_logger);
_state = ServerState.READY;
_sync = new object ();
_windows = false;
var os = Environment.OSVersion;
if (os.Platform != PlatformID.Unix && os.Platform != PlatformID.MacOSX)
_windows = true;
var prefix = String.Format ("http{0}://*:{1}/", _secure ? "s" : "", _port);
_listener.Prefixes.Add (prefix);
}
private void processHttpRequest (HttpListenerContext context)
private void acceptHttpRequest (HttpListenerContext context)
{
var args = new HttpRequestEventArgs (context);
var method = context.Request.HttpMethod;
if (method == "GET" && OnGet != null)
{
if (method == "GET" && OnGet != null) {
OnGet (this, args);
return;
}
if (method == "HEAD" && OnHead != null)
{
if (method == "HEAD" && OnHead != null) {
OnHead (this, args);
return;
}
if (method == "POST" && OnPost != null)
{
if (method == "POST" && OnPost != null) {
OnPost (this, args);
return;
}
if (method == "PUT" && OnPut != null)
{
if (method == "PUT" && OnPut != null) {
OnPut (this, args);
return;
}
if (method == "DELETE" && OnDelete != null)
{
if (method == "DELETE" && OnDelete != null) {
OnDelete (this, args);
return;
}
if (method == "OPTIONS" && OnOptions != null)
{
if (method == "OPTIONS" && OnOptions != null) {
OnOptions (this, args);
return;
}
if (method == "TRACE" && OnTrace != null)
{
if (method == "TRACE" && OnTrace != null) {
OnTrace (this, args);
return;
}
if (method == "CONNECT" && OnConnect != null)
{
if (method == "CONNECT" && OnConnect != null) {
OnConnect (this, args);
return;
}
if (method == "PATCH" && OnPatch != null)
{
if (method == "PATCH" && OnPatch != null) {
OnPatch (this, args);
return;
}
context.Response.StatusCode = (int) HttpStatusCode.NotImplemented;
context.Response.Close (HttpStatusCode.NotImplemented);
}
private bool processWebSocketRequest (HttpListenerContext context)
private void acceptRequestAsync (HttpListenerContext context)
{
var wsContext = context.AcceptWebSocket ();
ThreadPool.QueueUserWorkItem (
state => {
try {
var authScheme = _listener.SelectAuthenticationScheme (context);
if (authScheme != AuthenticationSchemes.Anonymous &&
!authenticateRequest (authScheme, context))
return;
if (context.Request.IsUpgradeTo ("websocket")) {
acceptWebSocketRequest (context.AcceptWebSocket (_logger));
return;
}
acceptHttpRequest (context);
context.Response.Close ();
}
catch (Exception ex) {
_logger.Fatal (ex.ToString ());
context.Connection.Close (true);
}
});
}
private void acceptWebSocketRequest (HttpListenerWebSocketContext context)
{
var path = context.Path;
var path = wsContext.Path;
WebSocketServiceHost host;
if (path == null || !_serviceHosts.TryGetServiceHostInternally (path, out host))
{
context.Response.StatusCode = (int) HttpStatusCode.NotImplemented;
return false;
if (path == null ||
!_serviceHosts.TryGetServiceHostInternally (path, out host)) {
context.Close (HttpStatusCode.NotImplemented);
return;
}
wsContext.WebSocket.Log = _logger;
host.StartSession (wsContext);
host.StartSession (context);
}
return true;
private bool authenticateRequest (
AuthenticationSchemes scheme, HttpListenerContext context)
{
if (context.Request.IsAuthenticated)
return true;
if (scheme == AuthenticationSchemes.Basic)
context.Response.CloseWithAuthChallenge (
HttpUtility.CreateBasicAuthChallenge (_listener.Realm));
else if (scheme == AuthenticationSchemes.Digest)
context.Response.CloseWithAuthChallenge (
HttpUtility.CreateDigestAuthChallenge (_listener.Realm));
else
context.Response.Close (HttpStatusCode.Forbidden);
return false;
}
private void processRequestAsync (HttpListenerContext context)
private bool canSet (string property)
{
WaitCallback callback = state =>
{
try {
if (context.Request.IsUpgradeTo ("websocket"))
{
if (processWebSocketRequest (context))
return;
}
else
{
processHttpRequest (context);
}
if (_state == ServerState.START || _state == ServerState.SHUTDOWN) {
_logger.Error (
String.Format (
"The '{0}' property cannot set a value because the server has already been started.",
property));
context.Response.Close ();
}
catch (Exception ex) {
_logger.Fatal (ex.ToString ());
context.Connection.Close (true);
}
};
return false;
}
ThreadPool.QueueUserWorkItem (callback);
return true;
}
private string checkIfCertExists ()
{
return _secure &&
!EndPointListener.CertificateExists (_port, _listener.CertificateFolderPath) &&
Certificate == null
? "The secure connection requires a server certificate."
: null;
}
private void receiveRequest ()
{
while (true)
{
while (true) {
try {
processRequestAsync (_listener.GetContext ());
acceptRequestAsync (_listener.GetContext ());
}
catch (HttpListenerException ex) {
_logger.Warn (String.Format ("Receiving has been stopped.\nreason: {0}.", ex.Message));
_logger.Warn (
String.Format (
"Receiving has been stopped.\nreason: {0}.", ex.Message));
break;
}
catch (Exception ex) {
@ -489,7 +567,7 @@ namespace WebSocketSharp.Server
abort ();
}
private void startReceiveRequestThread ()
private void startReceiving ()
{
_receiveRequestThread = new Thread (new ThreadStart (receiveRequest));
_receiveRequestThread.IsBackground = true;
@ -507,18 +585,21 @@ namespace WebSocketSharp.Server
#region Public Methods
/// <summary>
/// Adds the specified typed WebSocket service with the specified <paramref name="servicePath"/>.
/// Adds the specified typed WebSocket service with the specified
/// <paramref name="servicePath"/>.
/// </summary>
/// <remarks>
/// This method converts <paramref name="servicePath"/> to URL-decoded string and
/// removes <c>'/'</c> from tail end of <paramref name="servicePath"/>.
/// This method converts <paramref name="servicePath"/> to URL-decoded string
/// and removes <c>'/'</c> from tail end of <paramref name="servicePath"/>.
/// </remarks>
/// <param name="servicePath">
/// A <see cref="string"/> that contains an absolute path to the WebSocket service.
/// A <see cref="string"/> that contains an absolute path to the WebSocket
/// service.
/// </param>
/// <typeparam name="TWithNew">
/// The type of the WebSocket service. The TWithNew must inherit the <see cref="WebSocketService"/> class and
/// must have a public parameterless constructor.
/// The type of the WebSocket service. The TWithNew must inherit the
/// <see cref="WebSocketService"/> class and must have a public parameterless
/// constructor.
/// </typeparam>
public void AddWebSocketService<TWithNew> (string servicePath)
where TWithNew : WebSocketService, new ()
@ -527,42 +608,50 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Adds the specified typed WebSocket service with the specified <paramref name="servicePath"/> and
/// <paramref name="serviceConstructor"/>.
/// Adds the specified typed WebSocket service with the specified
/// <paramref name="servicePath"/> and <paramref name="serviceConstructor"/>.
/// </summary>
/// <remarks>
/// <para>
/// This method converts <paramref name="servicePath"/> to URL-decoded string and
/// removes <c>'/'</c> from tail end of <paramref name="servicePath"/>.
/// This method converts <paramref name="servicePath"/> to URL-decoded
/// string and removes <c>'/'</c> from tail end of
/// <paramref name="servicePath"/>.
/// </para>
/// <para>
/// <paramref name="serviceConstructor"/> returns a initialized specified typed WebSocket service
/// instance.
/// <paramref name="serviceConstructor"/> returns a initialized specified
/// typed WebSocket service instance.
/// </para>
/// </remarks>
/// <param name="servicePath">
/// A <see cref="string"/> that contains an absolute path to the WebSocket service.
/// A <see cref="string"/> that contains an absolute path to the WebSocket
/// service.
/// </param>
/// <param name="serviceConstructor">
/// A Func&lt;T&gt; delegate that references the method used to initialize a new WebSocket service
/// instance (a new WebSocket session).
/// A Func&lt;T&gt; delegate that references the method used to initialize
/// a new WebSocket service instance (a new WebSocket session).
/// </param>
/// <typeparam name="T">
/// The type of the WebSocket service. The T must inherit the <see cref="WebSocketService"/> class.
/// The type of the WebSocket service. The T must inherit the
/// <see cref="WebSocketService"/> class.
/// </typeparam>
public void AddWebSocketService<T> (string servicePath, Func<T> serviceConstructor)
where T : WebSocketService
{
var msg = servicePath.CheckIfValidServicePath () ??
(serviceConstructor == null ? "'serviceConstructor' must not be null." : null);
(serviceConstructor == null
? "'serviceConstructor' must not be null."
: null);
if (msg != null) {
_logger.Error (
String.Format ("{0}\nservice path: {1}", msg, servicePath ?? ""));
if (msg != null)
{
_logger.Error (String.Format ("{0}\nservice path: {1}", msg, servicePath ?? ""));
return;
}
var host = new WebSocketServiceHost<T> (servicePath, serviceConstructor, _logger);
var host = new WebSocketServiceHost<T> (
servicePath, serviceConstructor, _logger);
if (!KeepClean)
host.KeepClean = false;
@ -573,8 +662,8 @@ namespace WebSocketSharp.Server
/// Gets the contents of the file with the specified <paramref name="path"/>.
/// </summary>
/// <returns>
/// An array of <see cref="byte"/> that contains the contents of the file if exists;
/// otherwise, <see langword="null"/>.
/// An array of <see cref="byte"/> that contains the contents of the file if
/// it exists; otherwise, <see langword="null"/>.
/// </returns>
/// <param name="path">
/// A <see cref="string"/> that contains a virtual path to the file to get.
@ -591,24 +680,28 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Removes the WebSocket service with the specified <paramref name="servicePath"/>.
/// Removes the WebSocket service with the specified
/// <paramref name="servicePath"/>.
/// </summary>
/// <remarks>
/// This method converts <paramref name="servicePath"/> to URL-decoded string and
/// removes <c>'/'</c> from tail end of <paramref name="servicePath"/>.
/// This method converts <paramref name="servicePath"/> to URL-decoded string
/// and removes <c>'/'</c> from tail end of <paramref name="servicePath"/>.
/// </remarks>
/// <returns>
/// <c>true</c> if the WebSocket service is successfully found and removed; otherwise, <c>false</c>.
/// <c>true</c> if the WebSocket service is successfully found and removed;
/// otherwise, <c>false</c>.
/// </returns>
/// <param name="servicePath">
/// A <see cref="string"/> that contains an absolute path to the WebSocket service to find.
/// A <see cref="string"/> that contains an absolute path to the WebSocket
/// service to find.
/// </param>
public bool RemoveWebSocketService (string servicePath)
{
var msg = servicePath.CheckIfValidServicePath ();
if (msg != null)
{
_logger.Error (String.Format ("{0}\nservice path: {1}", msg, servicePath ?? ""));
if (msg != null) {
_logger.Error (
String.Format ("{0}\nservice path: {1}", msg, servicePath));
return false;
}
@ -616,22 +709,23 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Starts to receive the HTTP requests.
/// Starts receiving the HTTP requests.
/// </summary>
public void Start ()
{
lock (_sync)
{
lock (_sync) {
var msg = _state.CheckIfStopped () ?? checkIfCertExists ();
if (msg != null)
{
_logger.Error (String.Format ("{0}\nstate: {1}\nsecure: {2}", msg, _state, _secure));
if (msg != null) {
_logger.Error (
String.Format (
"{0}\nstate: {1}\nsecure: {2}", msg, _state, _secure));
return;
}
_serviceHosts.Start ();
_listener.Start ();
startReceiveRequestThread ();
startReceiving ();
_state = ServerState.START;
}
@ -642,11 +736,9 @@ namespace WebSocketSharp.Server
/// </summary>
public void Stop ()
{
lock (_sync)
{
lock (_sync) {
var msg = _state.CheckIfStarted ();
if (msg != null)
{
if (msg != null) {
_logger.Error (String.Format ("{0}\nstate: {1}", msg, _state));
return;
}
@ -654,18 +746,19 @@ namespace WebSocketSharp.Server
_state = ServerState.SHUTDOWN;
}
_serviceHosts.Stop (new byte []{}, true);
_serviceHosts.Stop (new byte [0], true);
stopListener (5000);
_state = ServerState.STOP;
}
/// <summary>
/// Stops receiving the HTTP requests with the specified <see cref="ushort"/> and
/// <see cref="string"/> used to stop the WebSocket services.
/// Stops receiving the HTTP requests with the specified <see cref="ushort"/>
/// and <see cref="string"/> used to stop the WebSocket services.
/// </summary>
/// <param name="code">
/// A <see cref="ushort"/> that contains a status code indicating the reason for stop.
/// A <see cref="ushort"/> that contains a status code indicating the reason
/// for stop.
/// </param>
/// <param name="reason">
/// A <see cref="string"/> that contains the reason for stop.
@ -673,16 +766,15 @@ namespace WebSocketSharp.Server
public void Stop (ushort code, string reason)
{
byte [] data = null;
lock (_sync)
{
lock (_sync) {
var msg = _state.CheckIfStarted () ??
code.CheckIfValidCloseStatusCode () ??
(data = code.Append (reason)).CheckIfValidCloseData ();
if (msg != null)
{
_logger.Error (String.Format (
"{0}\nstate: {1}\ncode: {2}\nreason: {3}", msg, _state, code, reason));
if (msg != null) {
_logger.Error (
String.Format (
"{0}\nstate: {1}\ncode: {2}\nreason: {3}", msg, _state, code, reason));
return;
}
@ -697,12 +789,13 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Stops receiving the HTTP requests with the specified <see cref="CloseStatusCode"/>
/// and <see cref="string"/> used to stop the WebSocket services.
/// Stops receiving the HTTP requests with the specified
/// <see cref="CloseStatusCode"/> and <see cref="string"/> used to stop the
/// WebSocket services.
/// </summary>
/// <param name="code">
/// One of the <see cref="CloseStatusCode"/> values that represent the status codes indicating
/// the reasons for stop.
/// One of the <see cref="CloseStatusCode"/> values that represent the status
/// codes indicating the reasons for stop.
/// </param>
/// <param name="reason">
/// A <see cref="string"/> that contains the reason for stop.
@ -710,14 +803,14 @@ namespace WebSocketSharp.Server
public void Stop (CloseStatusCode code, string reason)
{
byte [] data = null;
lock (_sync)
{
lock (_sync) {
var msg = _state.CheckIfStarted () ??
(data = ((ushort) code).Append (reason)).CheckIfValidCloseData ();
if (msg != null)
{
_logger.Error (String.Format ("{0}\nstate: {1}\nreason: {2}", msg, _state, reason));
if (msg != null) {
_logger.Error (
String.Format ("{0}\nstate: {1}\nreason: {2}", msg, _state, reason));
return;
}

@ -39,6 +39,7 @@ using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Text;
using System.Threading;
using WebSocketSharp.Net;
@ -47,7 +48,8 @@ using WebSocketSharp.Net.WebSockets;
namespace WebSocketSharp.Server
{
/// <summary>
/// Provides the functions of the server that receives the WebSocket connection requests.
/// Provides the functions of the server that receives the WebSocket connection
/// requests.
/// </summary>
/// <remarks>
/// The WebSocketServer class provides the multi WebSocket service.
@ -56,25 +58,28 @@ namespace WebSocketSharp.Server
{
#region Private Fields
private System.Net.IPAddress _address;
private X509Certificate2 _cert;
private TcpListener _listener;
private Logger _logger;
private int _port;
private Thread _receiveRequestThread;
private bool _secure;
private WebSocketServiceHostManager _serviceHosts;
private volatile ServerState _state;
private object _sync;
private Uri _uri;
private System.Net.IPAddress _address;
private AuthenticationSchemes _authSchemes;
private X509Certificate2 _cert;
private Func<IIdentity, NetworkCredential> _credentialsFinder;
private TcpListener _listener;
private Logger _logger;
private int _port;
private string _realm;
private Thread _receiveRequestThread;
private bool _secure;
private WebSocketServiceHostManager _serviceHosts;
private volatile ServerState _state;
private object _sync;
private Uri _uri;
#endregion
#region Public Constructors
/// <summary>
/// Initializes a new instance of the <see cref="WebSocketServer"/> class that listens for
/// incoming requests on port 80.
/// Initializes a new instance of the <see cref="WebSocketServer"/> class
/// that listens for incoming requests on port 80.
/// </summary>
public WebSocketServer ()
: this (80)
@ -82,8 +87,9 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Initializes a new instance of the <see cref="WebSocketServer"/> class that listens for
/// incoming connection attempts on the specified <paramref name="port"/>.
/// Initializes a new instance of the <see cref="WebSocketServer"/> class
/// that listens for incoming connection attempts on the specified
/// <paramref name="port"/>.
/// </summary>
/// <param name="port">
/// An <see cref="int"/> that contains a port number.
@ -97,8 +103,9 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Initializes a new instance of the <see cref="WebSocketServer"/> class that listens for
/// incoming connection attempts on the specified WebSocket URL.
/// Initializes a new instance of the <see cref="WebSocketServer"/> class
/// that listens for incoming connection attempts on the specified WebSocket
/// URL.
/// </summary>
/// <param name="url">
/// A <see cref="string"/> that contains a WebSocket URL.
@ -121,8 +128,9 @@ namespace WebSocketSharp.Server
var host = _uri.DnsSafeHost;
_address = host.ToIPAddress ();
if (_address == null || !_address.IsLocal ())
throw new ArgumentException (String.Format (
"The host part must be the local host name: {0}", host), "url");
throw new ArgumentException (
String.Format (
"The host part must be the local host name: {0}", host), "url");
_port = _uri.Port;
_secure = _uri.Scheme == "wss" ? true : false;
@ -131,8 +139,9 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Initializes a new instance of the <see cref="WebSocketServer"/> class that listens for
/// incoming connection attempts on the specified <paramref name="port"/> and <paramref name="secure"/>.
/// Initializes a new instance of the <see cref="WebSocketServer"/> class
/// that listens for incoming connection attempts on the specified
/// <paramref name="port"/> and <paramref name="secure"/>.
/// </summary>
/// <param name="port">
/// An <see cref="int"/> that contains a port number.
@ -153,11 +162,13 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Initializes a new instance of the <see cref="WebSocketServer"/> class that listens for
/// incoming connection attempts on the specified <paramref name="address"/> and <paramref name="port"/>.
/// Initializes a new instance of the <see cref="WebSocketServer"/> class
/// that listens for incoming connection attempts on the specified
/// <paramref name="address"/> and <paramref name="port"/>.
/// </summary>
/// <param name="address">
/// A <see cref="System.Net.IPAddress"/> that represents the local IP address.
/// A <see cref="System.Net.IPAddress"/> that represents the local IP
/// address.
/// </param>
/// <param name="port">
/// An <see cref="int"/> that contains a port number.
@ -177,12 +188,14 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Initializes a new instance of the <see cref="WebSocketServer"/> class that listens for
/// incoming connection attempts on the specified <paramref name="address"/>, <paramref name="port"/>
/// and <paramref name="secure"/>.
/// Initializes a new instance of the <see cref="WebSocketServer"/> class
/// that listens for incoming connection attempts on the specified
/// <paramref name="address"/>, <paramref name="port"/> and
/// <paramref name="secure"/>.
/// </summary>
/// <param name="address">
/// A <see cref="System.Net.IPAddress"/> that represents the local IP address.
/// A <see cref="System.Net.IPAddress"/> that represents the local IP
/// address.
/// </param>
/// <param name="port">
/// An <see cref="int"/> that contains a port number.
@ -205,21 +218,25 @@ namespace WebSocketSharp.Server
/// -or-
/// </para>
/// <para>
/// Pair of <paramref name="port"/> and <paramref name="secure"/> is invalid.
/// Pair of <paramref name="port"/> and <paramref name="secure"/> is
/// invalid.
/// </para>
/// </exception>
public WebSocketServer (System.Net.IPAddress address, int port, bool secure)
{
if (!address.IsLocal ())
throw new ArgumentException (String.Format (
"Must be the local IP address: {0}", address), "address");
throw new ArgumentException (
String.Format (
"Must be the local IP address: {0}", address), "address");
if (!port.IsPortNumber ())
throw new ArgumentOutOfRangeException ("port", "Must be between 1 and 65535: " + port);
throw new ArgumentOutOfRangeException (
"port", "Must be between 1 and 65535: " + port);
if ((port == 80 && secure) || (port == 443 && !secure))
throw new ArgumentException (String.Format (
"Invalid pair of 'port' and 'secure': {0}, {1}", port, secure));
throw new ArgumentException (
String.Format (
"Invalid pair of 'port' and 'secure': {0}, {1}", port, secure));
_address = address;
_port = port;
@ -234,10 +251,12 @@ namespace WebSocketSharp.Server
#region Public Properties
/// <summary>
/// Gets the local IP address on which to listen for incoming connection attempts.
/// Gets the local IP address on which to listen for incoming connection
/// attempts.
/// </summary>
/// <value>
/// A <see cref="System.Net.IPAddress"/> that represents the local IP address.
/// A <see cref="System.Net.IPAddress"/> that represents the local IP
/// address.
/// </value>
public System.Net.IPAddress Address {
get {
@ -246,7 +265,29 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Gets or sets the certificate used to authenticate the server on the secure connection.
/// Gets or sets the scheme used to authenticate the clients.
/// </summary>
/// <value>
/// One of the <see cref="WebSocketSharp.Net.AuthenticationSchemes"/> values
/// that indicates the scheme used to authenticate the clients. The default
/// value is <see cref="WebSocketSharp.Net.AuthenticationSchemes.Anonymous"/>.
/// </value>
public AuthenticationSchemes AuthenticationSchemes {
get {
return _authSchemes;
}
set {
if (!canSet ("AuthenticationSchemes"))
return;
_authSchemes = value;
}
}
/// <summary>
/// Gets or sets the certificate used to authenticate the server on the
/// secure connection.
/// </summary>
/// <value>
/// A <see cref="X509Certificate2"/> used to authenticate the server.
@ -257,13 +298,8 @@ namespace WebSocketSharp.Server
}
set {
if (_state == ServerState.START || _state == ServerState.SHUTDOWN)
{
_logger.Error (
"The value of Certificate property cannot be changed because the server has already been started.");
if (!canSet ("Certificate"))
return;
}
_cert = value;
}
@ -285,7 +321,8 @@ namespace WebSocketSharp.Server
/// Gets a value indicating whether the server provides secure connection.
/// </summary>
/// <value>
/// <c>true</c> if the server provides secure connection; otherwise, <c>false</c>.
/// <c>true</c> if the server provides secure connection; otherwise,
/// <c>false</c>.
/// </value>
public bool IsSecure {
get {
@ -294,11 +331,12 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Gets or sets a value indicating whether the server cleans up the inactive sessions periodically.
/// Gets or sets a value indicating whether the server cleans up the inactive
/// sessions periodically.
/// </summary>
/// <value>
/// <c>true</c> if the server cleans up the inactive sessions every 60 seconds;
/// otherwise, <c>false</c>. The default value is <c>true</c>.
/// <c>true</c> if the server cleans up the inactive sessions every 60
/// seconds; otherwise, <c>false</c>. The default value is <c>true</c>.
/// </value>
public bool KeepClean {
get {
@ -314,9 +352,9 @@ namespace WebSocketSharp.Server
/// Gets the logging functions.
/// </summary>
/// <remarks>
/// The default logging level is the <see cref="LogLevel.ERROR"/>.
/// If you want to change the current logging level, you set the <c>Log.Level</c> property
/// to one of the <see cref="LogLevel"/> values which you want.
/// The default logging level is the <see cref="LogLevel.ERROR"/>. If you
/// change the current logging level, you set the <c>Log.Level</c> property
/// to any of the <see cref="LogLevel"/> values.
/// </remarks>
/// <value>
/// A <see cref="Logger"/> that provides the logging functions.
@ -340,10 +378,55 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Gets the functions for the WebSocket services provided by the server.
/// Gets or sets the name of the realm associated with the
/// <see cref="WebSocketServer"/>.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the name of the realm.
/// The default value is <c>SECRET AREA</c>.
/// </value>
public string Realm {
get {
return _realm ?? (_realm = "SECRET AREA");
}
set {
if (!canSet ("Realm"))
return;
_realm = value;
}
}
/// <summary>
/// Gets or sets the delegate called to find the credentials for an identity
/// used to authenticate a client.
/// </summary>
/// <value>
/// A Func&lt;<see cref="IIdentity"/>, <see cref="NetworkCredential"/>&gt;
/// delegate that references the method(s) used to find the credentials. The
/// default value is a function that only returns <see langword="null"/>.
/// </value>
public Func<IIdentity, NetworkCredential> UserCredentialsFinder {
get {
return _credentialsFinder ?? (_credentialsFinder = identity => null);
}
set {
if (!canSet ("UserCredentialsFinder"))
return;
_credentialsFinder = value;
}
}
/// <summary>
/// Gets the functions for the WebSocket services provided by the
/// <see cref="WebSocketServer"/>.
/// </summary>
/// <value>
/// A <see cref="WebSocketServiceHostManager"/> that manages the WebSocket services.
/// A <see cref="WebSocketServiceHostManager"/> that manages the WebSocket
/// services.
/// </value>
public WebSocketServiceHostManager WebSocketServices {
get {
@ -357,8 +440,7 @@ namespace WebSocketSharp.Server
private void abort ()
{
lock (_sync)
{
lock (_sync) {
if (!IsListening)
return;
@ -367,30 +449,103 @@ namespace WebSocketSharp.Server
_listener.Stop ();
_serviceHosts.Stop (
((ushort) CloseStatusCode.SERVER_ERROR).ToByteArrayInternally (ByteOrder.BIG), true);
((ushort) CloseStatusCode.SERVER_ERROR).ToByteArrayInternally (ByteOrder.BIG),
true);
_state = ServerState.STOP;
}
private void acceptWebSocket (TcpListenerWebSocketContext context)
private void acceptRequestAsync (TcpClient client)
{
var websocket = context.WebSocket;
websocket.Log = _logger;
ThreadPool.QueueUserWorkItem (
state => {
try {
var context = client.GetWebSocketContext (_cert, _secure, _logger);
if (_authSchemes != AuthenticationSchemes.Anonymous &&
!authenticateRequest (_authSchemes, context))
return;
acceptWebSocket (context);
}
catch (Exception ex) {
_logger.Fatal (ex.ToString ());
client.Close ();
}
});
}
private void acceptWebSocket (TcpListenerWebSocketContext context)
{
var path = context.Path;
WebSocketServiceHost host;
if (path == null || !_serviceHosts.TryGetServiceHostInternally (path, out host))
{
websocket.Close (HttpStatusCode.NotImplemented);
if (path == null ||
!_serviceHosts.TryGetServiceHostInternally (path, out host)) {
context.Close (HttpStatusCode.NotImplemented);
return;
}
if (_uri.IsAbsoluteUri)
websocket.Url = new Uri (_uri, path);
host.StartSession (context);
}
private bool authenticateRequest (
AuthenticationSchemes authScheme, TcpListenerWebSocketContext context)
{
var challenge = authScheme == AuthenticationSchemes.Basic
? HttpUtility.CreateBasicAuthChallenge (Realm)
: authScheme == AuthenticationSchemes.Digest
? HttpUtility.CreateDigestAuthChallenge (Realm)
: null;
if (challenge == null) {
context.Close (HttpStatusCode.Forbidden);
return false;
}
var retry = -1;
var expected = authScheme.ToString ();
var realm = Realm;
var credentialsFinder = UserCredentialsFinder;
Func<bool> auth = null;
auth = () => {
retry++;
if (retry > 99) {
context.Close (HttpStatusCode.Forbidden);
return false;
}
var header = context.Headers ["Authorization"];
if (header == null ||
!header.StartsWith (expected, StringComparison.OrdinalIgnoreCase)) {
context.SendAuthChallenge (challenge);
return auth ();
}
context.SetUser (authScheme, realm, credentialsFinder);
if (context.IsAuthenticated)
return true;
context.SendAuthChallenge (challenge);
return auth ();
};
return auth ();
}
private bool canSet (string property)
{
if (_state == ServerState.START || _state == ServerState.SHUTDOWN) {
_logger.Error (
String.Format (
"The '{0}' property cannot set a value because the server has already been started.",
property));
return false;
}
return true;
}
private string checkIfCertExists ()
{
return _secure && _cert == null
@ -400,6 +555,7 @@ namespace WebSocketSharp.Server
private void init ()
{
_authSchemes = AuthenticationSchemes.Anonymous;
_listener = new TcpListener (_address, _port);
_logger = new Logger ();
_serviceHosts = new WebSocketServiceHostManager (_logger);
@ -407,32 +563,17 @@ namespace WebSocketSharp.Server
_sync = new object ();
}
private void processRequestAsync (TcpClient client)
{
WaitCallback callback = state =>
{
try {
acceptWebSocket (client.GetWebSocketContext (_secure, _cert));
}
catch (Exception ex)
{
_logger.Fatal (ex.ToString ());
client.Close ();
}
};
ThreadPool.QueueUserWorkItem (callback);
}
private void receiveRequest ()
{
while (true)
{
while (true) {
try {
processRequestAsync (_listener.AcceptTcpClient ());
acceptRequestAsync (_listener.AcceptTcpClient ());
}
catch (SocketException ex) {
_logger.Warn (String.Format ("Receiving has been stopped.\nreason: {0}.", ex.Message));
_logger.Warn (
String.Format (
"Receiving has been stopped.\nreason: {0}.", ex.Message));
break;
}
catch (Exception ex) {
@ -445,7 +586,7 @@ namespace WebSocketSharp.Server
abort ();
}
private void startReceiveRequestThread ()
private void startReceiving ()
{
_receiveRequestThread = new Thread (new ThreadStart (receiveRequest));
_receiveRequestThread.IsBackground = true;
@ -463,8 +604,7 @@ namespace WebSocketSharp.Server
if (!uriString.TryCreateWebSocketUri (out result, out message))
return false;
if (result.PathAndQuery != "/")
{
if (result.PathAndQuery != "/") {
result = null;
message = "Must not contain the path or query component: " + uriString;
@ -479,18 +619,21 @@ namespace WebSocketSharp.Server
#region Public Methods
/// <summary>
/// Adds the specified typed WebSocket service with the specified <paramref name="servicePath"/>.
/// Adds the specified typed WebSocket service with the specified
/// <paramref name="servicePath"/>.
/// </summary>
/// <remarks>
/// This method converts <paramref name="servicePath"/> to URL-decoded string and
/// removes <c>'/'</c> from tail end of <paramref name="servicePath"/>.
/// This method converts <paramref name="servicePath"/> to URL-decoded string
/// and removes <c>'/'</c> from tail end of <paramref name="servicePath"/>.
/// </remarks>
/// <param name="servicePath">
/// A <see cref="string"/> that contains an absolute path to the WebSocket service.
/// A <see cref="string"/> that contains an absolute path to the WebSocket
/// service.
/// </param>
/// <typeparam name="TWithNew">
/// The type of the WebSocket service. The TWithNew must inherit the <see cref="WebSocketService"/>
/// class and must have a public parameterless constructor.
/// The type of the WebSocket service. The TWithNew must inherit the
/// <see cref="WebSocketService"/> class and must have a public parameterless
/// constructor.
/// </typeparam>
public void AddWebSocketService<TWithNew> (string servicePath)
where TWithNew : WebSocketService, new ()
@ -499,42 +642,50 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Adds the specified typed WebSocket service with the specified <paramref name="servicePath"/> and
/// <paramref name="serviceConstructor"/>.
/// Adds the specified typed WebSocket service with the specified
/// <paramref name="servicePath"/> and <paramref name="serviceConstructor"/>.
/// </summary>
/// <remarks>
/// <para>
/// This method converts <paramref name="servicePath"/> to URL-decoded string and
/// removes <c>'/'</c> from tail end of <paramref name="servicePath"/>.
/// This method converts <paramref name="servicePath"/> to URL-decoded
/// string and removes <c>'/'</c> from tail end of
/// <paramref name="servicePath"/>.
/// </para>
/// <para>
/// <paramref name="serviceConstructor"/> returns a initialized specified typed WebSocket service
/// instance.
/// <paramref name="serviceConstructor"/> returns a initialized specified
/// typed WebSocket service instance.
/// </para>
/// </remarks>
/// <param name="servicePath">
/// A <see cref="string"/> that contains an absolute path to the WebSocket service.
/// A <see cref="string"/> that contains an absolute path to the WebSocket
/// service.
/// </param>
/// <param name="serviceConstructor">
/// A Func&lt;T&gt; delegate that references the method used to initialize a new WebSocket service
/// instance (a new WebSocket session).
/// A Func&lt;T&gt; delegate that references the method used to initialize
/// a new WebSocket service instance (a new WebSocket session).
/// </param>
/// <typeparam name="T">
/// The type of the WebSocket service. The T must inherit the <see cref="WebSocketService"/> class.
/// The type of the WebSocket service. The T must inherit the
/// <see cref="WebSocketService"/> class.
/// </typeparam>
public void AddWebSocketService<T> (string servicePath, Func<T> serviceConstructor)
where T : WebSocketService
{
var msg = servicePath.CheckIfValidServicePath () ??
(serviceConstructor == null ? "'serviceConstructor' must not be null." : null);
(serviceConstructor == null
? "'serviceConstructor' must not be null."
: null);
if (msg != null) {
_logger.Error (
String.Format ("{0}\nservice path: {1}", msg, servicePath ?? ""));
if (msg != null)
{
_logger.Error (String.Format ("{0}\nservice path: {1}", msg, servicePath ?? ""));
return;
}
var host = new WebSocketServiceHost<T> (servicePath, serviceConstructor, _logger);
var host = new WebSocketServiceHost<T> (
servicePath, serviceConstructor, _logger);
if (!KeepClean)
host.KeepClean = false;
@ -542,24 +693,28 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Removes the WebSocket service with the specified <paramref name="servicePath"/>.
/// Removes the WebSocket service with the specified
/// <paramref name="servicePath"/>.
/// </summary>
/// <remarks>
/// This method converts <paramref name="servicePath"/> to URL-decoded string and
/// removes <c>'/'</c> from tail end of <paramref name="servicePath"/>.
/// This method converts <paramref name="servicePath"/> to URL-decoded string
/// and removes <c>'/'</c> from tail end of <paramref name="servicePath"/>.
/// </remarks>
/// <returns>
/// <c>true</c> if the WebSocket service is successfully found and removed; otherwise, <c>false</c>.
/// <c>true</c> if the WebSocket service is successfully found and removed;
/// otherwise, <c>false</c>.
/// </returns>
/// <param name="servicePath">
/// A <see cref="string"/> that contains an absolute path to the WebSocket service to find.
/// A <see cref="string"/> that contains an absolute path to the WebSocket
/// service to find.
/// </param>
public bool RemoveWebSocketService (string servicePath)
{
var msg = servicePath.CheckIfValidServicePath ();
if (msg != null)
{
_logger.Error (String.Format ("{0}\nservice path: {1}", msg, servicePath ?? ""));
if (msg != null) {
_logger.Error (
String.Format ("{0}\nservice path: {1}", msg, servicePath));
return false;
}
@ -567,22 +722,23 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Starts to receive the WebSocket connection requests.
/// Starts receiving the WebSocket connection requests.
/// </summary>
public void Start ()
{
lock (_sync)
{
lock (_sync) {
var msg = _state.CheckIfStopped () ?? checkIfCertExists ();
if (msg != null)
{
_logger.Error (String.Format ("{0}\nstate: {1}\nsecure: {2}", msg, _state, _secure));
if (msg != null) {
_logger.Error (
String.Format (
"{0}\nstate: {1}\nsecure: {2}", msg, _state, _secure));
return;
}
_serviceHosts.Start ();
_listener.Start ();
startReceiveRequestThread ();
startReceiving ();
_state = ServerState.START;
}
@ -593,11 +749,9 @@ namespace WebSocketSharp.Server
/// </summary>
public void Stop ()
{
lock (_sync)
{
lock (_sync) {
var msg = _state.CheckIfStarted ();
if (msg != null)
{
if (msg != null) {
_logger.Error (String.Format ("{0}\nstate: {1}", msg, _state));
return;
}
@ -606,17 +760,18 @@ namespace WebSocketSharp.Server
}
stopListener (5000);
_serviceHosts.Stop (new byte []{}, true);
_serviceHosts.Stop (new byte [0], true);
_state = ServerState.STOP;
}
/// <summary>
/// Stops receiving the WebSocket connection requests with the specified <see cref="ushort"/> and
/// <see cref="string"/>.
/// Stops receiving the WebSocket connection requests with the specified
/// <see cref="ushort"/> and <see cref="string"/>.
/// </summary>
/// <param name="code">
/// A <see cref="ushort"/> that contains a status code indicating the reason for stop.
/// A <see cref="ushort"/> that contains a status code indicating the reason
/// for stop.
/// </param>
/// <param name="reason">
/// A <see cref="string"/> that contains the reason for stop.
@ -624,16 +779,15 @@ namespace WebSocketSharp.Server
public void Stop (ushort code, string reason)
{
byte [] data = null;
lock (_sync)
{
lock (_sync) {
var msg = _state.CheckIfStarted () ??
code.CheckIfValidCloseStatusCode () ??
(data = code.Append (reason)).CheckIfValidCloseData ();
if (msg != null)
{
_logger.Error (String.Format (
"{0}\nstate: {1}\ncode: {2}\nreason: {3}", msg, _state, code, reason));
if (msg != null) {
_logger.Error (
String.Format (
"{0}\nstate: {1}\ncode: {2}\nreason: {3}", msg, _state, code, reason));
return;
}
@ -648,12 +802,12 @@ namespace WebSocketSharp.Server
}
/// <summary>
/// Stops receiving the WebSocket connection requests with the specified <see cref="CloseStatusCode"/>
/// and <see cref="string"/>.
/// Stops receiving the WebSocket connection requests with the specified
/// <see cref="CloseStatusCode"/> and <see cref="string"/>.
/// </summary>
/// <param name="code">
/// One of the <see cref="CloseStatusCode"/> values that represent the status codes indicating
/// the reasons for stop.
/// One of the <see cref="CloseStatusCode"/> values that represent the status
/// codes indicating the reasons for stop.
/// </param>
/// <param name="reason">
/// A <see cref="string"/> that contains the reason for stop.
@ -661,14 +815,14 @@ namespace WebSocketSharp.Server
public void Stop (CloseStatusCode code, string reason)
{
byte [] data = null;
lock (_sync)
{
lock (_sync) {
var msg = _state.CheckIfStarted () ??
(data = ((ushort) code).Append (reason)).CheckIfValidCloseData ();
if (msg != null)
{
_logger.Error (String.Format ("{0}\nstate: {1}\nreason: {2}", msg, _state, reason));
if (msg != null) {
_logger.Error (
String.Format ("{0}\nstate: {1}\nreason: {2}", msg, _state, reason));
return;
}

@ -3,12 +3,14 @@
* WebSocket.cs
*
* A C# implementation of the WebSocket interface.
* This code derived from WebSocket.java (http://github.com/adamac/Java-WebSocket-client).
*
* This code is derived from WebSocket.java
* (http://github.com/adamac/Java-WebSocket-client).
*
* The MIT License
*
* Copyright (c) 2009 Adam MacBeth
* Copyright (c) 2010-2013 sta.blockhead
* Copyright (c) 2010-2014 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -51,8 +53,9 @@ namespace WebSocketSharp
/// Implements the WebSocket interface.
/// </summary>
/// <remarks>
/// The WebSocket class provides a set of methods and properties for two-way communication
/// using the WebSocket protocol (<see href="http://tools.ietf.org/html/rfc6455">RFC 6455</see>).
/// The WebSocket class provides a set of methods and properties for two-way
/// communication using the WebSocket protocol
/// (<see href="http://tools.ietf.org/html/rfc6455">RFC 6455</see>).
/// </remarks>
public class WebSocket : IDisposable
{
@ -65,6 +68,7 @@ namespace WebSocketSharp
#region Private Fields
private AuthenticationChallenge _authChallenge;
private string _base64key;
private RemoteCertificateValidationCallback
_certValidationCallback;
@ -75,12 +79,13 @@ namespace WebSocketSharp
private CookieCollection _cookies;
private Func<CookieCollection, CookieCollection, bool>
_cookiesValidation;
private WsCredential _credentials;
private NetworkCredential _credentials;
private string _extensions;
private AutoResetEvent _exitReceiving;
private object _forClose;
private object _forSend;
private volatile Logger _logger;
private uint _nonceCount;
private string _origin;
private bool _preAuth;
private string _protocol;
@ -109,8 +114,6 @@ namespace WebSocketSharp
_extensions = String.Empty;
_forClose = new object ();
_forSend = new object ();
_origin = String.Empty;
_preAuth = false;
_protocol = String.Empty;
_readyState = WebSocketState.CONNECTING;
}
@ -119,20 +122,20 @@ namespace WebSocketSharp
#region Internal Constructors
internal WebSocket (HttpListenerWebSocketContext context)
internal WebSocket (HttpListenerWebSocketContext context, Logger logger)
: this ()
{
_stream = context.Stream;
_closeContext = () => context.Close ();
init (context);
_closeContext = context.Close;
init (context, logger);
}
internal WebSocket (TcpListenerWebSocketContext context)
internal WebSocket (TcpListenerWebSocketContext context, Logger logger)
: this ()
{
_stream = context.Stream;
_closeContext = () => context.Close ();
init (context);
_closeContext = context.Close;
init (context, logger);
}
#endregion
@ -140,14 +143,15 @@ namespace WebSocketSharp
#region Public Constructors
/// <summary>
/// Initializes a new instance of the <see cref="WebSocket"/> class with the specified WebSocket URL
/// and subprotocols.
/// Initializes a new instance of the <see cref="WebSocket"/> class with the
/// specified WebSocket URL and subprotocols.
/// </summary>
/// <param name="url">
/// A <see cref="string"/> that contains a WebSocket URL to connect.
/// </param>
/// <param name="protocols">
/// An array of <see cref="string"/> that contains the WebSocket subprotocols if any.
/// An array of <see cref="string"/> that contains the WebSocket subprotocols
/// if any.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="url"/> is <see langword="null"/>.
@ -173,12 +177,13 @@ namespace WebSocketSharp
}
/// <summary>
/// Initializes a new instance of the <see cref="WebSocket"/> class with the specified WebSocket URL,
/// OnOpen, OnMessage, OnError, OnClose event handlers and subprotocols.
/// Initializes a new instance of the <see cref="WebSocket"/> class with the
/// specified WebSocket URL, OnOpen, OnMessage, OnError, OnClose event
/// handlers and subprotocols.
/// </summary>
/// <remarks>
/// This constructor initializes a new instance of the <see cref="WebSocket"/> class and
/// establishes a WebSocket connection.
/// This constructor initializes a new instance of the <see cref="WebSocket"/>
/// class and establishes a WebSocket connection.
/// </remarks>
/// <param name="url">
/// A <see cref="string"/> that contains a WebSocket URL to connect.
@ -196,7 +201,8 @@ namespace WebSocketSharp
/// An <see cref="OnClose"/> event handler.
/// </param>
/// <param name="protocols">
/// An array of <see cref="string"/> that contains the WebSocket subprotocols if any.
/// An array of <see cref="string"/> that contains the WebSocket subprotocols
/// if any.
/// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="url"/> is <see langword="null"/>.
@ -237,7 +243,8 @@ namespace WebSocketSharp
internal bool IsOpened {
get {
return _readyState == WebSocketState.OPEN || _readyState == WebSocketState.CLOSING;
return _readyState == WebSocketState.OPEN ||
_readyState == WebSocketState.CLOSING;
}
}
@ -246,11 +253,13 @@ namespace WebSocketSharp
#region Public Properties
/// <summary>
/// Gets or sets the compression method used to compress the payload data of the WebSocket Data frame.
/// Gets or sets the compression method used to compress the payload data of
/// the WebSocket Data frame.
/// </summary>
/// <value>
/// One of the <see cref="CompressionMethod"/> values that indicates the compression method to use.
/// The default is <see cref="CompressionMethod.NONE"/>.
/// One of the <see cref="CompressionMethod"/> values that represents the
/// compression method to use.
/// The default value is <see cref="CompressionMethod.NONE"/>.
/// </value>
public CompressionMethod Compression {
get {
@ -258,9 +267,13 @@ namespace WebSocketSharp
}
set {
if (IsOpened)
{
var msg = "A WebSocket connection has already been established.";
var msg = !_client
? "Set operation of Compression isn't available as a server."
: IsOpened
? "A WebSocket connection has already been established."
: null;
if (msg != null) {
_logger.Error (msg);
error (msg);
@ -272,16 +285,15 @@ namespace WebSocketSharp
}
/// <summary>
/// Gets the cookies used in the WebSocket opening handshake.
/// Gets the cookies used in the WebSocket connection request.
/// </summary>
/// <value>
/// An IEnumerable&lt;Cookie&gt; interface that provides an enumerator which supports the iteration
/// over the collection of cookies.
/// An IEnumerable&lt;Cookie&gt; interface that provides an enumerator which
/// supports the iteration over the collection of cookies.
/// </value>
public IEnumerable<Cookie> Cookies {
get {
lock (_cookies.SyncRoot)
{
lock (_cookies.SyncRoot) {
return from Cookie cookie in _cookies
select cookie;
}
@ -292,9 +304,10 @@ namespace WebSocketSharp
/// Gets the credentials for HTTP authentication (Basic/Digest).
/// </summary>
/// <value>
/// A <see cref="WsCredential"/> that contains the credentials for HTTP authentication.
/// A <see cref="NetworkCredential"/> that represents the credentials for
/// HTTP authentication. The default value is <see langword="null"/>.
/// </value>
public WsCredential Credentials {
public NetworkCredential Credentials {
get {
return _credentials;
}
@ -304,7 +317,8 @@ namespace WebSocketSharp
/// Gets the WebSocket extensions selected by the server.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the extensions if any. The default is <see cref="String.Empty"/>.
/// A <see cref="string"/> that represents the WebSocket extensions if any.
/// The default value is <see cref="String.Empty"/>.
/// </value>
public string Extensions {
get {
@ -340,9 +354,9 @@ namespace WebSocketSharp
/// Gets the logging functions.
/// </summary>
/// <remarks>
/// The default logging level is the <see cref="LogLevel.ERROR"/>.
/// If you want to change the current logging level, you set the <c>Log.Level</c> property
/// to one of the <see cref="LogLevel"/> values which you want.
/// The default logging level is the <see cref="LogLevel.ERROR"/>. If you
/// change the current logging level, you set the <c>Log.Level</c> property
/// to any of the <see cref="LogLevel"/> values.
/// </remarks>
/// <value>
/// A <see cref="Logger"/> that provides the logging functions.
@ -361,19 +375,22 @@ namespace WebSocketSharp
}
/// <summary>
/// Gets or sets the value of the Origin header used in the WebSocket opening handshake.
/// Gets or sets the value of the Origin header used in the WebSocket
/// connection request.
/// </summary>
/// <remarks>
/// A <see cref="WebSocket"/> instance does not send the Origin header in the WebSocket opening handshake
/// if the value of this property is <see cref="String.Empty"/>.
/// The <see cref="WebSocket"/> sends the Origin header if this property has
/// any.
/// </remarks>
/// <value>
/// <para>
/// A <see cref="string"/> that contains the value of the <see href="http://tools.ietf.org/html/rfc6454#section-7">HTTP Origin header</see> to send.
/// The default is <see cref="String.Empty"/>.
/// A <see cref="string"/> that represents the value of the
/// <see href="http://tools.ietf.org/html/rfc6454#section-7">HTTP Origin
/// header</see> to send. The default value is <see langword="null"/>.
/// </para>
/// <para>
/// The value of the Origin header has the following syntax: <c>&lt;scheme&gt;://&lt;host&gt;[:&lt;port&gt;]</c>
/// The Origin header has the following syntax:
/// <c>&lt;scheme&gt;://&lt;host&gt;[:&lt;port&gt;]</c>
/// </para>
/// </value>
public string Origin {
@ -383,24 +400,22 @@ namespace WebSocketSharp
set {
string msg = null;
if (IsOpened)
{
if (!_client)
msg = "Set operation of Origin isn't available as a server.";
else if (IsOpened)
msg = "A WebSocket connection has already been established.";
}
else if (value.IsNullOrEmpty ())
{
_origin = String.Empty;
else if (value.IsNullOrEmpty ()) {
_origin = value;
return;
}
else
{
var origin = new Uri (value);
if (!origin.IsAbsoluteUri || origin.Segments.Length > 1)
msg = "The syntax of value of Origin must be '<scheme>://<host>[:<port>]'.";
else {
Uri origin;
if (!Uri.TryCreate (value, UriKind.Absolute, out origin) ||
origin.Segments.Length > 1)
msg = "The syntax of Origin must be '<scheme>://<host>[:<port>]'.";
}
if (msg != null)
{
if (msg != null) {
_logger.Error (msg);
error (msg);
@ -415,7 +430,8 @@ namespace WebSocketSharp
/// Gets the WebSocket subprotocol selected by the server.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the subprotocol if any. The default is <see cref="String.Empty"/>.
/// A <see cref="string"/> that represents the subprotocol if any.
/// The default value is <see cref="String.Empty"/>.
/// </value>
public string Protocol {
get {
@ -437,15 +453,17 @@ namespace WebSocketSharp
}
/// <summary>
/// Gets or sets the callback used to validate the certificate supplied by the server.
/// Gets or sets the callback used to validate the certificate supplied by
/// the server.
/// </summary>
/// <remarks>
/// If the value of this property is <see langword="null"/>, the validation does nothing
/// with the server certificate, always returns valid.
/// If the value of this property is <see langword="null"/>, the validation
/// does nothing with the server certificate, always returns valid.
/// </remarks>
/// <value>
/// A <see cref="RemoteCertificateValidationCallback"/> delegate that references the method(s)
/// used to validate the server certificate. The default is <see langword="null"/>.
/// A <see cref="RemoteCertificateValidationCallback"/> delegate that
/// references the method(s) used to validate the server certificate.
/// The default value is <see langword="null"/>.
/// </value>
public RemoteCertificateValidationCallback ServerCertificateValidationCallback {
get {
@ -453,6 +471,19 @@ namespace WebSocketSharp
}
set {
var msg = !_client
? "Set operation of ServerCertificateValidationCallback isn't available as a server."
: IsOpened
? "A WebSocket connection has already been established."
: null;
if (msg != null) {
_logger.Error (msg);
error (msg);
return;
}
_certValidationCallback = value;
}
}
@ -461,7 +492,7 @@ namespace WebSocketSharp
/// Gets the WebSocket URL to connect.
/// </summary>
/// <value>
/// A <see cref="Uri"/> that contains the WebSocket URL to connect.
/// A <see cref="Uri"/> that represents the WebSocket URL to connect.
/// </value>
public Uri Url {
get {
@ -469,8 +500,7 @@ namespace WebSocketSharp
}
internal set {
if (_readyState == WebSocketState.CONNECTING && !_client)
_uri = value;
_uri = value;
}
}
@ -684,6 +714,18 @@ namespace WebSocketSharp
return Convert.ToBase64String (src);
}
// As client
private string createExtensionsRequest ()
{
var extensions = new StringBuilder (64);
if (_compression != CompressionMethod.NONE)
extensions.Append (_compression.ToCompressionExtension ());
return extensions.Length > 0
? extensions.ToString ()
: String.Empty;
}
// As client
private HandshakeRequest createHandshakeRequest ()
{
@ -693,24 +735,35 @@ namespace WebSocketSharp
: _uri.Authority;
var req = new HandshakeRequest (path);
req.AddHeader ("Host", host);
var headers = req.Headers;
headers ["Host"] = host;
if (_origin.Length > 0)
req.AddHeader ("Origin", _origin);
if (!_origin.IsNullOrEmpty ())
headers ["Origin"] = _origin;
req.AddHeader ("Sec-WebSocket-Key", _base64key);
headers ["Sec-WebSocket-Key"] = _base64key;
if (!_protocols.IsNullOrEmpty ())
req.AddHeader ("Sec-WebSocket-Protocol", _protocols);
headers ["Sec-WebSocket-Protocol"] = _protocols;
var extensions = createRequestExtensions ();
var extensions = createExtensionsRequest ();
if (extensions.Length > 0)
req.AddHeader ("Sec-WebSocket-Extensions", extensions);
headers ["Sec-WebSocket-Extensions"] = extensions;
req.AddHeader ("Sec-WebSocket-Version", _version);
headers ["Sec-WebSocket-Version"] = _version;
if (_preAuth && _credentials != null)
req.SetAuthorization (new AuthenticationResponse (_credentials));
AuthenticationResponse authRes = null;
if (_authChallenge != null && _credentials != null) {
authRes = new AuthenticationResponse (
_authChallenge, _credentials, _nonceCount);
_nonceCount = authRes.NonceCount;
}
else if (_preAuth)
authRes = new AuthenticationResponse (_credentials);
if (authRes != null)
headers ["Authorization"] = authRes.ToString ();
if (_cookies.Count > 0)
req.SetCookies (_cookies);
@ -721,14 +774,16 @@ namespace WebSocketSharp
// As server
private HandshakeResponse createHandshakeResponse ()
{
var res = new HandshakeResponse ();
res.AddHeader ("Sec-WebSocket-Accept", createResponseKey ());
var res = new HandshakeResponse (HttpStatusCode.SwitchingProtocols);
var headers = res.Headers;
headers ["Sec-WebSocket-Accept"] = createResponseKey ();
if (_protocol.Length > 0)
res.AddHeader ("Sec-WebSocket-Protocol", _protocol);
headers ["Sec-WebSocket-Protocol"] = _protocol;
if (_extensions.Length > 0)
res.AddHeader ("Sec-WebSocket-Extensions", _extensions);
headers ["Sec-WebSocket-Extensions"] = _extensions;
if (_cookies.Count > 0)
res.SetCookies (_cookies);
@ -740,23 +795,11 @@ namespace WebSocketSharp
private HandshakeResponse createHandshakeResponse (HttpStatusCode code)
{
var res = HandshakeResponse.CreateCloseResponse (code);
res.AddHeader ("Sec-WebSocket-Version", _version);
res.Headers ["Sec-WebSocket-Version"] = _version;
return res;
}
// As client
private string createRequestExtensions ()
{
var extensions = new StringBuilder (64);
if (_compression != CompressionMethod.NONE)
extensions.Append (_compression.ToCompressionExtension ());
return extensions.Length > 0
? extensions.ToString ()
: String.Empty;
}
private string createResponseKey ()
{
var buffer = new StringBuilder (_base64key, 64);
@ -808,12 +851,12 @@ namespace WebSocketSharp
}
// As server
private void init (WebSocketContext context)
private void init (WebSocketContext context, Logger logger)
{
_context = context;
_uri = context.Path.ToUri ();
_logger = logger;
_uri = context.RequestUri;
_secure = context.IsSecureConnection;
_client = false;
}
private void open ()
@ -1168,7 +1211,7 @@ namespace WebSocketSharp
int readLen = 0;
byte [] buffer = null;
// Not fragmented
// Not fragment
if (quo == 0)
{
buffer = new byte [rem];
@ -1219,11 +1262,21 @@ namespace WebSocketSharp
{
var req = createHandshakeRequest ();
var res = sendHandshakeRequest (req);
if (!_preAuth && res.IsUnauthorized && _credentials != null)
{
var challenge = res.AuthChallenge;
req.SetAuthorization (new AuthenticationResponse (_credentials, challenge));
res = sendHandshakeRequest (req);
if (res.IsUnauthorized) {
_authChallenge = res.AuthChallenge;
if (_credentials != null &&
(!_preAuth || _authChallenge.Scheme == "digest")) {
if (res.Headers.Contains ("Connection", "close")) {
closeClientResources ();
setClientStream ();
}
var authRes = new AuthenticationResponse (
_authChallenge, _credentials, _nonceCount);
_nonceCount = authRes.NonceCount;
req.Headers ["Authorization"] = authRes.ToString ();
res = sendHandshakeRequest (req);
}
}
return res;
@ -1316,16 +1369,22 @@ namespace WebSocketSharp
#region Internal Methods
// As server
internal void Close (HttpStatusCode code)
internal void Close (HandshakeResponse response)
{
_readyState = WebSocketState.CLOSING;
send (createHandshakeResponse (code));
send (response);
closeServerResources ();
_readyState = WebSocketState.CLOSED;
}
// As server
internal void Close (HttpStatusCode code)
{
Close (createHandshakeResponse (code));
}
// As server
internal void Close (CloseEventArgs args, byte [] frameAsBytes, int waitTimeOut)
{
@ -1824,78 +1883,79 @@ namespace WebSocketSharp
}
/// <summary>
/// Sets a <see cref="Cookie"/> used in the WebSocket opening handshake.
/// Sets a <see cref="Cookie"/> used in the WebSocket connection request.
/// </summary>
/// <param name="cookie">
/// A <see cref="Cookie"/> that contains an HTTP Cookie to set.
/// A <see cref="Cookie"/> that represents an HTTP Cookie to set.
/// </param>
public void SetCookie (Cookie cookie)
{
var msg = IsOpened
? "A WebSocket connection has already been established."
: cookie == null
? "'cookie' must not be null."
: null;
var msg = !_client
? "SetCookie isn't available as a server."
: IsOpened
? "A WebSocket connection has already been established."
: cookie == null
? "'cookie' must not be null."
: null;
if (msg != null)
{
if (msg != null) {
_logger.Error (msg);
error (msg);
return;
}
lock (_cookies.SyncRoot)
{
lock (_cookies.SyncRoot) {
_cookies.SetOrRemove (cookie);
}
}
/// <summary>
/// Sets the credentials for HTTP authentication (Basic/Digest).
/// Sets a pair of the <paramref name="username"/> and
/// <paramref name="password"/> for HTTP authentication (Basic/Digest).
/// </summary>
/// <param name="userName">
/// A <see cref="string"/> that contains a user name associated with the credentials.
/// <param name="username">
/// A <see cref="string"/> that represents the user name used to authenticate.
/// </param>
/// <param name="password">
/// A <see cref="string"/> that contains a password for <paramref name="userName"/> associated with the credentials.
/// A <see cref="string"/> that represents the password for
/// <paramref name="username"/> used to authenticate.
/// </param>
/// <param name="preAuth">
/// <c>true</c> if sends the credentials as a Basic authorization with the first request handshake;
/// otherwise, <c>false</c>.
/// <c>true</c> if the <see cref="WebSocket"/> sends a Basic authentication
/// credentials with the first connection request; otherwise, <c>false</c>.
/// </param>
public void SetCredentials (string userName, string password, bool preAuth)
public void SetCredentials (string username, string password, bool preAuth)
{
string msg = null;
if (IsOpened)
{
if (!_client)
msg = "SetCredentials isn't available as a server.";
else if (IsOpened)
msg = "A WebSocket connection has already been established.";
}
else if (userName == null)
{
else if (username.IsNullOrEmpty ()) {
_credentials = null;
_preAuth = false;
_logger.Warn ("Credentials was set back to the default.");
return;
}
else
{
msg = userName.Length > 0 && (userName.Contains (':') || !userName.IsText ())
? "'userName' contains an invalid character."
else {
msg = username.Contains (':') || !username.IsText ()
? "'username' contains an invalid character."
: !password.IsNullOrEmpty () && !password.IsText ()
? "'password' contains an invalid character."
: null;
}
if (msg != null)
{
if (msg != null) {
_logger.Error (msg);
error (msg);
return;
}
_credentials = new WsCredential (userName, password, _uri.PathAndQuery);
_credentials = new NetworkCredential (
username, password, _uri.PathAndQuery);
_preAuth = preAuth;
}

@ -1,119 +0,0 @@
#region License
/*
* WsCredential.cs
*
* The MIT License
*
* Copyright (c) 2013 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
using System;
namespace WebSocketSharp {
/// <summary>
/// Provides the credentials for HTTP authentication (Basic/Digest).
/// </summary>
public class WsCredential {
#region Private Fields
string _domain;
string _password;
string _userName;
#endregion
#region Internal Constructors
internal WsCredential()
{
}
internal WsCredential(string userName, string password)
: this(userName, password, null)
{
}
internal WsCredential(string userName, string password, string domain)
{
_userName = userName;
_password = password;
_domain = domain;
}
#endregion
#region Public Properties
/// <summary>
/// Gets the name of the user domain associated with the credentials.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the name of the user domain associated with the credentials.
/// Currently, returns the request uri of a WebSocket opening handshake.
/// </value>
public string Domain {
get {
return _domain ?? String.Empty;
}
internal set {
_domain = value;
}
}
/// <summary>
/// Gets the password for the user name associated with the credentials.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the password for the user name associated with the credentials.
/// </value>
public string Password {
get {
return _password ?? String.Empty;
}
internal set {
_password = value;
}
}
/// <summary>
/// Gets the user name associated with the credentials.
/// </summary>
/// <value>
/// A <see cref="string"/> that contains the user name associated with the credentials.
/// </value>
public string UserName {
get {
return _userName ?? String.Empty;
}
internal set {
_userName = value;
}
}
#endregion
}
}

@ -4,7 +4,7 @@
*
* The MIT License
*
* Copyright (c) 2010-2013 sta.blockhead
* Copyright (c) 2010-2014 sta.blockhead
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
@ -122,11 +122,11 @@ namespace WebSocketSharp
return new WsStream (netStream);
}
internal static WsStream CreateServerStream (TcpClient client, bool secure, X509Certificate cert)
internal static WsStream CreateServerStream (
TcpClient client, X509Certificate cert, bool secure)
{
var netStream = client.GetStream ();
if (secure)
{
if (secure) {
var sslStream = new SslStream (netStream, false);
sslStream.AuthenticateAsServer (cert);

@ -116,7 +116,6 @@
<Compile Include="WebSocketException.cs" />
<Compile Include="AuthenticationChallenge.cs" />
<Compile Include="AuthenticationResponse.cs" />
<Compile Include="WsCredential.cs" />
<Compile Include="LogData.cs" />
<Compile Include="LogLevel.cs" />
<Compile Include="Logger.cs" />
@ -128,6 +127,9 @@
<Compile Include="Server\IWebSocketSession.cs" />
<Compile Include="Server\WebSocketSessionManager.cs" />
<Compile Include="Server\ServerState.cs" />
<Compile Include="Net\HttpBasicIdentity.cs" />
<Compile Include="Net\HttpDigestIdentity.cs" />
<Compile Include="Net\NetworkCredential.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>

Loading…
Cancel
Save