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("nobita", "\"idiot, gunfighter\""));
//ws.SetCookie(new Cookie("dora", "tanuki")); //ws.SetCookie(new Cookie("dora", "tanuki"));
//ws.SetCredentials ("nobita", "password", false);
ws.Connect(); ws.Connect();
//Console.WriteLine("Compression: {0}", ws.Compression); //Console.WriteLine("Compression: {0}", ws.Compression);

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

@ -19,24 +19,39 @@ namespace Example3
_httpsv.Log.Level = LogLevel.TRACE; _httpsv.Log.Level = LogLevel.TRACE;
#endif #endif
_httpsv.RootPath = ConfigurationManager.AppSettings ["RootPath"]; _httpsv.RootPath = ConfigurationManager.AppSettings ["RootPath"];
//var cert = ConfigurationManager.AppSettings ["ServerCertFile"];
//var password = ConfigurationManager.AppSettings ["CertFilePassword"]; // HTTP Basic/Digest Authentication
//_httpsv.Certificate = new X509Certificate2 (cert, password); /*
_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.KeepClean = false;
_httpsv.AddWebSocketService<Echo> ("/Echo"); _httpsv.AddWebSocketService<Echo> ("/Echo");
_httpsv.AddWebSocketService<Chat> ("/Chat"); _httpsv.AddWebSocketService<Chat> ("/Chat");
//_httpsv.AddWebSocketService<Chat> ("/Chat", () => new Chat ("Anon#")); //_httpsv.AddWebSocketService<Chat> ("/Chat", () => new Chat ("Anon#"));
_httpsv.OnGet += (sender, e) => _httpsv.OnGet += (sender, e) => onGet (e);
{
onGet (e);
};
_httpsv.Start (); _httpsv.Start ();
if (_httpsv.IsListening) if (_httpsv.IsListening) {
{
Console.WriteLine ( 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) foreach (var path in _httpsv.WebSocketServices.ServicePaths)
Console.WriteLine (" {0}", path); Console.WriteLine (" {0}", path);
@ -61,8 +76,7 @@ namespace Example3
var request = eventArgs.Request; var request = eventArgs.Request;
var response = eventArgs.Response; var response = eventArgs.Response;
var content = getContent (request.RawUrl); var content = getContent (request.RawUrl);
if (content != null) if (content != null) {
{
response.WriteContent (content); response.WriteContent (content);
return; return;
} }

@ -4,8 +4,8 @@
* *
* The MIT License * 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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights * in the Software without restriction, including without limitation the rights
@ -15,7 +15,7 @@
* *
* The above copyright notice and this permission notice shall be included in * The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. * all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -27,112 +27,87 @@
#endregion #endregion
using System; using System;
using System.Collections.Specialized;
using System.Text; using System.Text;
namespace WebSocketSharp { namespace WebSocketSharp
{
internal class AuthenticationChallenge { internal class AuthenticationChallenge
{
#region Private Fields #region Private Fields
private string _algorithm; private NameValueCollection _params;
private string _domain; private string _scheme;
private string _nonce;
private string _opaque;
private string _qop;
private string _realm;
private string _scheme;
private string _stale;
#endregion #endregion
#region Private Constructors #region Internal Constructors
private AuthenticationChallenge() internal AuthenticationChallenge (string authScheme, string authParams)
{ {
_scheme = authScheme;
_params = authParams.ParseAuthParams ();
} }
#endregion #endregion
#region Public Properties #region Internal Properties
public string Algorithm { internal NameValueCollection Params {
get { get {
return _algorithm ?? String.Empty; return _params;
} }
}
#endregion
#region Public Properties
private set { public string Algorithm {
_algorithm = value; get {
return _params ["algorithm"];
} }
} }
public string Domain { public string Domain {
get { get {
return _domain ?? String.Empty; return _params ["domain"];
}
private set {
_domain = value;
} }
} }
public string Nonce { public string Nonce {
get { get {
return _nonce ?? String.Empty; return _params ["nonce"];
}
private set {
_nonce = value;
} }
} }
public string Opaque { public string Opaque {
get { get {
return _opaque ?? String.Empty; return _params ["opaque"];
}
private set {
_opaque = value;
} }
} }
public string Qop { public string Qop {
get { get {
return _qop ?? String.Empty; return _params ["qop"];
}
private set {
_qop = value;
} }
} }
public string Realm { public string Realm {
get { get {
return _realm ?? String.Empty; return _params ["realm"];
}
private set {
_realm = value;
} }
} }
public string Scheme { public string Scheme {
get { get {
return _scheme ?? String.Empty; return _scheme;
}
private set {
_scheme = value;
} }
} }
public string Stale { public string Stale {
get { get {
return _stale ?? String.Empty; return _params ["stale"];
}
private set {
_stale = value;
} }
} }
@ -140,64 +115,13 @@ namespace WebSocketSharp {
#region Public Methods #region Public Methods
public static AuthenticationChallenge Parse(string challenge) public static AuthenticationChallenge Parse (string value)
{ {
var authChallenge = new AuthenticationChallenge(); var challenge = value.Split (new char [] {' '}, 2);
if (challenge.StartsWith("basic", StringComparison.OrdinalIgnoreCase)) var scheme = challenge [0].ToLower ();
{ return scheme == "basic" || scheme == "digest"
authChallenge.Scheme = "Basic"; ? new AuthenticationChallenge (scheme, challenge [1])
authChallenge.Realm = challenge.Substring(6).GetValueInternal("=").Trim('"'); : null;
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;
} }
#endregion #endregion

@ -5,7 +5,7 @@
* The MIT License * The MIT License
* *
* Copyright (c) 2013 sta.blockhead * Copyright (c) 2013 sta.blockhead
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights * in the Software without restriction, including without limitation the rights
@ -15,7 +15,7 @@
* *
* The above copyright notice and this permission notice shall be included in * The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. * all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -27,169 +27,156 @@
#endregion #endregion
using System; using System;
using System.Security.Cryptography; using System.Collections.Specialized;
using System.Security.Principal;
using System.Text; using System.Text;
using WebSocketSharp.Net;
namespace WebSocketSharp { namespace WebSocketSharp
{
internal class AuthenticationResponse { internal class AuthenticationResponse
{
#region Private Fields #region Private Fields
private string _algorithm; private uint _nonceCount;
private string _cnonce; private NameValueCollection _params;
private string _method; private string _scheme;
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;
#endregion #endregion
#region Private Constructors #region Private Constructors
private AuthenticationResponse() private AuthenticationResponse (
string authScheme, NameValueCollection authParams)
{ {
_scheme = authScheme;
_params = authParams;
} }
#endregion #endregion
#region Public Constructors #region Internal Constructors
public AuthenticationResponse(WsCredential credential) internal AuthenticationResponse (NetworkCredential credentials)
: this ("Basic", new NameValueCollection (), credentials, 0)
{ {
_userName = credential.UserName;
_password = credential.Password;
_scheme = "Basic";
} }
public AuthenticationResponse(WsCredential credential, AuthenticationChallenge challenge) internal AuthenticationResponse (
AuthenticationChallenge challenge,
NetworkCredential credentials,
uint nonceCount)
: this (challenge.Scheme, challenge.Params, credentials, nonceCount)
{ {
_userName = credential.UserName; }
_password = credential.Password;
_scheme = challenge.Scheme; internal AuthenticationResponse (
_realm = challenge.Realm; string authScheme,
if (_scheme == "Digest") NameValueCollection authParams,
initForDigest(credential, challenge); NetworkCredential credentials,
uint nonceCount)
{
_scheme = authScheme.ToLower ();
_params = authParams;
_params ["username"] = credentials.UserName;
_params ["password"] = credentials.Password;
_params ["uri"] = credentials.Domain;
_nonceCount = nonceCount;
if (_scheme == "digest")
initAsDigest ();
} }
#endregion #endregion
#region Public Properties #region Internal Properties
public string Algorithm { internal uint NonceCount {
get { get {
return _algorithm ?? String.Empty; return _nonceCount < UInt32.MaxValue
? _nonceCount
: 0;
} }
}
private set { internal NameValueCollection Params {
_algorithm = value; get {
return _params;
} }
} }
public string Cnonce { #endregion
#region Public Properties
public string Algorithm {
get { get {
return _cnonce ?? String.Empty; return _params ["algorithm"];
} }
}
private set { public string Cnonce {
_cnonce = value; get {
return _params ["cnonce"];
} }
} }
public string Nc { public string Nc {
get { get {
return _nc ?? String.Empty; return _params ["nc"];
}
private set {
_nc = value;
} }
} }
public string Nonce { public string Nonce {
get { get {
return _nonce ?? String.Empty; return _params ["nonce"];
}
private set {
_nonce = value;
} }
} }
public string Opaque { public string Opaque {
get { get {
return _opaque ?? String.Empty; return _params ["opaque"];
} }
}
private set { public string Password {
_opaque = value; get {
return _params ["password"];
} }
} }
public string Qop { public string Qop {
get { get {
return _qop ?? String.Empty; return _params ["qop"];
}
private set {
_qop = value;
} }
} }
public string Realm { public string Realm {
get { get {
return _realm ?? String.Empty; return _params ["realm"];
}
private set {
_realm = value;
} }
} }
public string Response { public string Response {
get { get {
return _response ?? String.Empty; return _params ["response"];
}
private set {
_response = value;
} }
} }
public string Scheme { public string Scheme {
get { get {
return _scheme ?? String.Empty; return _scheme;
}
private set {
_scheme = value;
} }
} }
public string Uri { public string Uri {
get { get {
return _uri ?? String.Empty; return _params ["uri"];
}
private set {
_uri = value;
} }
} }
public string UserName { public string UserName {
get { get {
return _userName ?? String.Empty; return _params ["username"];
}
private set {
_userName = value;
} }
} }
@ -197,130 +184,77 @@ namespace WebSocketSharp {
#region Private Methods #region Private Methods
private string a1() private static bool contains (string [] array, string item)
{ {
var result = String.Format("{0}:{1}:{2}", _userName, _realm, _password); foreach (var i in array)
return _algorithm != null && _algorithm.ToLower() == "md5-sess" if (i.Trim ().ToLower () == item)
? String.Format("{0}:{1}:{2}", hash(result), _nonce, _cnonce) return true;
: result;
}
private string a2() return false;
{
return String.Format("{0}:{1}", _method, _uri);
}
private static string createNonceValue()
{
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) private void initAsDigest ()
{ {
_nonce = challenge.Nonce; var qops = _params ["qop"];
_method = "GET"; if (qops != null) {
_uri = credential.Domain; var qop = "auth";
_algorithm = challenge.Algorithm; if (contains (qops.Split (','), qop)) {
_opaque = challenge.Opaque; _params ["qop"] = qop;
foreach (var qop in challenge.Qop.Split(',')) _params ["nc"] = String.Format ("{0:x8}", ++_nonceCount);
{ _params ["cnonce"] = HttpUtility.CreateNonceValue ();
if (qop.Trim().ToLower() == "auth")
{
_qop = "auth";
_nc = "00000001";
break;
} }
else
_params ["qop"] = null;
} }
_cnonce = createNonceValue(); _params ["method"] = "GET";
_response = createRequestDigest(); _params ["response"] = HttpUtility.CreateRequestDigest (_params);
} }
private static string kd(string secret, string data) #endregion
{
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));
return "Basic " + base64UserPass; #region Public Methods
}
private string toDigestCredentials() public static AuthenticationResponse Parse (string value)
{ {
var digestResponse = new StringBuilder(64); try {
digestResponse.AppendFormat("username={0}", _userName.Quote()); var credentials = value.Split (new char [] { ' ' }, 2);
digestResponse.AppendFormat(", realm={0}", _realm.Quote()); if (credentials.Length != 2)
digestResponse.AppendFormat(", nonce={0}", _nonce.Quote()); return null;
digestResponse.AppendFormat(", uri={0}", _uri.Quote());
digestResponse.AppendFormat(", response={0}", _response.Quote()); var scheme = credentials [0].ToLower ();
if (!_algorithm.IsNullOrEmpty()) return scheme == "basic"
digestResponse.AppendFormat(", algorithm={0}", _algorithm); ? new AuthenticationResponse (
scheme, credentials [1].ParseBasicAuthResponseParams ())
if (!_opaque.IsNullOrEmpty()) : scheme == "digest"
digestResponse.AppendFormat(", opaque={0}", _opaque.Quote()); ? new AuthenticationResponse (
scheme, credentials [1].ParseAuthParams ())
if (!_qop.IsNullOrEmpty()) : null;
digestResponse.AppendFormat(", qop={0}", _qop); }
catch {
if (!_nc.IsNullOrEmpty()) }
digestResponse.AppendFormat(", nc={0}", _nc);
if (!_qop.IsNullOrEmpty())
digestResponse.AppendFormat(", cnonce={0}", _cnonce.Quote());
return "Digest " + digestResponse.ToString(); return null;
} }
#endregion public IIdentity ToIdentity ()
#region Public Methods
public static AuthenticationResponse Parse(string response)
{ {
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" return _scheme == "basic"
? toBasicCredentials() ? HttpUtility.CreateBasicAuthCredentials (
: toDigestCredentials(); _params ["username"], _params ["password"])
: _scheme == "digest"
? HttpUtility.CreateDigestAuthCredentials (_params)
: String.Empty;
} }
#endregion #endregion

@ -1,23 +1,22 @@
#region License #region License
/* /*
* Ext.cs * Ext.cs
* IsPredefinedScheme and MaybeUri methods are derived from System.Uri.cs *
* GetStatusDescription method is derived from System.Net.HttpListenerResponse.cs * Some part of this code are derived from Mono (http://www.mono-project.com).
*
* 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
* *
* The MIT License * The MIT License
* *
* 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 * Copyright (c) 2010-2013 sta.blockhead
* *
* 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
*
* System.Net.HttpListenerResponse.cs
* Copyright (C) 2003, 2005, 2009 Novell, Inc. (http://www.novell.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights * in the Software without restriction, including without limitation the rights
@ -27,7 +26,7 @@
* *
* The above copyright notice and this permission notice shall be included in * The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. * all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -279,6 +278,20 @@ namespace WebSocketSharp
: null; : 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) internal static byte [] Compress (this byte [] value, CompressionMethod method)
{ {
return method == CompressionMethod.DEFLATE return method == CompressionMethod.DEFLATE
@ -447,9 +460,9 @@ namespace WebSocketSharp
} }
internal static TcpListenerWebSocketContext GetWebSocketContext ( 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) internal static bool IsCompressionExtension (this string value)
@ -517,6 +530,57 @@ namespace WebSocketSharp
: String.Format ("\"{0}\"", value.Replace ("\"", "\\\"")); : 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) internal static byte [] ReadBytes (this Stream stream, int length)
{ {
return stream.readBytes (new byte [length], 0, 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"/>. /// Converts the specified <see cref="string"/> to a <see cref="Uri"/>.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// A <see cref="Uri"/> converted from <paramref name="uriString"/>, or <see langword="null"/> /// A <see cref="Uri"/> converted from <paramref name="uriString"/>, or
/// if <paramref name="uriString"/> is <see langword="null"/> or <see cref="String.Empty"/>. /// <see langword="null"/> if <paramref name="uriString"/> is
/// <see langword="null"/> or empty.
/// </returns> /// </returns>
/// <param name="uriString"> /// <param name="uriString">
/// A <see cref="string"/> to convert. /// A <see cref="string"/> to convert.

@ -5,7 +5,7 @@
* The MIT License * The MIT License
* *
* Copyright (c) 2012-2013 sta.blockhead * Copyright (c) 2012-2013 sta.blockhead
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights * in the Software without restriction, including without limitation the rights
@ -15,7 +15,7 @@
* *
* The above copyright notice and this permission notice shall be included in * The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. * all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -35,6 +35,13 @@ namespace WebSocketSharp
{ {
internal abstract class HandshakeBase internal abstract class HandshakeBase
{ {
#region Private Fields
private NameValueCollection _headers;
private Version _version;
#endregion
#region Protected Const Fields #region Protected Const Fields
protected const string CrLf = "\r\n"; protected const string CrLf = "\r\n";
@ -45,8 +52,8 @@ namespace WebSocketSharp
protected HandshakeBase () protected HandshakeBase ()
{ {
ProtocolVersion = HttpVersion.Version11; _version = HttpVersion.Version11;
Headers = new NameValueCollection (); _headers = new NameValueCollection ();
} }
#endregion #endregion
@ -54,37 +61,29 @@ namespace WebSocketSharp
#region Public Properties #region Public Properties
public NameValueCollection Headers { public NameValueCollection Headers {
get; protected set; get {
return _headers;
}
protected set {
_headers = value;
}
} }
public Version ProtocolVersion { public Version ProtocolVersion {
get; protected set; get {
return _version;
}
protected set {
_version = value;
}
} }
#endregion #endregion
#region Public Methods #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 () public byte [] ToByteArray ()
{ {
return Encoding.UTF8.GetBytes (ToString ()); return Encoding.UTF8.GetBytes (ToString ());

@ -4,8 +4,8 @@
* *
* The MIT License * 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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights * in the Software without restriction, including without limitation the rights
@ -15,7 +15,7 @@
* *
* The above copyright notice and this permission notice shall be included in * The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. * all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -31,7 +31,6 @@ using System.Collections.Specialized;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using WebSocketSharp.Net; using WebSocketSharp.Net;
using WebSocketSharp.Net.WebSockets;
namespace WebSocketSharp namespace WebSocketSharp
{ {
@ -39,7 +38,10 @@ namespace WebSocketSharp
{ {
#region Private Fields #region Private Fields
private string _method;
private NameValueCollection _queryString; private NameValueCollection _queryString;
private string _rawUrl;
private Uri _uri;
#endregion #endregion
@ -55,17 +57,31 @@ namespace WebSocketSharp
public HandshakeRequest (string uriString) public HandshakeRequest (string uriString)
{ {
HttpMethod = "GET"; _method = "GET";
RequestUri = uriString.ToUri (); _uri = uriString.ToUri ();
AddHeader ("User-Agent", "websocket-sharp/1.0"); _rawUrl = _uri.IsAbsoluteUri
AddHeader ("Upgrade", "websocket"); ? _uri.PathAndQuery
AddHeader ("Connection", "Upgrade"); : uriString;
var headers = Headers;
headers ["User-Agent"] = "websocket-sharp/1.0";
headers ["Upgrade"] = "websocket";
headers ["Connection"] = "Upgrade";
} }
#endregion #endregion
#region Public Properties #region Public Properties
public AuthenticationResponse AuthResponse {
get {
var response = Headers ["Authorization"];
return !response.IsNullOrEmpty ()
? AuthenticationResponse.Parse (response)
: null;
}
}
public CookieCollection Cookies { public CookieCollection Cookies {
get { get {
return Headers.GetCookies (false); return Headers.GetCookies (false);
@ -73,33 +89,36 @@ namespace WebSocketSharp
} }
public string HttpMethod { public string HttpMethod {
get; private set; get {
return _method;
}
private set {
_method = value;
}
} }
public bool IsWebSocketRequest { public bool IsWebSocketRequest {
get { get {
return HttpMethod == "GET" && var headers = Headers;
return _method == "GET" &&
ProtocolVersion >= HttpVersion.Version11 && ProtocolVersion >= HttpVersion.Version11 &&
Headers.Contains ("Upgrade", "websocket") && headers.Contains ("Upgrade", "websocket") &&
Headers.Contains ("Connection", "Upgrade"); headers.Contains ("Connection", "Upgrade");
} }
} }
public NameValueCollection QueryString { public NameValueCollection QueryString {
get { get {
if (_queryString == null) if (_queryString == null) {
{
_queryString = new NameValueCollection (); _queryString = new NameValueCollection ();
var i = RawUrl.IndexOf ('?'); var i = RawUrl.IndexOf ('?');
if (i > 0) if (i > 0) {
{
var query = RawUrl.Substring (i + 1); var query = RawUrl.Substring (i + 1);
var components = query.Split ('&'); var components = query.Split ('&');
foreach (var c in components) foreach (var c in components) {
{
var nv = c.GetNameAndValue ("="); var nv = c.GetNameAndValue ("=");
if (nv.Key != null) if (nv.Key != null) {
{
var name = nv.Key.UrlDecode (); var name = nv.Key.UrlDecode ();
var val = nv.Value.UrlDecode (); var val = nv.Value.UrlDecode ();
_queryString.Add (name, val); _queryString.Add (name, val);
@ -114,14 +133,22 @@ namespace WebSocketSharp
public string RawUrl { public string RawUrl {
get { get {
return RequestUri.IsAbsoluteUri return _rawUrl;
? RequestUri.PathAndQuery }
: RequestUri.OriginalString;
private set {
_rawUrl = value;
} }
} }
public Uri RequestUri { public Uri RequestUri {
get; private set; get {
return _uri;
}
private set {
_uri = value;
}
} }
#endregion #endregion
@ -132,10 +159,8 @@ namespace WebSocketSharp
{ {
var requestLine = request [0].Split (' '); var requestLine = request [0].Split (' ');
if (requestLine.Length != 3) if (requestLine.Length != 3)
{ throw new ArgumentException (
var msg = "Invalid HTTP Request-Line: " + request [0]; "Invalid HTTP Request-Line: " + request [0], "request");
throw new ArgumentException (msg, "request");
}
var headers = new WebHeaderCollection (); var headers = new WebHeaderCollection ();
for (int i = 1; i < request.Length; i++) for (int i = 1; i < request.Length; i++)
@ -144,8 +169,9 @@ namespace WebSocketSharp
return new HandshakeRequest { return new HandshakeRequest {
Headers = headers, Headers = headers,
HttpMethod = requestLine [0], 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) if (!sorted [i].Expired)
header.AppendFormat ("; {0}", sorted [i].ToString ()); header.AppendFormat ("; {0}", sorted [i].ToString ());
AddHeader ("Cookie", header.ToString ()); Headers ["Cookie"] = header.ToString ();
}
public void SetAuthorization (AuthenticationResponse response)
{
var credentials = response.ToString ();
AddHeader ("Authorization", credentials);
} }
public override string ToString () public override string ToString ()
{ {
var buffer = new StringBuilder (64); var buffer = new StringBuilder (64);
buffer.AppendFormat ("{0} {1} HTTP/{2}{3}", HttpMethod, RawUrl, ProtocolVersion, CrLf); buffer.AppendFormat ("{0} {1} HTTP/{2}{3}", _method, _rawUrl, ProtocolVersion, CrLf);
foreach (string key in Headers.AllKeys)
buffer.AppendFormat ("{0}: {1}{2}", key, Headers [key], CrLf); var headers = Headers;
foreach (var key in headers.AllKeys)
buffer.AppendFormat ("{0}: {1}{2}", key, headers [key], CrLf);
buffer.Append (CrLf); buffer.Append (CrLf);
return buffer.ToString (); return buffer.ToString ();

@ -4,8 +4,8 @@
* *
* The MIT License * 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 * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights * in the Software without restriction, including without limitation the rights
@ -15,7 +15,7 @@
* *
* The above copyright notice and this permission notice shall be included in * The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. * all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -35,20 +35,34 @@ namespace WebSocketSharp
{ {
internal class HandshakeResponse : HandshakeBase internal class HandshakeResponse : HandshakeBase
{ {
#region Public Constructors #region Private Fields
private string _code;
private string _reason;
#endregion
public HandshakeResponse () #region Private Constructors
: this (HttpStatusCode.SwitchingProtocols)
private HandshakeResponse ()
{ {
AddHeader ("Upgrade", "websocket");
AddHeader ("Connection", "Upgrade");
} }
#endregion
#region Public Constructors
public HandshakeResponse (HttpStatusCode code) public HandshakeResponse (HttpStatusCode code)
{ {
StatusCode = ((int) code).ToString (); _code = ((int) code).ToString ();
Reason = code.GetDescription (); _reason = code.GetDescription ();
AddHeader ("Server", "websocket-sharp/1.0");
var headers = Headers;
headers ["Server"] = "websocket-sharp/1.0";
if (code == HttpStatusCode.SwitchingProtocols) {
headers ["Upgrade"] = "websocket";
headers ["Connection"] = "Upgrade";
}
} }
#endregion #endregion
@ -72,25 +86,38 @@ namespace WebSocketSharp
public bool IsUnauthorized { public bool IsUnauthorized {
get { get {
return StatusCode == "401"; return _code == "401";
} }
} }
public bool IsWebSocketResponse { public bool IsWebSocketResponse {
get { get {
var headers = Headers;
return ProtocolVersion >= HttpVersion.Version11 && return ProtocolVersion >= HttpVersion.Version11 &&
StatusCode == "101" && _code == "101" &&
Headers.Contains ("Upgrade", "websocket") && headers.Contains ("Upgrade", "websocket") &&
Headers.Contains ("Connection", "Upgrade"); headers.Contains ("Connection", "Upgrade");
} }
} }
public string Reason { public string Reason {
get; private set; get {
return _reason;
}
private set {
_reason = value;
}
} }
public string StatusCode { public string StatusCode {
get; private set; get {
return _code;
}
private set {
_code = value;
}
} }
#endregion #endregion
@ -100,7 +127,7 @@ namespace WebSocketSharp
public static HandshakeResponse CreateCloseResponse (HttpStatusCode code) public static HandshakeResponse CreateCloseResponse (HttpStatusCode code)
{ {
var res = new HandshakeResponse (code); var res = new HandshakeResponse (code);
res.AddHeader ("Connection", "close"); res.Headers ["Connection"] = "close";
return res; return res;
} }
@ -132,16 +159,20 @@ namespace WebSocketSharp
if (cookies == null || cookies.Count == 0) if (cookies == null || cookies.Count == 0)
return; return;
var headers = Headers;
foreach (var cookie in cookies.Sorted) foreach (var cookie in cookies.Sorted)
AddHeader ("Set-Cookie", cookie.ToResponseString ()); headers.Add ("Set-Cookie", cookie.ToResponseString ());
} }
public override string ToString () public override string ToString ()
{ {
var buffer = new StringBuilder (64); var buffer = new StringBuilder (64);
buffer.AppendFormat ("HTTP/{0} {1} {2}{3}", ProtocolVersion, StatusCode, Reason, CrLf); buffer.AppendFormat (
foreach (string key in Headers.AllKeys) "HTTP/{0} {1} {2}{3}", ProtocolVersion, _code, _reason, CrLf);
buffer.AppendFormat ("{0}: {1}{2}", key, Headers [key], CrLf);
var headers = Headers;
foreach (var key in headers.AllKeys)
buffer.AppendFormat ("{0}: {1}{2}", key, headers [key], CrLf);
buffer.Append (CrLf); buffer.Append (CrLf);
return buffer.ToString (); return buffer.ToString ();

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

@ -1,32 +1,41 @@
// #region License
// EndPointListener.cs /*
// Copied from System.Net.EndPointListener.cs * EndPointListener.cs
// *
// Author: * This code is derived from System.Net.EndPointListener.cs of Mono
// Gonzalo Paniagua Javier (gonzalo@novell.com) * (http://www.mono-project.com).
// *
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com) * The MIT License
// Copyright (c) 2012-2013 sta.blockhead *
// * Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
// Permission is hereby granted, free of charge, to any person obtaining * Copyright (c) 2012-2013 sta.blockhead
// a copy of this software and associated documentation files (the *
// "Software"), to deal in the Software without restriction, including * Permission is hereby granted, free of charge, to any person obtaining a copy
// without limitation the rights to use, copy, modify, merge, publish, * of this software and associated documentation files (the "Software"), to deal
// distribute, sublicense, and/or sell copies of the Software, and to * in the Software without restriction, including without limitation the rights
// permit persons to whom the Software is furnished to do so, subject to * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// the following conditions: * 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 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 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 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;
using System.Collections; using System.Collections;
@ -40,411 +49,405 @@ using System.Threading;
namespace WebSocketSharp.Net namespace WebSocketSharp.Net
{ {
internal sealed class EndPointListener internal sealed class EndPointListener
{ {
#region Private Fields #region Private Fields
List<ListenerPrefix> _all; // host = '+' private List<ListenerPrefix> _all; // host = '+'
X509Certificate2 _cert; private X509Certificate2 _cert;
IPEndPoint _endpoint; private IPEndPoint _endpoint;
Dictionary<ListenerPrefix, HttpListener> _prefixes; private Dictionary<ListenerPrefix, HttpListener> _prefixes;
bool _secure; private bool _secure;
Socket _socket; private Socket _socket;
List<ListenerPrefix> _unhandled; // host = '*' private List<ListenerPrefix> _unhandled; // host = '*'
Dictionary<HttpConnection, HttpConnection> _unregistered; private Dictionary<HttpConnection, HttpConnection> _unregistered;
#endregion #endregion
#region Public Constructors #region Public Constructors
public EndPointListener ( public EndPointListener (
IPAddress address, IPAddress address,
int port, int port,
bool secure, bool secure,
string certFolderPath, string certFolderPath,
X509Certificate2 defaultCert X509Certificate2 defaultCert)
) {
{ if (secure) {
if (secure) { _secure = secure;
_secure = secure; _cert = getCertificate (port, certFolderPath, defaultCert);
_cert = getCertificate (port, certFolderPath, defaultCert); if (_cert == null)
if (_cert == null) throw new ArgumentException ("Server certificate not found.");
throw new ArgumentException ("Server certificate not found."); }
}
_endpoint = new IPEndPoint (address, port);
_endpoint = new IPEndPoint (address, port); _socket = new Socket (
_socket = new Socket (address.AddressFamily, SocketType.Stream, ProtocolType.Tcp); address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_socket.Bind (_endpoint); _socket.Bind (_endpoint);
_socket.Listen (500); _socket.Listen (500);
var args = new SocketAsyncEventArgs (); var args = new SocketAsyncEventArgs ();
args.UserToken = this; args.UserToken = this;
args.Completed += onAccept; args.Completed += onAccept;
_socket.AcceptAsync (args); _socket.AcceptAsync (args);
_prefixes = new Dictionary<ListenerPrefix, HttpListener> (); _prefixes = new Dictionary<ListenerPrefix, HttpListener> ();
_unregistered = new Dictionary<HttpConnection, HttpConnection> (); _unregistered = new Dictionary<HttpConnection, HttpConnection> ();
} }
#endregion #endregion
#region Private Methods #region Private Methods
private static void addSpecial (List<ListenerPrefix> prefixes, ListenerPrefix prefix) private static void addSpecial (
{ List<ListenerPrefix> prefixes, ListenerPrefix prefix)
if (prefixes == null) {
return; if (prefixes == null)
return;
foreach (var p in prefixes)
if (p.Path == prefix.Path) // TODO: code foreach (var p in prefixes)
throw new HttpListenerException (400, "Prefix already in use."); if (p.Path == prefix.Path) // TODO: code
throw new HttpListenerException (400, "Prefix already in use.");
prefixes.Add (prefix);
} prefixes.Add (prefix);
}
private void checkIfRemove ()
{ private void checkIfRemove ()
if (_prefixes.Count > 0) {
return; if (_prefixes.Count > 0)
return;
if (_unhandled != null && _unhandled.Count > 0)
return; if (_unhandled != null && _unhandled.Count > 0)
return;
if (_all != null && _all.Count > 0)
return; if (_all != null && _all.Count > 0)
return;
EndPointManager.RemoveEndPoint (this, _endpoint);
} EndPointManager.RemoveEndPoint (this, _endpoint);
}
private static RSACryptoServiceProvider createRSAFromFile (string filename)
{ private static RSACryptoServiceProvider createRSAFromFile (string filename)
var rsa = new RSACryptoServiceProvider (); {
byte[] pvk = null; var rsa = new RSACryptoServiceProvider ();
using (var fs = File.Open (filename, FileMode.Open, FileAccess.Read, FileShare.Read)) byte[] pvk = null;
{ using (var fs = File.Open (filename, FileMode.Open, FileAccess.Read, FileShare.Read)) {
pvk = new byte [fs.Length]; pvk = new byte [fs.Length];
fs.Read (pvk, 0, pvk.Length); fs.Read (pvk, 0, pvk.Length);
} }
rsa.ImportCspBlob (pvk); rsa.ImportCspBlob (pvk);
return rsa; return rsa;
} }
private static X509Certificate2 getCertificate ( private static X509Certificate2 getCertificate (
int port, string certFolderPath, X509Certificate2 defaultCert) int port, string certFolderPath, X509Certificate2 defaultCert)
{ {
try { try {
var cer = Path.Combine (certFolderPath, String.Format ("{0}.cer", port)); var cer = Path.Combine (certFolderPath, String.Format ("{0}.cer", port));
var key = Path.Combine (certFolderPath, String.Format ("{0}.key", port)); var key = Path.Combine (certFolderPath, String.Format ("{0}.key", port));
if (File.Exists (cer) && File.Exists (key)) if (File.Exists (cer) && File.Exists (key)) {
{ var cert = new X509Certificate2 (cer);
var cert = new X509Certificate2 (cer); cert.PrivateKey = createRSAFromFile (key);
cert.PrivateKey = createRSAFromFile (key);
return cert;
return cert; }
} }
} catch {
catch { }
}
return defaultCert;
return defaultCert; }
}
private static HttpListener matchFromList (
private static HttpListener matchFromList ( string host, string path, List<ListenerPrefix> list, out ListenerPrefix prefix)
string host, string path, List<ListenerPrefix> list, out ListenerPrefix prefix) {
{ prefix = null;
prefix = null; if (list == null)
if (list == null) return null;
return null;
HttpListener bestMatch = null;
HttpListener best_match = null; var bestLength = -1;
var best_length = -1; foreach (var p in list) {
foreach (var p in list) var ppath = p.Path;
{ if (ppath.Length < bestLength)
var ppath = p.Path; continue;
if (ppath.Length < best_length)
continue; if (path.StartsWith (ppath)) {
bestLength = ppath.Length;
if (path.StartsWith (ppath)) bestMatch = p.Listener;
{ prefix = p;
best_length = ppath.Length; }
best_match = p.Listener; }
prefix = p;
} return bestMatch;
} }
return best_match; private static void onAccept (object sender, EventArgs e)
} {
var args = (SocketAsyncEventArgs) e;
private static void onAccept (object sender, EventArgs e) var epListener = (EndPointListener) args.UserToken;
{ Socket accepted = null;
var args = (SocketAsyncEventArgs) e; if (args.SocketError == SocketError.Success) {
var epListener = (EndPointListener) args.UserToken; accepted = args.AcceptSocket;
Socket accepted = null; args.AcceptSocket = null;
if (args.SocketError == SocketError.Success) }
{
accepted = args.AcceptSocket; try {
args.AcceptSocket = null; epListener._socket.AcceptAsync (args);
} }
catch {
try { if (accepted != null)
epListener._socket.AcceptAsync (args); accepted.Close ();
}
catch { return;
if (accepted != null) }
accepted.Close ();
if (accepted == null)
return; return;
}
HttpConnection conn = null;
if (accepted == null) try {
return; conn = new HttpConnection (
accepted, epListener, epListener._secure, epListener._cert);
HttpConnection conn = null; lock (((ICollection) epListener._unregistered).SyncRoot) {
try { epListener._unregistered [conn] = conn;
conn = new HttpConnection (accepted, epListener, epListener._secure, epListener._cert); }
lock (((ICollection) epListener._unregistered).SyncRoot)
{ conn.BeginReadRequest ();
epListener._unregistered [conn] = conn; }
} catch {
if (conn != null) {
conn.BeginReadRequest (); conn.Close (true);
} return;
catch { }
if (conn != null)
{ accepted.Close ();
conn.Close (true); }
return; }
}
private static bool removeSpecial (
accepted.Close (); List<ListenerPrefix> prefixes, ListenerPrefix prefix)
} {
} if (prefixes == null)
return false;
private static bool removeSpecial (List<ListenerPrefix> prefixes, ListenerPrefix prefix)
{ var count = prefixes.Count;
if (prefixes == null) for (int i = 0; i < count; i++) {
return false; if (prefixes [i].Path == prefix.Path) {
prefixes.RemoveAt (i);
var count = prefixes.Count; return true;
for (int i = 0; i < count; i++) }
{ }
if (prefixes [i].Path == prefix.Path)
{ return false;
prefixes.RemoveAt (i); }
return true;
} private HttpListener searchListener (Uri uri, out ListenerPrefix prefix)
} {
prefix = null;
return false; if (uri == null)
} return null;
private HttpListener searchListener (Uri uri, out ListenerPrefix prefix) var host = uri.Host;
{ var port = uri.Port;
prefix = null; var path = HttpUtility.UrlDecode (uri.AbsolutePath);
if (uri == null) var pathSlash = path [path.Length - 1] == '/' ? path : path + "/";
return null; HttpListener bestMatch = null;
var bestLength = -1;
var host = uri.Host; if (host != null && host.Length > 0) {
var port = uri.Port; foreach (var p in _prefixes.Keys) {
var path = HttpUtility.UrlDecode (uri.AbsolutePath); var ppath = p.Path;
var path_slash = path [path.Length - 1] == '/' ? path : path + "/"; if (ppath.Length < bestLength)
HttpListener best_match = null; continue;
var best_length = -1;
if (host != null && host.Length > 0) if (p.Host != host || p.Port != port)
{ continue;
foreach (var p in _prefixes.Keys)
{ if (path.StartsWith (ppath) || pathSlash.StartsWith (ppath)) {
var ppath = p.Path; bestLength = ppath.Length;
if (ppath.Length < best_length) bestMatch = _prefixes [p];
continue; prefix = p;
}
if (p.Host != host || p.Port != port) }
continue;
if (bestLength != -1)
if (path.StartsWith (ppath) || path_slash.StartsWith (ppath)) return bestMatch;
{ }
best_length = ppath.Length;
best_match = _prefixes [p]; var list = _unhandled;
prefix = p; bestMatch = matchFromList (host, path, list, out prefix);
} if (path != pathSlash && bestMatch == null)
} bestMatch = matchFromList (host, pathSlash, list, out prefix);
if (best_length != -1) if (bestMatch != null)
return best_match; return bestMatch;
}
list = _all;
var list = _unhandled; bestMatch = matchFromList (host, path, list, out prefix);
best_match = matchFromList (host, path, list, out prefix); if (path != pathSlash && bestMatch == null)
if (path != path_slash && best_match == null) bestMatch = matchFromList (host, pathSlash, list, out prefix);
best_match = matchFromList (host, path_slash, list, out prefix);
if (bestMatch != null)
if (best_match != null) return bestMatch;
return best_match;
return null;
list = _all; }
best_match = matchFromList (host, path, list, out prefix);
if (path != path_slash && best_match == null) #endregion
best_match = matchFromList (host, path_slash, list, out prefix);
#region Internal Methods
if (best_match != null)
return best_match; internal static bool CertificateExists (int port, string certFolderPath)
{
return null; var cer = Path.Combine (certFolderPath, String.Format ("{0}.cer", port));
} var key = Path.Combine (certFolderPath, String.Format ("{0}.key", port));
#endregion return File.Exists (cer) && File.Exists (key);
}
#region Internal Methods
internal void RemoveConnection (HttpConnection connection)
internal static bool CertificateExists (int port, string certFolderPath) {
{ lock (((ICollection) _unregistered).SyncRoot)
var cer = Path.Combine (certFolderPath, String.Format ("{0}.cer", port)); _unregistered.Remove (connection);
var key = Path.Combine (certFolderPath, String.Format ("{0}.key", port)); }
return File.Exists (cer) && File.Exists (key); #endregion
}
#region Public Methods
internal void RemoveConnection (HttpConnection connection)
{ public void AddPrefix (ListenerPrefix prefix, HttpListener listener)
lock (((ICollection) _unregistered).SyncRoot) {
{ List<ListenerPrefix> current, future;
_unregistered.Remove (connection); if (prefix.Host == "*") {
} do {
} current = _unhandled;
future = current != null
#endregion ? new List<ListenerPrefix> (current)
: new List<ListenerPrefix> ();
#region Public Methods
prefix.Listener = listener;
public void AddPrefix (ListenerPrefix prefix, HttpListener listener) addSpecial (future, prefix);
{ }
List<ListenerPrefix> current, future; while (Interlocked.CompareExchange (ref _unhandled, future, current) != current);
if (prefix.Host == "*")
{ return;
do { }
current = _unhandled;
future = current != null if (prefix.Host == "+") {
? new List<ListenerPrefix> (current) do {
: new List<ListenerPrefix> (); current = _all;
prefix.Listener = listener; future = current != null
addSpecial (future, prefix); ? new List<ListenerPrefix> (current)
} while (Interlocked.CompareExchange (ref _unhandled, future, current) != current); : new List<ListenerPrefix> ();
return; prefix.Listener = listener;
} addSpecial (future, prefix);
}
if (prefix.Host == "+") while (Interlocked.CompareExchange (ref _all, future, current) != current);
{
do { return;
current = _all; }
future = current != null
? new List<ListenerPrefix> (current) Dictionary<ListenerPrefix, HttpListener> prefs, p2;
: new List<ListenerPrefix> (); do {
prefix.Listener = listener; prefs = _prefixes;
addSpecial (future, prefix); if (prefs.ContainsKey (prefix)) {
} while (Interlocked.CompareExchange (ref _all, future, current) != current); var other = prefs [prefix];
if (other != listener) // TODO: code.
return; throw new HttpListenerException (
} 400, "There's another listener for " + prefix);
Dictionary<ListenerPrefix, HttpListener> prefs, p2; return;
do { }
prefs = _prefixes;
if (prefs.ContainsKey (prefix)) p2 = new Dictionary<ListenerPrefix, HttpListener> (prefs);
{ p2 [prefix] = listener;
HttpListener other = prefs [prefix]; }
if (other != listener) // TODO: code. while (Interlocked.CompareExchange (ref _prefixes, p2, prefs) != prefs);
throw new HttpListenerException (400, "There's another listener for " + prefix); }
return; public bool BindContext (HttpListenerContext context)
} {
var req = context.Request;
p2 = new Dictionary<ListenerPrefix, HttpListener> (prefs); ListenerPrefix prefix;
p2 [prefix] = listener; var listener = searchListener (req.Url, out prefix);
} while (Interlocked.CompareExchange (ref _prefixes, p2, prefs) != prefs); if (listener == null)
} return false;
public bool BindContext (HttpListenerContext context) context.Listener = listener;
{ context.Connection.Prefix = prefix;
var req = context.Request;
ListenerPrefix prefix; return true;
var listener = searchListener (req.Url, out prefix); }
if (listener == null)
return false; public void Close ()
{
context.Listener = listener; _socket.Close ();
context.Connection.Prefix = prefix; lock (((ICollection) _unregistered).SyncRoot) {
var copy = new Dictionary<HttpConnection, HttpConnection> (_unregistered);
return true; foreach (var conn in copy.Keys)
} conn.Close (true);
public void Close () copy.Clear ();
{ _unregistered.Clear ();
_socket.Close (); }
lock (((ICollection) _unregistered).SyncRoot) }
{
var copy = new Dictionary<HttpConnection, HttpConnection> (_unregistered); public void RemovePrefix (ListenerPrefix prefix, HttpListener listener)
foreach (var conn in copy.Keys) {
conn.Close (true); List<ListenerPrefix> current, future;
if (prefix.Host == "*") {
copy.Clear (); do {
_unregistered.Clear (); current = _unhandled;
} future = current != null
} ? new List<ListenerPrefix> (current)
: new List<ListenerPrefix> ();
public void RemovePrefix (ListenerPrefix prefix, HttpListener listener)
{ if (!removeSpecial (future, prefix))
List<ListenerPrefix> current, future; break; // Prefix not found.
if (prefix.Host == "*") }
{ while (Interlocked.CompareExchange (ref _unhandled, future, current) != current);
do {
current = _unhandled; checkIfRemove ();
future = current != null return;
? new List<ListenerPrefix> (current) }
: new List<ListenerPrefix> ();
if (!removeSpecial (future, prefix)) if (prefix.Host == "+") {
break; // Prefix not found. do {
} while (Interlocked.CompareExchange (ref _unhandled, future, current) != current); current = _all;
future = current != null
checkIfRemove (); ? new List<ListenerPrefix> (current)
return; : new List<ListenerPrefix> ();
}
if (!removeSpecial (future, prefix))
if (prefix.Host == "+") break; // Prefix not found.
{ }
do { while (Interlocked.CompareExchange (ref _all, future, current) != current);
current = _all;
future = current != null checkIfRemove ();
? new List<ListenerPrefix> (current) return;
: new List<ListenerPrefix> (); }
if (!removeSpecial (future, prefix))
break; // Prefix not found. Dictionary<ListenerPrefix, HttpListener> prefs, p2;
} while (Interlocked.CompareExchange (ref _all, future, current) != current); do {
prefs = _prefixes;
checkIfRemove (); if (!prefs.ContainsKey (prefix))
return; break;
}
p2 = new Dictionary<ListenerPrefix, HttpListener> (prefs);
Dictionary<ListenerPrefix, HttpListener> prefs, p2; p2.Remove (prefix);
do { }
prefs = _prefixes; while (Interlocked.CompareExchange (ref _prefixes, p2, prefs) != prefs);
if (!prefs.ContainsKey (prefix))
break; checkIfRemove ();
}
p2 = new Dictionary<ListenerPrefix, HttpListener> (prefs);
p2.Remove (prefix); public void UnbindContext (HttpListenerContext context)
} while (Interlocked.CompareExchange (ref _prefixes, p2, prefs) != prefs); {
if (context == null || context.Listener == null)
checkIfRemove (); return;
}
context.Listener.UnregisterContext (context);
public void UnbindContext (HttpListenerContext context) }
{
if (context == null || context.Request == null) #endregion
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 #region License
// /*
// HttpListenerContext.cs * HttpListenerContext.cs
// Copied from System.Net.HttpListenerContext.cs *
// * This code is derived from System.Net.HttpListenerContext.cs of Mono
// Author: * (http://www.mono-project.com).
// Gonzalo Paniagua Javier (gonzalo@novell.com) *
// * The MIT License
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com) *
// Copyright (c) 2012-2013 sta.blockhead (sta.blockhead@gmail.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 * Permission is hereby granted, free of charge, to any person obtaining a copy
// "Software"), to deal in the Software without restriction, including * of this software and associated documentation files (the "Software"), to deal
// without limitation the rights to use, copy, modify, merge, publish, * in the Software without restriction, including without limitation the rights
// distribute, sublicense, and/or sell copies of the Software, and to * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// permit persons to whom the Software is furnished to do so, subject to * copies of the Software, and to permit persons to whom the Software is
// the following conditions: * furnished to do so, subject to the following conditions:
// *
// The above copyright notice and this permission notice shall be * The above copyright notice and this permission notice shall be included in
// included in all copies or substantial portions of the Software. * all copies or substantial portions of the Software.
// *
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * THE SOFTWARE.
// */
#endregion
#region Authors
/*
* Authors:
* Gonzalo Paniagua Javier <gonzalo@novell.com>
*/
#endregion #endregion
using System; using System;
@ -37,182 +44,184 @@ using System.Security.Principal;
using System.Text; using System.Text;
using WebSocketSharp.Net.WebSockets; using WebSocketSharp.Net.WebSockets;
namespace WebSocketSharp.Net { namespace WebSocketSharp.Net
{
/// <summary> /// <summary>
/// Provides access to the HTTP request and response objects used by the <see cref="HttpListener"/> class. /// Provides access to the HTTP request and response information used by the
/// </summary> /// <see cref="HttpListener"/>.
/// <remarks> /// </summary>
/// The HttpListenerContext class cannot be inherited. /// <remarks>
/// </remarks> /// The HttpListenerContext class cannot be inherited.
public sealed class HttpListenerContext { /// </remarks>
public sealed class HttpListenerContext
#region Private Fields {
#region Private Fields
private HttpConnection _connection;
private string _error; private HttpConnection _connection;
private int _errorStatus; private string _error;
private HttpListenerRequest _request; private int _errorStatus;
private HttpListenerResponse _response; private HttpListenerRequest _request;
private IPrincipal _user; private HttpListenerResponse _response;
private IPrincipal _user;
#endregion
#endregion
#region Internal Fields
#region Internal Fields
internal HttpListener Listener;
internal HttpListener Listener;
#endregion
#endregion
#region Constructor
#region Internal Constructors
internal HttpListenerContext (HttpConnection connection)
{ internal HttpListenerContext (HttpConnection connection)
_connection = connection; {
_errorStatus = 400; _connection = connection;
_request = new HttpListenerRequest (this); _errorStatus = 400;
_response = new HttpListenerResponse (this); _request = new HttpListenerRequest (this);
} _response = new HttpListenerResponse (this);
}
#endregion
#endregion
#region Internal Properties
#region Internal Properties
internal HttpConnection Connection {
get { internal HttpConnection Connection {
return _connection; get {
} return _connection;
} }
}
internal string ErrorMessage {
get { internal string ErrorMessage {
return _error; get {
} return _error;
}
set {
_error = value; set {
} _error = value;
} }
}
internal int ErrorStatus {
get { internal int ErrorStatus {
return _errorStatus; get {
} return _errorStatus;
}
set {
_errorStatus = value; set {
} _errorStatus = value;
} }
}
internal bool HaveError {
get { internal bool HaveError {
return _error != null; get {
} return _error != null && _error.Length > 0;
} }
}
#endregion
#endregion
#region Public Properties
#region Public Properties
/// <summary>
/// Gets the <see cref="HttpListenerRequest"/> that contains the HTTP request from a client. /// <summary>
/// </summary> /// Gets the <see cref="HttpListenerRequest"/> that contains the HTTP
/// <value> /// request information from a client.
/// A <see cref="HttpListenerRequest"/> that contains the HTTP request objects. /// </summary>
/// </value> /// <value>
public HttpListenerRequest Request { /// A <see cref="HttpListenerRequest"/> that contains the HTTP request
get { /// information.
return _request; /// </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> /// <summary>
/// <value> /// Gets the <see cref="HttpListenerResponse"/> that contains the HTTP
/// A <see cref="HttpListenerResponse"/> that contains the HTTP response objects. /// response information to send to the client in response to the client's
/// </value> /// request.
public HttpListenerResponse Response { /// </summary>
get { /// <value>
return _response; /// A <see cref="HttpListenerResponse"/> that contains the HTTP response
} /// information.
} /// </value>
public HttpListenerResponse Response {
/// <summary> get {
/// Gets the client information (identity, authentication information and security roles). return _response;
/// </summary> }
/// <value> }
/// A <see cref="IPrincipal"/> contains the client information.
/// </value> /// <summary>
public IPrincipal User { /// Gets the client information (identity, authentication information, and
get { /// security roles).
return _user; /// </summary>
} /// <value>
} /// A <see cref="IPrincipal"/> contains the client information.
/// </value>
#endregion public IPrincipal User {
get {
#region Internal Methods return _user;
}
internal void ParseAuthentication (AuthenticationSchemes expectedSchemes) }
{
if (expectedSchemes == AuthenticationSchemes.Anonymous) #endregion
return;
#region Internal Methods
// TODO: Handle NTLM/Digest modes.
var header = _request.Headers ["Authorization"]; internal void SetUser (
if (header == null || header.Length < 2) AuthenticationSchemes expectedScheme,
return; string realm,
Func<IIdentity, NetworkCredential> credentialsFinder)
var authData = header.Split (new char [] {' '}, 2); {
if (authData [0].ToLower () == "basic") var authRes = AuthenticationResponse.Parse (_request.Headers ["Authorization"]);
_user = ParseBasicAuthentication (authData [1]); if (authRes == null)
return;
// TODO: Throw if malformed -> 400 bad request.
} var identity = authRes.ToIdentity ();
if (identity == null)
internal IPrincipal ParseBasicAuthentication (string authData) return;
{
try { NetworkCredential credentials = null;
// HTTP Basic Authentication data is a formatted Base64 string. try {
var authString = Encoding.Default.GetString (Convert.FromBase64String (authData)); credentials = credentialsFinder (identity);
}
// The format is domain\username:password. catch {
// Domain is optional. }
var pos = authString.IndexOf (':'); if (credentials == null)
var user = authString.Substring (0, pos); return;
var password = authString.Substring (pos + 1);
var valid = expectedScheme == AuthenticationSchemes.Basic
// Check if there is a domain. ? ((HttpBasicIdentity) identity).Password == credentials.Password
pos = user.IndexOf ('\\'); : expectedScheme == AuthenticationSchemes.Digest
if (pos > 0) ? ((HttpDigestIdentity) identity).IsValid (
user = user.Substring (pos + 1); credentials.Password, realm, _request.HttpMethod, null)
: false;
var identity = new System.Net.HttpListenerBasicIdentity (user, password);
// TODO: What are the roles MS sets? if (valid)
return new GenericPrincipal (identity, new string [0]); _user = new GenericPrincipal (identity, credentials.Roles);
} catch { }
return null;
} #endregion
}
#region Public Method
#endregion
/// <summary>
#region Public Method /// Accepts a WebSocket connection request.
/// </summary>
/// <summary> /// <returns>
/// Accepts a WebSocket connection by the <see cref="HttpListener"/>. /// A <see cref="HttpListenerWebSocketContext"/> that contains a WebSocket
/// </summary> /// connection request information.
/// <returns> /// </returns>
/// A <see cref="HttpListenerWebSocketContext"/> that contains a WebSocket connection. /// <param name="logger">
/// </returns> /// A <see cref="Logger"/> that provides the logging functions used in the
public HttpListenerWebSocketContext AcceptWebSocket () /// WebSocket attempts.
{ /// </param>
return new HttpListenerWebSocketContext (this); public HttpListenerWebSocketContext AcceptWebSocket (Logger logger)
} {
return new HttpListenerWebSocketContext (this, logger);
#endregion }
}
#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 @@
// #region License
// ListenerAsyncResult.cs /*
// Copied from System.Net.ListenerAsyncResult.cs * ListenerAsyncResult.cs
// *
// Authors: * This code is derived from System.Net.ListenerAsyncResult.cs of Mono
// Gonzalo Paniagua Javier (gonzalo@ximian.com) * (http://www.mono-project.com).
// *
// Copyright (c) 2005 Ximian, Inc (http://www.ximian.com) * The MIT License
// *
// Permission is hereby granted, free of charge, to any person obtaining * Copyright (c) 2005 Ximian, Inc. (http://www.ximian.com)
// a copy of this software and associated documentation files (the * Copyright (c) 2012-2013 sta.blockhead
// "Software"), to deal in the Software without restriction, including *
// without limitation the rights to use, copy, modify, merge, publish, * Permission is hereby granted, free of charge, to any person obtaining a copy
// distribute, sublicense, and/or sell copies of the Software, and to * of this software and associated documentation files (the "Software"), to deal
// permit persons to whom the Software is furnished to do so, subject to * in the Software without restriction, including without limitation the rights
// the following conditions: * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// * copies of the Software, and to permit persons to whom the Software is
// The above copyright notice and this permission notice shall be * furnished to do so, subject to the following conditions:
// included in all copies or substantial portions of the Software. *
// * The above copyright notice and this permission notice shall be included in
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * all copies or substantial portions of the Software.
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF *
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 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;
using System.Net;
using System.Threading; using System.Threading;
namespace WebSocketSharp.Net { namespace WebSocketSharp.Net
{
class ListenerAsyncResult : IAsyncResult internal class ListenerAsyncResult : IAsyncResult
{ {
#region Private Static Field #region Private Fields
static WaitCallback InvokeCB = new WaitCallback (InvokeCallback); private AsyncCallback _callback;
private bool _completed;
#endregion private HttpListenerContext _context;
private Exception _exception;
#region Private Fields private ManualResetEvent _waitHandle;
private object _state;
AsyncCallback cb; private object _sync;
bool completed; private bool _syncCompleted;
HttpListenerContext context;
Exception exception; #endregion
ListenerAsyncResult forward;
ManualResetEvent handle; #region Internal Fields
object locker;
object state; internal bool EndCalled;
bool synch; internal bool InGet;
#endregion #endregion
#region Internal Fields #region Public Constructors
internal bool EndCalled; public ListenerAsyncResult (AsyncCallback callback, object state)
internal bool InGet; {
_callback = callback;
#endregion _state = state;
_sync = new object ();
#region Constructor }
public ListenerAsyncResult (AsyncCallback cb, object state) #endregion
{
this.cb = cb; #region Public Properties
this.state = state;
this.locker = new object(); public object AsyncState {
} get {
return _state;
#endregion }
}
#region Properties
public WaitHandle AsyncWaitHandle {
public object AsyncState { get {
get { lock (_sync)
if (forward != null) return _waitHandle ?? (_waitHandle = new ManualResetEvent (_completed));
return forward.AsyncState; }
}
return state;
} public bool CompletedSynchronously {
} get {
return _syncCompleted;
public WaitHandle AsyncWaitHandle { }
get { }
if (forward != null)
return forward.AsyncWaitHandle; public bool IsCompleted {
get {
lock (locker) { lock (_sync)
if (handle == null) return _completed;
handle = new ManualResetEvent (completed); }
} }
return handle; #endregion
}
} #region Private Methods
public bool CompletedSynchronously { private static void invokeCallback (object state)
get { {
if (forward != null) try {
return forward.CompletedSynchronously; var ares = (ListenerAsyncResult) state;
ares._callback (ares);
return synch; }
} catch {
} }
}
public bool IsCompleted {
get { #endregion
if (forward != null)
return forward.IsCompleted; #region Internal Methods
lock (locker) { internal void Complete (Exception exception)
return completed; {
} _exception = InGet && (exception is ObjectDisposedException)
} ? new HttpListenerException (500, "Listener closed")
} : exception;
#endregion lock (_sync) {
_completed = true;
#region Private Method if (_waitHandle != null)
_waitHandle.Set ();
static void InvokeCallback (object o)
{ if (_callback != null)
ListenerAsyncResult ares = (ListenerAsyncResult) o; ThreadPool.UnsafeQueueUserWorkItem (invokeCallback, this);
if (ares.forward != null) { }
InvokeCallback (ares.forward); }
return;
} internal void Complete (HttpListenerContext context)
{
try { Complete (context, false);
ares.cb (ares); }
} catch {
} internal void Complete (HttpListenerContext context, bool syncCompleted)
} {
var listener = context.Listener;
#endregion var scheme = listener.SelectAuthenticationScheme (context);
if (scheme == AuthenticationSchemes.None) {
#region Internal Methods context.Response.Close (HttpStatusCode.Forbidden);
listener.BeginGetContext (this);
internal void Complete (Exception exc)
{ return;
if (forward != null) { }
forward.Complete (exc);
return; var header = context.Request.Headers ["Authorization"];
} if (scheme == AuthenticationSchemes.Basic &&
(header == null ||
exception = exc; !header.StartsWith ("basic", StringComparison.OrdinalIgnoreCase))) {
if (InGet && (exc is ObjectDisposedException)) context.Response.CloseWithAuthChallenge (
exception = new HttpListenerException (500, "Listener closed"); HttpUtility.CreateBasicAuthChallenge (listener.Realm));
listener.BeginGetContext (this);
lock (locker) {
completed = true; return;
if (handle != null) }
handle.Set ();
if (scheme == AuthenticationSchemes.Digest &&
if (cb != null) (header == null ||
ThreadPool.UnsafeQueueUserWorkItem (InvokeCB, this); !header.StartsWith ("digest", StringComparison.OrdinalIgnoreCase))) {
} context.Response.CloseWithAuthChallenge (
} HttpUtility.CreateDigestAuthChallenge (listener.Realm));
listener.BeginGetContext (this);
internal void Complete (HttpListenerContext context)
{ return;
Complete (context, false); }
}
_context = context;
internal void Complete (HttpListenerContext context, bool synch) _syncCompleted = syncCompleted;
{
if (forward != null) { lock (_sync) {
forward.Complete (context, synch); _completed = true;
return; if (_waitHandle != null)
} _waitHandle.Set ();
this.synch = synch; if (_callback != null)
this.context = context; ThreadPool.UnsafeQueueUserWorkItem (invokeCallback, this);
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; internal HttpListenerContext GetContext ()
context.Response.Headers ["WWW-Authenticate"] = schemes + " realm=\"" + context.Listener.Realm + "\""; {
context.Response.OutputStream.Close (); if (_exception != null)
IAsyncResult ares = context.Listener.BeginGetContext (cb, state); throw _exception;
this.forward = (ListenerAsyncResult) ares;
lock (forward.locker) { return _context;
if (handle != null) }
forward.handle = handle;
} #endregion
}
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
}
} }

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

@ -5,7 +5,7 @@
* The MIT License * The MIT License
* *
* Copyright (c) 2012-2013 sta.blockhead * Copyright (c) 2012-2013 sta.blockhead
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights * in the Software without restriction, including without limitation the rights
@ -15,7 +15,7 @@
* *
* The above copyright notice and this permission notice shall be included in * The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. * all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -34,7 +34,8 @@ using System.Security.Principal;
namespace WebSocketSharp.Net.WebSockets namespace WebSocketSharp.Net.WebSockets
{ {
/// <summary> /// <summary>
/// Provides access to the WebSocket connection request objects received by the <see cref="HttpListener"/>. /// Provides access to the WebSocket connection request information received by
/// the <see cref="HttpListener"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// </remarks> /// </remarks>
@ -50,11 +51,12 @@ namespace WebSocketSharp.Net.WebSockets
#region Internal Constructors #region Internal Constructors
internal HttpListenerWebSocketContext (HttpListenerContext context) internal HttpListenerWebSocketContext (
HttpListenerContext context, Logger logger)
{ {
_context = context; _context = context;
_stream = WsStream.CreateServerStream (context); _stream = WsStream.CreateServerStream (context);
_websocket = new WebSocket (this); _websocket = new WebSocket (this, logger ?? new Logger ());
} }
#endregion #endregion
@ -72,10 +74,11 @@ namespace WebSocketSharp.Net.WebSockets
#region Public Properties #region Public Properties
/// <summary> /// <summary>
/// Gets the cookies used in the WebSocket opening handshake. /// Gets the cookies used in the WebSocket connection request.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="WebSocketSharp.Net.CookieCollection"/> that contains the cookies. /// A <see cref="WebSocketSharp.Net.CookieCollection"/> that contains the
/// cookies.
/// </value> /// </value>
public override CookieCollection CookieCollection { public override CookieCollection CookieCollection {
get { get {
@ -84,10 +87,10 @@ namespace WebSocketSharp.Net.WebSockets
} }
/// <summary> /// <summary>
/// Gets the HTTP headers used in the WebSocket opening handshake. /// Gets the HTTP headers used in the WebSocket connection request.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="System.Collections.Specialized.NameValueCollection"/> that contains the HTTP headers. /// A <see cref="NameValueCollection"/> that contains the HTTP headers.
/// </value> /// </value>
public override NameValueCollection Headers { public override NameValueCollection Headers {
get { get {
@ -96,10 +99,11 @@ namespace WebSocketSharp.Net.WebSockets
} }
/// <summary> /// <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> /// </summary>
/// <value> /// <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> /// </value>
public override string Host { public override string Host {
get { get {
@ -120,10 +124,12 @@ namespace WebSocketSharp.Net.WebSockets
} }
/// <summary> /// <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> /// </summary>
/// <value> /// <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> /// </value>
public override bool IsLocal { public override bool IsLocal {
get { get {
@ -135,7 +141,8 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets a value indicating whether the WebSocket connection is secured. /// Gets a value indicating whether the WebSocket connection is secured.
/// </summary> /// </summary>
/// <value> /// <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> /// </value>
public override bool IsSecureConnection { public override bool IsSecureConnection {
get { get {
@ -144,10 +151,12 @@ namespace WebSocketSharp.Net.WebSockets
} }
/// <summary> /// <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> /// </summary>
/// <value> /// <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> /// </value>
public override bool IsWebSocketRequest { public override bool IsWebSocketRequest {
get { get {
@ -156,10 +165,12 @@ namespace WebSocketSharp.Net.WebSockets
} }
/// <summary> /// <summary>
/// Gets the value of the Origin header field used in the WebSocket opening handshake. /// Gets the value of the Origin header field used in the WebSocket
/// connection request.
/// </summary> /// </summary>
/// <value> /// <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> /// </value>
public override string Origin { public override string Origin {
get { get {
@ -171,19 +182,22 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets the absolute path of the requested WebSocket URI. /// Gets the absolute path of the requested WebSocket URI.
/// </summary> /// </summary>
/// <value> /// <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> /// </value>
public override string Path { public override string Path {
get { get {
return RequestUri.GetAbsolutePath (); return _context.Request.Url.GetAbsolutePath ();
} }
} }
/// <summary> /// <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> /// </summary>
/// <value> /// <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> /// </value>
public override NameValueCollection QueryString { public override NameValueCollection QueryString {
get { get {
@ -195,22 +209,26 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets the WebSocket URI requested by the client. /// Gets the WebSocket URI requested by the client.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="Uri"/> that contains the WebSocket URI. /// A <see cref="Uri"/> that represents the WebSocket URI requested by the
/// client.
/// </value> /// </value>
public override Uri RequestUri { public override Uri RequestUri {
get { get {
return _context.Request.RawUrl.ToUri (); return _context.Request.Url;
} }
} }
/// <summary> /// <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> /// </summary>
/// <remarks> /// <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> /// </remarks>
/// <value> /// <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> /// </value>
public override string SecWebSocketKey { public override string SecWebSocketKey {
get { get {
@ -219,13 +237,15 @@ namespace WebSocketSharp.Net.WebSockets
} }
/// <summary> /// <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> /// </summary>
/// <remarks> /// <remarks>
/// The SecWebSocketProtocols property indicates the subprotocols of the WebSocket connection. /// This property represents the subprotocols of the WebSocket connection.
/// </remarks> /// </remarks>
/// <value> /// <value>
/// An IEnumerable&lt;string&gt; that contains the values of the Sec-WebSocket-Protocol header field. /// An IEnumerable&lt;string&gt; that contains the values of the
/// Sec-WebSocket-Protocol header field.
/// </value> /// </value>
public override IEnumerable<string> SecWebSocketProtocols { public override IEnumerable<string> SecWebSocketProtocols {
get { get {
@ -234,13 +254,15 @@ namespace WebSocketSharp.Net.WebSockets
} }
/// <summary> /// <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> /// </summary>
/// <remarks> /// <remarks>
/// The SecWebSocketVersion property indicates the WebSocket protocol version of the connection. /// This property represents the WebSocket protocol version of the connection.
/// </remarks> /// </remarks>
/// <value> /// <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> /// </value>
public override string SecWebSocketVersion { public override string SecWebSocketVersion {
get { get {
@ -252,7 +274,7 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets the server endpoint as an IP address and a port number. /// Gets the server endpoint as an IP address and a port number.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="System.Net.IPEndPoint"/> that contains the server endpoint. /// A <see cref="System.Net.IPEndPoint"/> that represents the server endpoint.
/// </value> /// </value>
public override System.Net.IPEndPoint ServerEndPoint { public override System.Net.IPEndPoint ServerEndPoint {
get { get {
@ -261,10 +283,11 @@ namespace WebSocketSharp.Net.WebSockets
} }
/// <summary> /// <summary>
/// Gets the client information (identity, authentication information and security roles). /// Gets the client information (identity, authentication information and
/// security roles).
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="IPrincipal"/> that contains the client information. /// A <see cref="IPrincipal"/> that represents the client information.
/// </value> /// </value>
public override IPrincipal User { public override IPrincipal User {
get { get {
@ -276,7 +299,7 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets the client endpoint as an IP address and a port number. /// Gets the client endpoint as an IP address and a port number.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="System.Net.IPEndPoint"/> that contains the client endpoint. /// A <see cref="System.Net.IPEndPoint"/> that represents the client endpoint.
/// </value> /// </value>
public override System.Net.IPEndPoint UserEndPoint { public override System.Net.IPEndPoint UserEndPoint {
get { get {
@ -285,7 +308,8 @@ namespace WebSocketSharp.Net.WebSockets
} }
/// <summary> /// <summary>
/// Gets the WebSocket instance used for two-way communication between client and server. /// Gets the WebSocket instance used for two-way communication between client
/// and server.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="WebSocketSharp.WebSocket"/>. /// A <see cref="WebSocketSharp.WebSocket"/>.
@ -305,15 +329,22 @@ namespace WebSocketSharp.Net.WebSockets
_context.Connection.Close (true); _context.Connection.Close (true);
} }
internal void Close (HttpStatusCode code)
{
_context.Response.Close (code);
}
#endregion #endregion
#region Public Methods #region Public Methods
/// <summary> /// <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> /// </summary>
/// <returns> /// <returns>
/// A <see cref="string"/> that represents the current <see cref="HttpListenerWebSocketContext"/>. /// A <see cref="string"/> that represents the current
/// <see cref="HttpListenerWebSocketContext"/>.
/// </returns> /// </returns>
public override string ToString () public override string ToString ()
{ {

@ -5,7 +5,7 @@
* The MIT License * The MIT License
* *
* Copyright (c) 2012-2013 sta.blockhead * Copyright (c) 2012-2013 sta.blockhead
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights * in the Software without restriction, including without limitation the rights
@ -15,7 +15,7 @@
* *
* The above copyright notice and this permission notice shall be included in * The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. * all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -36,7 +36,8 @@ using System.Security.Principal;
namespace WebSocketSharp.Net.WebSockets namespace WebSocketSharp.Net.WebSockets
{ {
/// <summary> /// <summary>
/// Provides access to the WebSocket connection request objects received by the <see cref="TcpListener"/>. /// Provides access to the WebSocket connection request information received by
/// the <see cref="TcpListener"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// </remarks> /// </remarks>
@ -49,19 +50,22 @@ namespace WebSocketSharp.Net.WebSockets
private HandshakeRequest _request; private HandshakeRequest _request;
private bool _secure; private bool _secure;
private WsStream _stream; private WsStream _stream;
private Uri _uri;
private IPrincipal _user;
private WebSocket _websocket; private WebSocket _websocket;
#endregion #endregion
#region Internal Constructors #region Internal Constructors
internal TcpListenerWebSocketContext (TcpClient client, bool secure, X509Certificate cert) internal TcpListenerWebSocketContext (
TcpClient client, X509Certificate cert, bool secure, Logger logger)
{ {
_client = client; _client = client;
_secure = secure; _secure = secure;
_stream = WsStream.CreateServerStream (client, secure, cert); _stream = WsStream.CreateServerStream (client, cert, secure);
_request = HandshakeRequest.Parse (_stream.ReadHandshake ()); _request = HandshakeRequest.Parse (_stream.ReadHandshake ());
_websocket = new WebSocket (this); _websocket = new WebSocket (this, logger);
} }
#endregion #endregion
@ -79,25 +83,23 @@ namespace WebSocketSharp.Net.WebSockets
#region Public Properties #region Public Properties
/// <summary> /// <summary>
/// Gets the cookies used in the WebSocket opening handshake. /// Gets the cookies used in the WebSocket connection request.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="CookieCollection"/> that contains the cookies. /// A <see cref="WebSocketSharp.Net.CookieCollection"/> that contains the
/// cookies.
/// </value> /// </value>
public override CookieCollection CookieCollection { public override CookieCollection CookieCollection {
get { get {
if (_cookies == null) return _cookies ?? (_cookies = _request.Cookies);
_cookies = _request.Cookies;
return _cookies;
} }
} }
/// <summary> /// <summary>
/// Gets the HTTP headers used in the WebSocket opening handshake. /// Gets the HTTP headers used in the WebSocket connection request.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="System.Collections.Specialized.NameValueCollection"/> that contains the HTTP headers. /// A <see cref="NameValueCollection"/> that contains the HTTP headers.
/// </value> /// </value>
public override NameValueCollection Headers { public override NameValueCollection Headers {
get { get {
@ -106,10 +108,11 @@ namespace WebSocketSharp.Net.WebSockets
} }
/// <summary> /// <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> /// </summary>
/// <value> /// <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> /// </value>
public override string Host { public override string Host {
get { get {
@ -123,20 +126,19 @@ namespace WebSocketSharp.Net.WebSockets
/// <value> /// <value>
/// <c>true</c> if the client is authenticated; otherwise, <c>false</c>. /// <c>true</c> if the client is authenticated; otherwise, <c>false</c>.
/// </value> /// </value>
/// <exception cref="NotImplementedException">
/// This property is not implemented.
/// </exception>
public override bool IsAuthenticated { public override bool IsAuthenticated {
get { get {
throw new NotImplementedException (); return _user != null && _user.Identity.IsAuthenticated;
} }
} }
/// <summary> /// <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> /// </summary>
/// <value> /// <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> /// </value>
public override bool IsLocal { public override bool IsLocal {
get { get {
@ -148,7 +150,8 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets a value indicating whether the WebSocket connection is secured. /// Gets a value indicating whether the WebSocket connection is secured.
/// </summary> /// </summary>
/// <value> /// <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> /// </value>
public override bool IsSecureConnection { public override bool IsSecureConnection {
get { get {
@ -157,10 +160,12 @@ namespace WebSocketSharp.Net.WebSockets
} }
/// <summary> /// <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> /// </summary>
/// <value> /// <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> /// </value>
public override bool IsWebSocketRequest { public override bool IsWebSocketRequest {
get { get {
@ -169,10 +174,12 @@ namespace WebSocketSharp.Net.WebSockets
} }
/// <summary> /// <summary>
/// Gets the value of the Origin header field used in the WebSocket opening handshake. /// Gets the value of the Origin header field used in the WebSocket
/// connection request.
/// </summary> /// </summary>
/// <value> /// <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> /// </value>
public override string Origin { public override string Origin {
get { get {
@ -184,7 +191,8 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets the absolute path of the requested WebSocket URI. /// Gets the absolute path of the requested WebSocket URI.
/// </summary> /// </summary>
/// <value> /// <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> /// </value>
public override string Path { public override string Path {
get { get {
@ -193,10 +201,12 @@ namespace WebSocketSharp.Net.WebSockets
} }
/// <summary> /// <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> /// </summary>
/// <value> /// <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> /// </value>
public override NameValueCollection QueryString { public override NameValueCollection QueryString {
get { get {
@ -208,22 +218,26 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets the WebSocket URI requested by the client. /// Gets the WebSocket URI requested by the client.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="Uri"/> that contains the WebSocket URI. /// A <see cref="Uri"/> that represents the WebSocket URI requested by the
/// client.
/// </value> /// </value>
public override Uri RequestUri { public override Uri RequestUri {
get { get {
return _request.RequestUri; return _uri ?? (_uri = createRequestUri ());
} }
} }
/// <summary> /// <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> /// </summary>
/// <remarks> /// <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> /// </remarks>
/// <value> /// <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> /// </value>
public override string SecWebSocketKey { public override string SecWebSocketKey {
get { get {
@ -232,13 +246,15 @@ namespace WebSocketSharp.Net.WebSockets
} }
/// <summary> /// <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> /// </summary>
/// <remarks> /// <remarks>
/// This property indicates the subprotocols of the WebSocket connection. /// This property represents the subprotocols of the WebSocket connection.
/// </remarks> /// </remarks>
/// <value> /// <value>
/// An IEnumerable&lt;string&gt; that contains the values of the Sec-WebSocket-Protocol header field. /// An IEnumerable&lt;string&gt; that contains the values of the
/// Sec-WebSocket-Protocol header field.
/// </value> /// </value>
public override IEnumerable<string> SecWebSocketProtocols { public override IEnumerable<string> SecWebSocketProtocols {
get { get {
@ -247,13 +263,15 @@ namespace WebSocketSharp.Net.WebSockets
} }
/// <summary> /// <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> /// </summary>
/// <remarks> /// <remarks>
/// The SecWebSocketVersion property indicates the WebSocket protocol version of the connection. /// This property represents the WebSocket protocol version of the connection.
/// </remarks> /// </remarks>
/// <value> /// <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> /// </value>
public override string SecWebSocketVersion { public override string SecWebSocketVersion {
get { get {
@ -265,7 +283,7 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets the server endpoint as an IP address and a port number. /// Gets the server endpoint as an IP address and a port number.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="System.Net.IPEndPoint"/> that contains the server endpoint. /// A <see cref="System.Net.IPEndPoint"/> that represents the server endpoint.
/// </value> /// </value>
public override System.Net.IPEndPoint ServerEndPoint { public override System.Net.IPEndPoint ServerEndPoint {
get { get {
@ -274,17 +292,15 @@ namespace WebSocketSharp.Net.WebSockets
} }
/// <summary> /// <summary>
/// Gets the client information (identity, authentication information and security roles). /// Gets the client information (identity, authentication information and
/// security roles).
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="IPrincipal"/> that contains the client information. /// A <see cref="IPrincipal"/> that represents the client information.
/// </value> /// </value>
/// <exception cref="NotImplementedException">
/// This property is not implemented.
/// </exception>
public override IPrincipal User { public override IPrincipal User {
get { 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. /// Gets the client endpoint as an IP address and a port number.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="System.Net.IPEndPoint"/> that contains the client endpoint. /// A <see cref="System.Net.IPEndPoint"/> that represents the client endpoint.
/// </value> /// </value>
public override System.Net.IPEndPoint UserEndPoint { public override System.Net.IPEndPoint UserEndPoint {
get { get {
@ -301,7 +317,8 @@ namespace WebSocketSharp.Net.WebSockets
} }
/// <summary> /// <summary>
/// Gets the WebSocket instance used for two-way communication between client and server. /// Gets the WebSocket instance used for two-way communication between client
/// and server.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="WebSocketSharp.WebSocket"/>. /// A <see cref="WebSocketSharp.WebSocket"/>.
@ -314,6 +331,22 @@ namespace WebSocketSharp.Net.WebSockets
#endregion #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 #region Internal Methods
internal void Close () internal void Close ()
@ -322,15 +355,64 @@ namespace WebSocketSharp.Net.WebSockets
_client.Close (); _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 #endregion
#region Public Methods #region Public Methods
/// <summary> /// <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> /// </summary>
/// <returns> /// <returns>
/// A <see cref="string"/> that represents the current <see cref="TcpListenerWebSocketContext"/>. /// A <see cref="string"/> that represents the current
/// <see cref="TcpListenerWebSocketContext"/>.
/// </returns> /// </returns>
public override string ToString () public override string ToString ()
{ {

@ -5,7 +5,7 @@
* The MIT License * The MIT License
* *
* Copyright (c) 2012-2013 sta.blockhead * Copyright (c) 2012-2013 sta.blockhead
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights * in the Software without restriction, including without limitation the rights
@ -15,7 +15,7 @@
* *
* The above copyright notice and this permission notice shall be included in * The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. * all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -34,7 +34,7 @@ using System.Security.Principal;
namespace WebSocketSharp.Net.WebSockets namespace WebSocketSharp.Net.WebSockets
{ {
/// <summary> /// <summary>
/// Provides access to the WebSocket connection request objects. /// Provides access to the WebSocket connection request information.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The WebSocketContext class is an abstract class. /// The WebSocketContext class is an abstract class.
@ -55,26 +55,28 @@ namespace WebSocketSharp.Net.WebSockets
#region Public Properties #region Public Properties
/// <summary> /// <summary>
/// Gets the cookies used in the WebSocket opening handshake. /// Gets the cookies used in the WebSocket connection request.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="WebSocketSharp.Net.CookieCollection"/> that contains the cookies. /// A <see cref="WebSocketSharp.Net.CookieCollection"/> that contains the
/// cookies.
/// </value> /// </value>
public abstract CookieCollection CookieCollection { get; } public abstract CookieCollection CookieCollection { get; }
/// <summary> /// <summary>
/// Gets the HTTP headers used in the WebSocket opening handshake. /// Gets the HTTP headers used in the WebSocket connection request.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="System.Collections.Specialized.NameValueCollection"/> that contains the HTTP headers. /// A <see cref="NameValueCollection"/> that contains the HTTP headers.
/// </value> /// </value>
public abstract NameValueCollection Headers { get; } public abstract NameValueCollection Headers { get; }
/// <summary> /// <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> /// </summary>
/// <value> /// <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> /// </value>
public abstract string Host { get; } public abstract string Host { get; }
@ -87,10 +89,12 @@ namespace WebSocketSharp.Net.WebSockets
public abstract bool IsAuthenticated { get; } public abstract bool IsAuthenticated { get; }
/// <summary> /// <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> /// </summary>
/// <value> /// <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> /// </value>
public abstract bool IsLocal { get; } public abstract bool IsLocal { get; }
@ -98,23 +102,28 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets a value indicating whether the WebSocket connection is secured. /// Gets a value indicating whether the WebSocket connection is secured.
/// </summary> /// </summary>
/// <value> /// <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> /// </value>
public abstract bool IsSecureConnection { get; } public abstract bool IsSecureConnection { get; }
/// <summary> /// <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> /// </summary>
/// <value> /// <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> /// </value>
public abstract bool IsWebSocketRequest { get; } public abstract bool IsWebSocketRequest { get; }
/// <summary> /// <summary>
/// Gets the value of the Origin header field used in the WebSocket opening handshake. /// Gets the value of the Origin header field used in the WebSocket
/// connection request.
/// </summary> /// </summary>
/// <value> /// <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> /// </value>
public abstract string Origin { get; } public abstract string Origin { get; }
@ -122,15 +131,18 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets the absolute path of the requested WebSocket URI. /// Gets the absolute path of the requested WebSocket URI.
/// </summary> /// </summary>
/// <value> /// <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> /// </value>
public abstract string Path { get; } public abstract string Path { get; }
/// <summary> /// <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> /// </summary>
/// <value> /// <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> /// </value>
public abstract NameValueCollection QueryString { get; } public abstract NameValueCollection QueryString { get; }
@ -138,40 +150,47 @@ namespace WebSocketSharp.Net.WebSockets
/// Gets the WebSocket URI requested by the client. /// Gets the WebSocket URI requested by the client.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="Uri"/> that contains the WebSocket URI. /// A <see cref="Uri"/> that represents the WebSocket URI.
/// </value> /// </value>
public abstract Uri RequestUri { get; } public abstract Uri RequestUri { get; }
/// <summary> /// <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> /// </summary>
/// <remarks> /// <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> /// </remarks>
/// <value> /// <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> /// </value>
public abstract string SecWebSocketKey { get; } public abstract string SecWebSocketKey { get; }
/// <summary> /// <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> /// </summary>
/// <remarks> /// <remarks>
/// The SecWebSocketProtocols property indicates the subprotocols of the WebSocket connection. /// This property represents the subprotocols of the WebSocket connection.
/// </remarks> /// </remarks>
/// <value> /// <value>
/// An IEnumerable&lt;string&gt; that contains the values of the Sec-WebSocket-Protocol header field. /// An IEnumerable&lt;string&gt; that contains the values of the
/// Sec-WebSocket-Protocol header field.
/// </value> /// </value>
public abstract IEnumerable<string> SecWebSocketProtocols { get; } public abstract IEnumerable<string> SecWebSocketProtocols { get; }
/// <summary> /// <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> /// </summary>
/// <remarks> /// <remarks>
/// The SecWebSocketVersion property indicates the WebSocket protocol version of the connection. /// This property represents the WebSocket protocol version of the connection.
/// </remarks> /// </remarks>
/// <value> /// <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> /// </value>
public abstract string SecWebSocketVersion { get; } 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. /// Gets the server endpoint as an IP address and a port number.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="System.Net.IPEndPoint"/> that contains the server endpoint. /// A <see cref="System.Net.IPEndPoint"/> that represents the server endpoint.
/// </value> /// </value>
public abstract System.Net.IPEndPoint ServerEndPoint { get; } public abstract System.Net.IPEndPoint ServerEndPoint { get; }
/// <summary> /// <summary>
/// Gets the client information (identity, authentication information and security roles). /// Gets the client information (identity, authentication information and
/// security roles).
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="IPrincipal"/> that contains the client information. /// A <see cref="IPrincipal"/> that represents the client information.
/// </value> /// </value>
public abstract IPrincipal User { get; } 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. /// Gets the client endpoint as an IP address and a port number.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="System.Net.IPEndPoint"/> that contains the client endpoint. /// A <see cref="System.Net.IPEndPoint"/> that represents the client endpoint.
/// </value> /// </value>
public abstract System.Net.IPEndPoint UserEndPoint { get; } public abstract System.Net.IPEndPoint UserEndPoint { get; }
/// <summary> /// <summary>
/// Gets the WebSocket instance used for two-way communication between client and server. /// Gets the WebSocket instance used for two-way communication between client
/// and server.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="WebSocketSharp.WebSocket"/>. /// A <see cref="WebSocketSharp.WebSocket"/>.

@ -7,7 +7,7 @@
* The MIT License * The MIT License
* *
* Copyright (c) 2012-2013 sta.blockhead * Copyright (c) 2012-2013 sta.blockhead
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights * in the Software without restriction, including without limitation the rights
@ -17,7 +17,7 @@
* *
* The above copyright notice and this permission notice shall be included in * The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. * all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -40,16 +40,19 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Threading; using System.Threading;
using WebSocketSharp.Net; using WebSocketSharp.Net;
using WebSocketSharp.Net.WebSockets;
namespace WebSocketSharp.Server namespace WebSocketSharp.Server
{ {
/// <summary> /// <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> /// </summary>
/// <remarks> /// <remarks>
/// The HttpServer instance can provide the multi WebSocket services. /// The HttpServer class can provide the multi WebSocket services.
/// </remarks> /// </remarks>
public class HttpServer public class HttpServer
{ {
@ -71,8 +74,8 @@ namespace WebSocketSharp.Server
#region Public Constructors #region Public Constructors
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HttpServer"/> class that listens for /// Initializes a new instance of the <see cref="HttpServer"/> class that
/// incoming requests on port 80. /// listens for incoming requests on port 80.
/// </summary> /// </summary>
public HttpServer () public HttpServer ()
: this (80) : this (80)
@ -80,8 +83,8 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HttpServer"/> class that listens for /// Initializes a new instance of the <see cref="HttpServer"/> class that
/// incoming requests on the specified <paramref name="port"/>. /// listens for incoming requests on the specified <paramref name="port"/>.
/// </summary> /// </summary>
/// <param name="port"> /// <param name="port">
/// An <see cref="int"/> that contains a port number. /// An <see cref="int"/> that contains a port number.
@ -95,8 +98,9 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HttpServer"/> class that listens for /// Initializes a new instance of the <see cref="HttpServer"/> class that
/// incoming requests on the specified <paramref name="port"/> and <paramref name="secure"/>. /// listens for incoming requests on the specified <paramref name="port"/>
/// and <paramref name="secure"/>.
/// </summary> /// </summary>
/// <param name="port"> /// <param name="port">
/// An <see cref="int"/> that contains a port number. /// An <see cref="int"/> that contains a port number.
@ -114,16 +118,28 @@ namespace WebSocketSharp.Server
public HttpServer (int port, bool secure) public HttpServer (int port, bool secure)
{ {
if (!port.IsPortNumber ()) 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)) if ((port == 80 && secure) || (port == 443 && !secure))
throw new ArgumentException (String.Format ( throw new ArgumentException (
"Invalid pair of 'port' and 'secure': {0}, {1}", port, secure)); String.Format (
"Invalid pair of 'port' and 'secure': {0}, {1}", port, secure));
_port = port; _port = port;
_secure = secure; _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 #endregion
@ -131,7 +147,29 @@ namespace WebSocketSharp.Server
#region Public Properties #region Public Properties
/// <summary> /// <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> /// </summary>
/// <value> /// <value>
/// A <see cref="X509Certificate2"/> used to authenticate the server. /// A <see cref="X509Certificate2"/> used to authenticate the server.
@ -142,16 +180,12 @@ namespace WebSocketSharp.Server
} }
set { set {
if (_state == ServerState.START || _state == ServerState.SHUTDOWN) if (!canSet ("Certificate"))
{
_logger.Error (
"The value of Certificate property cannot be changed because the server has already been started.");
return; return;
}
if (EndPointListener.CertificateExists (_port, _listener.CertificateFolderPath)) 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; _listener.DefaultCertificate = value;
} }
@ -173,7 +207,8 @@ namespace WebSocketSharp.Server
/// Gets a value indicating whether the server provides secure connection. /// Gets a value indicating whether the server provides secure connection.
/// </summary> /// </summary>
/// <value> /// <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> /// </value>
public bool IsSecure { public bool IsSecure {
get { get {
@ -182,12 +217,12 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Gets or sets a value indicating whether the server cleans up the inactive WebSocket sessions /// Gets or sets a value indicating whether the server cleans up the inactive
/// periodically. /// WebSocket sessions periodically.
/// </summary> /// </summary>
/// <value> /// <value>
/// <c>true</c> if the server cleans up the inactive WebSocket sessions every 60 seconds; /// <c>true</c> if the server cleans up the inactive WebSocket sessions every
/// otherwise, <c>false</c>. The default value is <c>true</c>. /// 60 seconds; otherwise, <c>false</c>. The default value is <c>true</c>.
/// </value> /// </value>
public bool KeepClean { public bool KeepClean {
get { get {
@ -203,9 +238,9 @@ namespace WebSocketSharp.Server
/// Gets the logging functions. /// Gets the logging functions.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The default logging level is the <see cref="LogLevel.ERROR"/>. /// The default logging level is the <see cref="LogLevel.ERROR"/>. If you
/// If you want to change the current logging level, you set the <c>Log.Level</c> property /// change the current logging level, you set the <c>Log.Level</c> property
/// to one of the <see cref="LogLevel"/> values which you want. /// to any of the <see cref="LogLevel"/> values.
/// </remarks> /// </remarks>
/// <value> /// <value>
/// A <see cref="Logger"/> that provides the logging functions. /// 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> /// <summary>
/// Gets or sets the document root path of server. /// Gets or sets the document root path of server.
/// </summary> /// </summary>
@ -243,23 +299,42 @@ namespace WebSocketSharp.Server
} }
set { set {
if (_state == ServerState.START || _state == ServerState.SHUTDOWN) if (!canSet ("RootPath"))
{
_logger.Error (
"The value of RootPath property cannot be changed because the server has already been started.");
return; return;
}
_rootPath = value; _rootPath = value;
} }
} }
/// <summary> /// <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 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> /// </summary>
/// <value> /// <value>
/// A <see cref="WebSocketServiceHostManager"/> that manages the WebSocket services. /// A <see cref="WebSocketServiceHostManager"/> that manages the WebSocket
/// services.
/// </value> /// </value>
public WebSocketServiceHostManager WebSocketServices { public WebSocketServiceHostManager WebSocketServices {
get { get {
@ -322,8 +397,7 @@ namespace WebSocketSharp.Server
private void abort () private void abort ()
{ {
lock (_sync) lock (_sync) {
{
if (!IsListening) if (!IsListening)
return; return;
@ -331,152 +405,156 @@ namespace WebSocketSharp.Server
} }
_serviceHosts.Stop ( _serviceHosts.Stop (
((ushort) CloseStatusCode.SERVER_ERROR).ToByteArrayInternally (ByteOrder.BIG), true); ((ushort) CloseStatusCode.SERVER_ERROR).ToByteArrayInternally (ByteOrder.BIG),
true);
_listener.Abort (); _listener.Abort ();
_state = ServerState.STOP; _state = ServerState.STOP;
} }
private string checkIfCertExists () private void acceptHttpRequest (HttpListenerContext context)
{
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)
{ {
var args = new HttpRequestEventArgs (context); var args = new HttpRequestEventArgs (context);
var method = context.Request.HttpMethod; var method = context.Request.HttpMethod;
if (method == "GET" && OnGet != null) if (method == "GET" && OnGet != null) {
{
OnGet (this, args); OnGet (this, args);
return; return;
} }
if (method == "HEAD" && OnHead != null) if (method == "HEAD" && OnHead != null) {
{
OnHead (this, args); OnHead (this, args);
return; return;
} }
if (method == "POST" && OnPost != null) if (method == "POST" && OnPost != null) {
{
OnPost (this, args); OnPost (this, args);
return; return;
} }
if (method == "PUT" && OnPut != null) if (method == "PUT" && OnPut != null) {
{
OnPut (this, args); OnPut (this, args);
return; return;
} }
if (method == "DELETE" && OnDelete != null) if (method == "DELETE" && OnDelete != null) {
{
OnDelete (this, args); OnDelete (this, args);
return; return;
} }
if (method == "OPTIONS" && OnOptions != null) if (method == "OPTIONS" && OnOptions != null) {
{
OnOptions (this, args); OnOptions (this, args);
return; return;
} }
if (method == "TRACE" && OnTrace != null) if (method == "TRACE" && OnTrace != null) {
{
OnTrace (this, args); OnTrace (this, args);
return; return;
} }
if (method == "CONNECT" && OnConnect != null) if (method == "CONNECT" && OnConnect != null) {
{
OnConnect (this, args); OnConnect (this, args);
return; return;
} }
if (method == "PATCH" && OnPatch != null) if (method == "PATCH" && OnPatch != null) {
{
OnPatch (this, args); OnPatch (this, args);
return; return;
} }
context.Response.StatusCode = (int) HttpStatusCode.NotImplemented; context.Response.Close (HttpStatusCode.NotImplemented);
}
private void acceptRequestAsync (HttpListenerContext context)
{
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 bool processWebSocketRequest (HttpListenerContext context) private void acceptWebSocketRequest (HttpListenerWebSocketContext context)
{ {
var wsContext = context.AcceptWebSocket (); var path = context.Path;
var path = wsContext.Path;
WebSocketServiceHost host; WebSocketServiceHost host;
if (path == null || !_serviceHosts.TryGetServiceHostInternally (path, out host)) if (path == null ||
{ !_serviceHosts.TryGetServiceHostInternally (path, out host)) {
context.Response.StatusCode = (int) HttpStatusCode.NotImplemented; context.Close (HttpStatusCode.NotImplemented);
return false; return;
} }
wsContext.WebSocket.Log = _logger; host.StartSession (context);
host.StartSession (wsContext); }
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 => if (_state == ServerState.START || _state == ServerState.SHUTDOWN) {
{ _logger.Error (
try { String.Format (
if (context.Request.IsUpgradeTo ("websocket")) "The '{0}' property cannot set a value because the server has already been started.",
{ property));
if (processWebSocketRequest (context))
return;
}
else
{
processHttpRequest (context);
}
context.Response.Close (); return false;
} }
catch (Exception ex) {
_logger.Fatal (ex.ToString ());
context.Connection.Close (true);
}
};
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 () private void receiveRequest ()
{ {
while (true) while (true) {
{
try { try {
processRequestAsync (_listener.GetContext ()); acceptRequestAsync (_listener.GetContext ());
} }
catch (HttpListenerException ex) { 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; break;
} }
catch (Exception ex) { catch (Exception ex) {
@ -489,9 +567,9 @@ namespace WebSocketSharp.Server
abort (); abort ();
} }
private void startReceiveRequestThread () private void startReceiving ()
{ {
_receiveRequestThread = new Thread (new ThreadStart (receiveRequest)); _receiveRequestThread = new Thread (new ThreadStart (receiveRequest));
_receiveRequestThread.IsBackground = true; _receiveRequestThread.IsBackground = true;
_receiveRequestThread.Start (); _receiveRequestThread.Start ();
} }
@ -507,18 +585,21 @@ namespace WebSocketSharp.Server
#region Public Methods #region Public Methods
/// <summary> /// <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> /// </summary>
/// <remarks> /// <remarks>
/// This method converts <paramref name="servicePath"/> to URL-decoded string and /// This method converts <paramref name="servicePath"/> to URL-decoded string
/// removes <c>'/'</c> from tail end of <paramref name="servicePath"/>. /// and removes <c>'/'</c> from tail end of <paramref name="servicePath"/>.
/// </remarks> /// </remarks>
/// <param name="servicePath"> /// <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>
/// <typeparam name="TWithNew"> /// <typeparam name="TWithNew">
/// The type of the WebSocket service. The TWithNew must inherit the <see cref="WebSocketService"/> class and /// The type of the WebSocket service. The TWithNew must inherit the
/// must have a public parameterless constructor. /// <see cref="WebSocketService"/> class and must have a public parameterless
/// constructor.
/// </typeparam> /// </typeparam>
public void AddWebSocketService<TWithNew> (string servicePath) public void AddWebSocketService<TWithNew> (string servicePath)
where TWithNew : WebSocketService, new () where TWithNew : WebSocketService, new ()
@ -527,42 +608,50 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Adds the specified typed WebSocket service with the specified <paramref name="servicePath"/> and /// Adds the specified typed WebSocket service with the specified
/// <paramref name="serviceConstructor"/>. /// <paramref name="servicePath"/> and <paramref name="serviceConstructor"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para> /// <para>
/// This method converts <paramref name="servicePath"/> to URL-decoded string and /// This method converts <paramref name="servicePath"/> to URL-decoded
/// removes <c>'/'</c> from tail end of <paramref name="servicePath"/>. /// string and removes <c>'/'</c> from tail end of
/// <paramref name="servicePath"/>.
/// </para> /// </para>
/// <para> /// <para>
/// <paramref name="serviceConstructor"/> returns a initialized specified typed WebSocket service /// <paramref name="serviceConstructor"/> returns a initialized specified
/// instance. /// typed WebSocket service instance.
/// </para> /// </para>
/// </remarks> /// </remarks>
/// <param name="servicePath"> /// <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>
/// <param name="serviceConstructor"> /// <param name="serviceConstructor">
/// A Func&lt;T&gt; delegate that references the method used to initialize a new WebSocket service /// A Func&lt;T&gt; delegate that references the method used to initialize
/// instance (a new WebSocket session). /// a new WebSocket service instance (a new WebSocket session).
/// </param> /// </param>
/// <typeparam name="T"> /// <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> /// </typeparam>
public void AddWebSocketService<T> (string servicePath, Func<T> serviceConstructor) public void AddWebSocketService<T> (string servicePath, Func<T> serviceConstructor)
where T : WebSocketService where T : WebSocketService
{ {
var msg = servicePath.CheckIfValidServicePath () ?? 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; return;
} }
var host = new WebSocketServiceHost<T> (servicePath, serviceConstructor, _logger); var host = new WebSocketServiceHost<T> (
servicePath, serviceConstructor, _logger);
if (!KeepClean) if (!KeepClean)
host.KeepClean = false; host.KeepClean = false;
@ -573,8 +662,8 @@ namespace WebSocketSharp.Server
/// Gets the contents of the file with the specified <paramref name="path"/>. /// Gets the contents of the file with the specified <paramref name="path"/>.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// An array of <see cref="byte"/> that contains the contents of the file if exists; /// An array of <see cref="byte"/> that contains the contents of the file if
/// otherwise, <see langword="null"/>. /// it exists; otherwise, <see langword="null"/>.
/// </returns> /// </returns>
/// <param name="path"> /// <param name="path">
/// A <see cref="string"/> that contains a virtual path to the file to get. /// A <see cref="string"/> that contains a virtual path to the file to get.
@ -591,24 +680,28 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Removes the WebSocket service with the specified <paramref name="servicePath"/>. /// Removes the WebSocket service with the specified
/// <paramref name="servicePath"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This method converts <paramref name="servicePath"/> to URL-decoded string and /// This method converts <paramref name="servicePath"/> to URL-decoded string
/// removes <c>'/'</c> from tail end of <paramref name="servicePath"/>. /// and removes <c>'/'</c> from tail end of <paramref name="servicePath"/>.
/// </remarks> /// </remarks>
/// <returns> /// <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> /// </returns>
/// <param name="servicePath"> /// <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> /// </param>
public bool RemoveWebSocketService (string servicePath) public bool RemoveWebSocketService (string servicePath)
{ {
var msg = servicePath.CheckIfValidServicePath (); var msg = servicePath.CheckIfValidServicePath ();
if (msg != null) if (msg != null) {
{ _logger.Error (
_logger.Error (String.Format ("{0}\nservice path: {1}", msg, servicePath ?? "")); String.Format ("{0}\nservice path: {1}", msg, servicePath));
return false; return false;
} }
@ -616,22 +709,23 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Starts to receive the HTTP requests. /// Starts receiving the HTTP requests.
/// </summary> /// </summary>
public void Start () public void Start ()
{ {
lock (_sync) lock (_sync) {
{
var msg = _state.CheckIfStopped () ?? checkIfCertExists (); var msg = _state.CheckIfStopped () ?? checkIfCertExists ();
if (msg != null) if (msg != null) {
{ _logger.Error (
_logger.Error (String.Format ("{0}\nstate: {1}\nsecure: {2}", msg, _state, _secure)); String.Format (
"{0}\nstate: {1}\nsecure: {2}", msg, _state, _secure));
return; return;
} }
_serviceHosts.Start (); _serviceHosts.Start ();
_listener.Start (); _listener.Start ();
startReceiveRequestThread (); startReceiving ();
_state = ServerState.START; _state = ServerState.START;
} }
@ -642,11 +736,9 @@ namespace WebSocketSharp.Server
/// </summary> /// </summary>
public void Stop () public void Stop ()
{ {
lock (_sync) lock (_sync) {
{
var msg = _state.CheckIfStarted (); var msg = _state.CheckIfStarted ();
if (msg != null) if (msg != null) {
{
_logger.Error (String.Format ("{0}\nstate: {1}", msg, _state)); _logger.Error (String.Format ("{0}\nstate: {1}", msg, _state));
return; return;
} }
@ -654,18 +746,19 @@ namespace WebSocketSharp.Server
_state = ServerState.SHUTDOWN; _state = ServerState.SHUTDOWN;
} }
_serviceHosts.Stop (new byte []{}, true); _serviceHosts.Stop (new byte [0], true);
stopListener (5000); stopListener (5000);
_state = ServerState.STOP; _state = ServerState.STOP;
} }
/// <summary> /// <summary>
/// Stops receiving the HTTP requests with the specified <see cref="ushort"/> and /// Stops receiving the HTTP requests with the specified <see cref="ushort"/>
/// <see cref="string"/> used to stop the WebSocket services. /// and <see cref="string"/> used to stop the WebSocket services.
/// </summary> /// </summary>
/// <param name="code"> /// <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>
/// <param name="reason"> /// <param name="reason">
/// A <see cref="string"/> that contains the reason for stop. /// A <see cref="string"/> that contains the reason for stop.
@ -673,16 +766,15 @@ namespace WebSocketSharp.Server
public void Stop (ushort code, string reason) public void Stop (ushort code, string reason)
{ {
byte [] data = null; byte [] data = null;
lock (_sync) lock (_sync) {
{
var msg = _state.CheckIfStarted () ?? var msg = _state.CheckIfStarted () ??
code.CheckIfValidCloseStatusCode () ?? code.CheckIfValidCloseStatusCode () ??
(data = code.Append (reason)).CheckIfValidCloseData (); (data = code.Append (reason)).CheckIfValidCloseData ();
if (msg != null) if (msg != null) {
{ _logger.Error (
_logger.Error (String.Format ( String.Format (
"{0}\nstate: {1}\ncode: {2}\nreason: {3}", msg, _state, code, reason)); "{0}\nstate: {1}\ncode: {2}\nreason: {3}", msg, _state, code, reason));
return; return;
} }
@ -697,12 +789,13 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Stops receiving the HTTP requests with the specified <see cref="CloseStatusCode"/> /// Stops receiving the HTTP requests with the specified
/// and <see cref="string"/> used to stop the WebSocket services. /// <see cref="CloseStatusCode"/> and <see cref="string"/> used to stop the
/// WebSocket services.
/// </summary> /// </summary>
/// <param name="code"> /// <param name="code">
/// One of the <see cref="CloseStatusCode"/> values that represent the status codes indicating /// One of the <see cref="CloseStatusCode"/> values that represent the status
/// the reasons for stop. /// codes indicating the reasons for stop.
/// </param> /// </param>
/// <param name="reason"> /// <param name="reason">
/// A <see cref="string"/> that contains the reason for stop. /// A <see cref="string"/> that contains the reason for stop.
@ -710,14 +803,14 @@ namespace WebSocketSharp.Server
public void Stop (CloseStatusCode code, string reason) public void Stop (CloseStatusCode code, string reason)
{ {
byte [] data = null; byte [] data = null;
lock (_sync) lock (_sync) {
{
var msg = _state.CheckIfStarted () ?? var msg = _state.CheckIfStarted () ??
(data = ((ushort) code).Append (reason)).CheckIfValidCloseData (); (data = ((ushort) code).Append (reason)).CheckIfValidCloseData ();
if (msg != null) if (msg != null) {
{ _logger.Error (
_logger.Error (String.Format ("{0}\nstate: {1}\nreason: {2}", msg, _state, reason)); String.Format ("{0}\nstate: {1}\nreason: {2}", msg, _state, reason));
return; return;
} }

@ -7,7 +7,7 @@
* The MIT License * The MIT License
* *
* Copyright (c) 2012-2013 sta.blockhead * Copyright (c) 2012-2013 sta.blockhead
* *
* Permission is hereby granted, free of charge, to any person obtaining a copy * Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal * of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights * in the Software without restriction, including without limitation the rights
@ -17,7 +17,7 @@
* *
* The above copyright notice and this permission notice shall be included in * The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software. * all copies or substantial portions of the Software.
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -39,6 +39,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net.Sockets; using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Security.Principal;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using WebSocketSharp.Net; using WebSocketSharp.Net;
@ -47,7 +48,8 @@ using WebSocketSharp.Net.WebSockets;
namespace WebSocketSharp.Server namespace WebSocketSharp.Server
{ {
/// <summary> /// <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> /// </summary>
/// <remarks> /// <remarks>
/// The WebSocketServer class provides the multi WebSocket service. /// The WebSocketServer class provides the multi WebSocket service.
@ -56,25 +58,28 @@ namespace WebSocketSharp.Server
{ {
#region Private Fields #region Private Fields
private System.Net.IPAddress _address; private System.Net.IPAddress _address;
private X509Certificate2 _cert; private AuthenticationSchemes _authSchemes;
private TcpListener _listener; private X509Certificate2 _cert;
private Logger _logger; private Func<IIdentity, NetworkCredential> _credentialsFinder;
private int _port; private TcpListener _listener;
private Thread _receiveRequestThread; private Logger _logger;
private bool _secure; private int _port;
private WebSocketServiceHostManager _serviceHosts; private string _realm;
private volatile ServerState _state; private Thread _receiveRequestThread;
private object _sync; private bool _secure;
private Uri _uri; private WebSocketServiceHostManager _serviceHosts;
private volatile ServerState _state;
private object _sync;
private Uri _uri;
#endregion #endregion
#region Public Constructors #region Public Constructors
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WebSocketServer"/> class that listens for /// Initializes a new instance of the <see cref="WebSocketServer"/> class
/// incoming requests on port 80. /// that listens for incoming requests on port 80.
/// </summary> /// </summary>
public WebSocketServer () public WebSocketServer ()
: this (80) : this (80)
@ -82,8 +87,9 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WebSocketServer"/> class that listens for /// Initializes a new instance of the <see cref="WebSocketServer"/> class
/// incoming connection attempts on the specified <paramref name="port"/>. /// that listens for incoming connection attempts on the specified
/// <paramref name="port"/>.
/// </summary> /// </summary>
/// <param name="port"> /// <param name="port">
/// An <see cref="int"/> that contains a port number. /// An <see cref="int"/> that contains a port number.
@ -97,8 +103,9 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WebSocketServer"/> class that listens for /// Initializes a new instance of the <see cref="WebSocketServer"/> class
/// incoming connection attempts on the specified WebSocket URL. /// that listens for incoming connection attempts on the specified WebSocket
/// URL.
/// </summary> /// </summary>
/// <param name="url"> /// <param name="url">
/// A <see cref="string"/> that contains a WebSocket URL. /// A <see cref="string"/> that contains a WebSocket URL.
@ -121,8 +128,9 @@ namespace WebSocketSharp.Server
var host = _uri.DnsSafeHost; var host = _uri.DnsSafeHost;
_address = host.ToIPAddress (); _address = host.ToIPAddress ();
if (_address == null || !_address.IsLocal ()) if (_address == null || !_address.IsLocal ())
throw new ArgumentException (String.Format ( throw new ArgumentException (
"The host part must be the local host name: {0}", host), "url"); String.Format (
"The host part must be the local host name: {0}", host), "url");
_port = _uri.Port; _port = _uri.Port;
_secure = _uri.Scheme == "wss" ? true : false; _secure = _uri.Scheme == "wss" ? true : false;
@ -131,8 +139,9 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WebSocketServer"/> class that listens for /// Initializes a new instance of the <see cref="WebSocketServer"/> class
/// incoming connection attempts on the specified <paramref name="port"/> and <paramref name="secure"/>. /// that listens for incoming connection attempts on the specified
/// <paramref name="port"/> and <paramref name="secure"/>.
/// </summary> /// </summary>
/// <param name="port"> /// <param name="port">
/// An <see cref="int"/> that contains a port number. /// An <see cref="int"/> that contains a port number.
@ -153,11 +162,13 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WebSocketServer"/> class that listens for /// Initializes a new instance of the <see cref="WebSocketServer"/> class
/// incoming connection attempts on the specified <paramref name="address"/> and <paramref name="port"/>. /// that listens for incoming connection attempts on the specified
/// <paramref name="address"/> and <paramref name="port"/>.
/// </summary> /// </summary>
/// <param name="address"> /// <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>
/// <param name="port"> /// <param name="port">
/// An <see cref="int"/> that contains a port number. /// An <see cref="int"/> that contains a port number.
@ -177,12 +188,14 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="WebSocketServer"/> class that listens for /// Initializes a new instance of the <see cref="WebSocketServer"/> class
/// incoming connection attempts on the specified <paramref name="address"/>, <paramref name="port"/> /// that listens for incoming connection attempts on the specified
/// and <paramref name="secure"/>. /// <paramref name="address"/>, <paramref name="port"/> and
/// <paramref name="secure"/>.
/// </summary> /// </summary>
/// <param name="address"> /// <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>
/// <param name="port"> /// <param name="port">
/// An <see cref="int"/> that contains a port number. /// An <see cref="int"/> that contains a port number.
@ -205,21 +218,25 @@ namespace WebSocketSharp.Server
/// -or- /// -or-
/// </para> /// </para>
/// <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> /// </para>
/// </exception> /// </exception>
public WebSocketServer (System.Net.IPAddress address, int port, bool secure) public WebSocketServer (System.Net.IPAddress address, int port, bool secure)
{ {
if (!address.IsLocal ()) if (!address.IsLocal ())
throw new ArgumentException (String.Format ( throw new ArgumentException (
"Must be the local IP address: {0}", address), "address"); String.Format (
"Must be the local IP address: {0}", address), "address");
if (!port.IsPortNumber ()) 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)) if ((port == 80 && secure) || (port == 443 && !secure))
throw new ArgumentException (String.Format ( throw new ArgumentException (
"Invalid pair of 'port' and 'secure': {0}, {1}", port, secure)); String.Format (
"Invalid pair of 'port' and 'secure': {0}, {1}", port, secure));
_address = address; _address = address;
_port = port; _port = port;
@ -234,10 +251,12 @@ namespace WebSocketSharp.Server
#region Public Properties #region Public Properties
/// <summary> /// <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> /// </summary>
/// <value> /// <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> /// </value>
public System.Net.IPAddress Address { public System.Net.IPAddress Address {
get { get {
@ -246,7 +265,29 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <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> /// </summary>
/// <value> /// <value>
/// A <see cref="X509Certificate2"/> used to authenticate the server. /// A <see cref="X509Certificate2"/> used to authenticate the server.
@ -257,13 +298,8 @@ namespace WebSocketSharp.Server
} }
set { set {
if (_state == ServerState.START || _state == ServerState.SHUTDOWN) if (!canSet ("Certificate"))
{
_logger.Error (
"The value of Certificate property cannot be changed because the server has already been started.");
return; return;
}
_cert = value; _cert = value;
} }
@ -285,7 +321,8 @@ namespace WebSocketSharp.Server
/// Gets a value indicating whether the server provides secure connection. /// Gets a value indicating whether the server provides secure connection.
/// </summary> /// </summary>
/// <value> /// <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> /// </value>
public bool IsSecure { public bool IsSecure {
get { get {
@ -294,11 +331,12 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <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> /// </summary>
/// <value> /// <value>
/// <c>true</c> if the server cleans up the inactive sessions every 60 seconds; /// <c>true</c> if the server cleans up the inactive sessions every 60
/// otherwise, <c>false</c>. The default value is <c>true</c>. /// seconds; otherwise, <c>false</c>. The default value is <c>true</c>.
/// </value> /// </value>
public bool KeepClean { public bool KeepClean {
get { get {
@ -314,9 +352,9 @@ namespace WebSocketSharp.Server
/// Gets the logging functions. /// Gets the logging functions.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// The default logging level is the <see cref="LogLevel.ERROR"/>. /// The default logging level is the <see cref="LogLevel.ERROR"/>. If you
/// If you want to change the current logging level, you set the <c>Log.Level</c> property /// change the current logging level, you set the <c>Log.Level</c> property
/// to one of the <see cref="LogLevel"/> values which you want. /// to any of the <see cref="LogLevel"/> values.
/// </remarks> /// </remarks>
/// <value> /// <value>
/// A <see cref="Logger"/> that provides the logging functions. /// A <see cref="Logger"/> that provides the logging functions.
@ -340,10 +378,55 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <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> /// </summary>
/// <value> /// <value>
/// A <see cref="WebSocketServiceHostManager"/> that manages the WebSocket services. /// 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.
/// </value> /// </value>
public WebSocketServiceHostManager WebSocketServices { public WebSocketServiceHostManager WebSocketServices {
get { get {
@ -357,8 +440,7 @@ namespace WebSocketSharp.Server
private void abort () private void abort ()
{ {
lock (_sync) lock (_sync) {
{
if (!IsListening) if (!IsListening)
return; return;
@ -367,30 +449,103 @@ namespace WebSocketSharp.Server
_listener.Stop (); _listener.Stop ();
_serviceHosts.Stop ( _serviceHosts.Stop (
((ushort) CloseStatusCode.SERVER_ERROR).ToByteArrayInternally (ByteOrder.BIG), true); ((ushort) CloseStatusCode.SERVER_ERROR).ToByteArrayInternally (ByteOrder.BIG),
true);
_state = ServerState.STOP; _state = ServerState.STOP;
} }
private void acceptWebSocket (TcpListenerWebSocketContext context) private void acceptRequestAsync (TcpClient client)
{ {
var websocket = context.WebSocket; ThreadPool.QueueUserWorkItem (
websocket.Log = _logger; 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; var path = context.Path;
WebSocketServiceHost host; WebSocketServiceHost host;
if (path == null || !_serviceHosts.TryGetServiceHostInternally (path, out host)) if (path == null ||
{ !_serviceHosts.TryGetServiceHostInternally (path, out host)) {
websocket.Close (HttpStatusCode.NotImplemented); context.Close (HttpStatusCode.NotImplemented);
return; return;
} }
if (_uri.IsAbsoluteUri)
websocket.Url = new Uri (_uri, path);
host.StartSession (context); 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 () private string checkIfCertExists ()
{ {
return _secure && _cert == null return _secure && _cert == null
@ -400,6 +555,7 @@ namespace WebSocketSharp.Server
private void init () private void init ()
{ {
_authSchemes = AuthenticationSchemes.Anonymous;
_listener = new TcpListener (_address, _port); _listener = new TcpListener (_address, _port);
_logger = new Logger (); _logger = new Logger ();
_serviceHosts = new WebSocketServiceHostManager (_logger); _serviceHosts = new WebSocketServiceHostManager (_logger);
@ -407,32 +563,17 @@ namespace WebSocketSharp.Server
_sync = new object (); _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 () private void receiveRequest ()
{ {
while (true) while (true) {
{
try { try {
processRequestAsync (_listener.AcceptTcpClient ()); acceptRequestAsync (_listener.AcceptTcpClient ());
} }
catch (SocketException ex) { 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; break;
} }
catch (Exception ex) { catch (Exception ex) {
@ -445,7 +586,7 @@ namespace WebSocketSharp.Server
abort (); abort ();
} }
private void startReceiveRequestThread () private void startReceiving ()
{ {
_receiveRequestThread = new Thread (new ThreadStart (receiveRequest)); _receiveRequestThread = new Thread (new ThreadStart (receiveRequest));
_receiveRequestThread.IsBackground = true; _receiveRequestThread.IsBackground = true;
@ -463,8 +604,7 @@ namespace WebSocketSharp.Server
if (!uriString.TryCreateWebSocketUri (out result, out message)) if (!uriString.TryCreateWebSocketUri (out result, out message))
return false; return false;
if (result.PathAndQuery != "/") if (result.PathAndQuery != "/") {
{
result = null; result = null;
message = "Must not contain the path or query component: " + uriString; message = "Must not contain the path or query component: " + uriString;
@ -479,18 +619,21 @@ namespace WebSocketSharp.Server
#region Public Methods #region Public Methods
/// <summary> /// <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> /// </summary>
/// <remarks> /// <remarks>
/// This method converts <paramref name="servicePath"/> to URL-decoded string and /// This method converts <paramref name="servicePath"/> to URL-decoded string
/// removes <c>'/'</c> from tail end of <paramref name="servicePath"/>. /// and removes <c>'/'</c> from tail end of <paramref name="servicePath"/>.
/// </remarks> /// </remarks>
/// <param name="servicePath"> /// <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>
/// <typeparam name="TWithNew"> /// <typeparam name="TWithNew">
/// The type of the WebSocket service. The TWithNew must inherit the <see cref="WebSocketService"/> /// The type of the WebSocket service. The TWithNew must inherit the
/// class and must have a public parameterless constructor. /// <see cref="WebSocketService"/> class and must have a public parameterless
/// constructor.
/// </typeparam> /// </typeparam>
public void AddWebSocketService<TWithNew> (string servicePath) public void AddWebSocketService<TWithNew> (string servicePath)
where TWithNew : WebSocketService, new () where TWithNew : WebSocketService, new ()
@ -499,42 +642,50 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Adds the specified typed WebSocket service with the specified <paramref name="servicePath"/> and /// Adds the specified typed WebSocket service with the specified
/// <paramref name="serviceConstructor"/>. /// <paramref name="servicePath"/> and <paramref name="serviceConstructor"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para> /// <para>
/// This method converts <paramref name="servicePath"/> to URL-decoded string and /// This method converts <paramref name="servicePath"/> to URL-decoded
/// removes <c>'/'</c> from tail end of <paramref name="servicePath"/>. /// string and removes <c>'/'</c> from tail end of
/// <paramref name="servicePath"/>.
/// </para> /// </para>
/// <para> /// <para>
/// <paramref name="serviceConstructor"/> returns a initialized specified typed WebSocket service /// <paramref name="serviceConstructor"/> returns a initialized specified
/// instance. /// typed WebSocket service instance.
/// </para> /// </para>
/// </remarks> /// </remarks>
/// <param name="servicePath"> /// <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>
/// <param name="serviceConstructor"> /// <param name="serviceConstructor">
/// A Func&lt;T&gt; delegate that references the method used to initialize a new WebSocket service /// A Func&lt;T&gt; delegate that references the method used to initialize
/// instance (a new WebSocket session). /// a new WebSocket service instance (a new WebSocket session).
/// </param> /// </param>
/// <typeparam name="T"> /// <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> /// </typeparam>
public void AddWebSocketService<T> (string servicePath, Func<T> serviceConstructor) public void AddWebSocketService<T> (string servicePath, Func<T> serviceConstructor)
where T : WebSocketService where T : WebSocketService
{ {
var msg = servicePath.CheckIfValidServicePath () ?? 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; return;
} }
var host = new WebSocketServiceHost<T> (servicePath, serviceConstructor, _logger); var host = new WebSocketServiceHost<T> (
servicePath, serviceConstructor, _logger);
if (!KeepClean) if (!KeepClean)
host.KeepClean = false; host.KeepClean = false;
@ -542,24 +693,28 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Removes the WebSocket service with the specified <paramref name="servicePath"/>. /// Removes the WebSocket service with the specified
/// <paramref name="servicePath"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// This method converts <paramref name="servicePath"/> to URL-decoded string and /// This method converts <paramref name="servicePath"/> to URL-decoded string
/// removes <c>'/'</c> from tail end of <paramref name="servicePath"/>. /// and removes <c>'/'</c> from tail end of <paramref name="servicePath"/>.
/// </remarks> /// </remarks>
/// <returns> /// <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> /// </returns>
/// <param name="servicePath"> /// <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> /// </param>
public bool RemoveWebSocketService (string servicePath) public bool RemoveWebSocketService (string servicePath)
{ {
var msg = servicePath.CheckIfValidServicePath (); var msg = servicePath.CheckIfValidServicePath ();
if (msg != null) if (msg != null) {
{ _logger.Error (
_logger.Error (String.Format ("{0}\nservice path: {1}", msg, servicePath ?? "")); String.Format ("{0}\nservice path: {1}", msg, servicePath));
return false; return false;
} }
@ -567,22 +722,23 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Starts to receive the WebSocket connection requests. /// Starts receiving the WebSocket connection requests.
/// </summary> /// </summary>
public void Start () public void Start ()
{ {
lock (_sync) lock (_sync) {
{
var msg = _state.CheckIfStopped () ?? checkIfCertExists (); var msg = _state.CheckIfStopped () ?? checkIfCertExists ();
if (msg != null) if (msg != null) {
{ _logger.Error (
_logger.Error (String.Format ("{0}\nstate: {1}\nsecure: {2}", msg, _state, _secure)); String.Format (
"{0}\nstate: {1}\nsecure: {2}", msg, _state, _secure));
return; return;
} }
_serviceHosts.Start (); _serviceHosts.Start ();
_listener.Start (); _listener.Start ();
startReceiveRequestThread (); startReceiving ();
_state = ServerState.START; _state = ServerState.START;
} }
@ -593,11 +749,9 @@ namespace WebSocketSharp.Server
/// </summary> /// </summary>
public void Stop () public void Stop ()
{ {
lock (_sync) lock (_sync) {
{
var msg = _state.CheckIfStarted (); var msg = _state.CheckIfStarted ();
if (msg != null) if (msg != null) {
{
_logger.Error (String.Format ("{0}\nstate: {1}", msg, _state)); _logger.Error (String.Format ("{0}\nstate: {1}", msg, _state));
return; return;
} }
@ -606,17 +760,18 @@ namespace WebSocketSharp.Server
} }
stopListener (5000); stopListener (5000);
_serviceHosts.Stop (new byte []{}, true); _serviceHosts.Stop (new byte [0], true);
_state = ServerState.STOP; _state = ServerState.STOP;
} }
/// <summary> /// <summary>
/// Stops receiving the WebSocket connection requests with the specified <see cref="ushort"/> and /// Stops receiving the WebSocket connection requests with the specified
/// <see cref="string"/>. /// <see cref="ushort"/> and <see cref="string"/>.
/// </summary> /// </summary>
/// <param name="code"> /// <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>
/// <param name="reason"> /// <param name="reason">
/// A <see cref="string"/> that contains the reason for stop. /// A <see cref="string"/> that contains the reason for stop.
@ -624,16 +779,15 @@ namespace WebSocketSharp.Server
public void Stop (ushort code, string reason) public void Stop (ushort code, string reason)
{ {
byte [] data = null; byte [] data = null;
lock (_sync) lock (_sync) {
{
var msg = _state.CheckIfStarted () ?? var msg = _state.CheckIfStarted () ??
code.CheckIfValidCloseStatusCode () ?? code.CheckIfValidCloseStatusCode () ??
(data = code.Append (reason)).CheckIfValidCloseData (); (data = code.Append (reason)).CheckIfValidCloseData ();
if (msg != null) if (msg != null) {
{ _logger.Error (
_logger.Error (String.Format ( String.Format (
"{0}\nstate: {1}\ncode: {2}\nreason: {3}", msg, _state, code, reason)); "{0}\nstate: {1}\ncode: {2}\nreason: {3}", msg, _state, code, reason));
return; return;
} }
@ -648,12 +802,12 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Stops receiving the WebSocket connection requests with the specified <see cref="CloseStatusCode"/> /// Stops receiving the WebSocket connection requests with the specified
/// and <see cref="string"/>. /// <see cref="CloseStatusCode"/> and <see cref="string"/>.
/// </summary> /// </summary>
/// <param name="code"> /// <param name="code">
/// One of the <see cref="CloseStatusCode"/> values that represent the status codes indicating /// One of the <see cref="CloseStatusCode"/> values that represent the status
/// the reasons for stop. /// codes indicating the reasons for stop.
/// </param> /// </param>
/// <param name="reason"> /// <param name="reason">
/// A <see cref="string"/> that contains the reason for stop. /// A <see cref="string"/> that contains the reason for stop.
@ -661,14 +815,14 @@ namespace WebSocketSharp.Server
public void Stop (CloseStatusCode code, string reason) public void Stop (CloseStatusCode code, string reason)
{ {
byte [] data = null; byte [] data = null;
lock (_sync) lock (_sync) {
{
var msg = _state.CheckIfStarted () ?? var msg = _state.CheckIfStarted () ??
(data = ((ushort) code).Append (reason)).CheckIfValidCloseData (); (data = ((ushort) code).Append (reason)).CheckIfValidCloseData ();
if (msg != null) if (msg != null) {
{ _logger.Error (
_logger.Error (String.Format ("{0}\nstate: {1}\nreason: {2}", msg, _state, reason)); String.Format ("{0}\nstate: {1}\nreason: {2}", msg, _state, reason));
return; return;
} }

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

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

Loading…
Cancel
Save