Fix for HTTP Basic/Digest Authentication

This commit is contained in:
sta 2014-01-01 21:43:18 +09:00
parent 0a263622f0
commit 537229902f
32 changed files with 7488 additions and 6273 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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;
} }

View File

@ -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,29 +27,36 @@
#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
#region Internal Properties
internal NameValueCollection Params {
get {
return _params;
}
} }
#endregion #endregion
@ -58,81 +65,49 @@ namespace WebSocketSharp {
public string Algorithm { public string Algorithm {
get { get {
return _algorithm ?? String.Empty; return _params ["algorithm"];
}
private set {
_algorithm = value;
} }
} }
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

View File

@ -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,56 +27,81 @@
#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
#region Internal Properties
internal uint NonceCount {
get {
return _nonceCount < UInt32.MaxValue
? _nonceCount
: 0;
}
}
internal NameValueCollection Params {
get {
return _params;
}
} }
#endregion #endregion
@ -85,111 +110,73 @@ namespace WebSocketSharp {
public string Algorithm { public string Algorithm {
get { get {
return _algorithm ?? String.Empty; return _params ["algorithm"];
}
private set {
_algorithm = value;
} }
} }
public string Cnonce { public string Cnonce {
get { get {
return _cnonce ?? String.Empty; return _params ["cnonce"];
}
private set {
_cnonce = value;
} }
} }
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;
return false;
} }
private string a2() private void initAsDigest ()
{ {
return String.Format("{0}:{1}", _method, _uri); var qops = _params ["qop"];
} if (qops != null) {
var qop = "auth";
private static string createNonceValue() if (contains (qops.Split (','), qop)) {
{ _params ["qop"] = qop;
var src = new byte[16]; _params ["nc"] = String.Format ("{0:x8}", ++_nonceCount);
var rand = new Random(); _params ["cnonce"] = HttpUtility.CreateNonceValue ();
rand.NextBytes(src);
var nonce = new StringBuilder(32);
foreach (var b in src)
nonce.Append(b.ToString("x2"));
return nonce.ToString();
}
private string createRequestDigest()
{
if (Qop == "auth")
{
var data = String.Format("{0}:{1}:{2}:{3}:{4}",
_nonce, _nc, _cnonce, _qop, hash(a2()));
return kd(hash(a1()), data);
}
return kd(hash(a1()), String.Format("{0}:{1}", _nonce, hash(a2())));
}
private static string hash(string value)
{
var md5 = MD5.Create();
var src = Encoding.UTF8.GetBytes(value);
var hashed = md5.ComputeHash(src);
var result = new StringBuilder(64);
foreach (var b in hashed)
result.Append(b.ToString("x2"));
return result.ToString();
}
private void initForDigest(WsCredential credential, AuthenticationChallenge challenge)
{
_nonce = challenge.Nonce;
_method = "GET";
_uri = credential.Domain;
_algorithm = challenge.Algorithm;
_opaque = challenge.Opaque;
foreach (var qop in challenge.Qop.Split(','))
{
if (qop.Trim().ToLower() == "auth")
{
_qop = "auth";
_nc = "00000001";
break;
} }
else
_params ["qop"] = null;
} }
_cnonce = createNonceValue(); _params ["method"] = "GET";
_response = createRequestDigest(); _params ["response"] = HttpUtility.CreateRequestDigest (_params);
}
private static string kd(string secret, string data)
{
var concatenated = String.Format("{0}:{1}", secret, data);
return hash(concatenated);
}
private string toBasicCredentials()
{
var userPass = String.Format("{0}:{1}", _userName, _password);
var base64UserPass = Convert.ToBase64String(Encoding.UTF8.GetBytes(userPass));
return "Basic " + base64UserPass;
}
private string toDigestCredentials()
{
var digestResponse = new StringBuilder(64);
digestResponse.AppendFormat("username={0}", _userName.Quote());
digestResponse.AppendFormat(", realm={0}", _realm.Quote());
digestResponse.AppendFormat(", nonce={0}", _nonce.Quote());
digestResponse.AppendFormat(", uri={0}", _uri.Quote());
digestResponse.AppendFormat(", response={0}", _response.Quote());
if (!_algorithm.IsNullOrEmpty())
digestResponse.AppendFormat(", algorithm={0}", _algorithm);
if (!_opaque.IsNullOrEmpty())
digestResponse.AppendFormat(", opaque={0}", _opaque.Quote());
if (!_qop.IsNullOrEmpty())
digestResponse.AppendFormat(", qop={0}", _qop);
if (!_nc.IsNullOrEmpty())
digestResponse.AppendFormat(", nc={0}", _nc);
if (!_qop.IsNullOrEmpty())
digestResponse.AppendFormat(", cnonce={0}", _cnonce.Quote());
return "Digest " + digestResponse.ToString();
} }
#endregion #endregion
#region Public Methods #region Public Methods
public static AuthenticationResponse Parse(string response) public static AuthenticationResponse Parse (string value)
{ {
throw new NotImplementedException(); try {
var credentials = value.Split (new char [] { ' ' }, 2);
if (credentials.Length != 2)
return null;
var scheme = credentials [0].ToLower ();
return scheme == "basic"
? new AuthenticationResponse (
scheme, credentials [1].ParseBasicAuthResponseParams ())
: scheme == "digest"
? new AuthenticationResponse (
scheme, credentials [1].ParseAuthParams ())
: null;
}
catch {
}
return null;
} }
public override string ToString() public IIdentity ToIdentity ()
{ {
return _scheme == "Basic" return _scheme == "basic"
? toBasicCredentials() ? new HttpBasicIdentity (
: toDigestCredentials(); _params ["username"], _params ["password"]) as IIdentity
: _scheme == "digest"
? new HttpDigestIdentity (_params)
: null;
}
public override string ToString ()
{
return _scheme == "basic"
? HttpUtility.CreateBasicAuthCredentials (
_params ["username"], _params ["password"])
: _scheme == "digest"
? HttpUtility.CreateDigestAuthCredentials (_params)
: String.Empty;
} }
#endregion #endregion

View File

@ -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.

View File

@ -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 ());

View File

@ -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 ();

View File

@ -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
public HandshakeResponse () private string _code;
: this (HttpStatusCode.SwitchingProtocols) private string _reason;
#endregion
#region Private Constructors
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 ();

View File

@ -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
#region Authors
/*
* Authors:
* Atsushi Enomoto <atsushi@ximian.com>
*/
#endregion
using System; using System;
namespace WebSocketSharp.Net { namespace WebSocketSharp.Net
{
/// <summary> /// <summary>
/// Contains the values of the schemes for authentication. /// Contains the values of the schemes for authentication.
/// </summary> /// </summary>
[Flags] [Flags]
public enum AuthenticationSchemes { public enum AuthenticationSchemes
{
/// <summary> /// <summary>
/// Indicates that no authentication is allowed. /// Indicates that no authentication is allowed.
/// </summary> /// </summary>
None, None,
/// <summary> /// <summary>
/// Indicates digest authentication. /// Indicates digest authentication.
/// </summary> /// </summary>
Digest = 1, Digest = 1,
/// <summary> /// <summary>
/// Indicates negotiating with the client to determine the authentication scheme. /// Indicates basic authentication.
/// </summary> /// </summary>
Negotiate = 2, Basic = 8,
/// <summary> /// <summary>
/// Indicates NTLM authentication. /// Indicates anonymous authentication.
/// </summary> /// </summary>
Ntlm = 4, Anonymous = 0x8000,
/// <summary> }
/// Indicates Windows authentication.
/// </summary>
IntegratedWindowsAuthentication = 6,
/// <summary>
/// Indicates basic authentication.
/// </summary>
Basic = 8,
/// <summary>
/// Indicates anonymous authentication.
/// </summary>
Anonymous = 0x8000,
}
} }

View File

@ -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
}
} }

View File

@ -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

View File

@ -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

View File

@ -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>
/// Provides access to the HTTP request and response information used by the
/// <see cref="HttpListener"/>.
/// </summary>
/// <remarks>
/// The HttpListenerContext class cannot be inherited.
/// </remarks>
public sealed class HttpListenerContext
{
#region Private Fields
/// <summary> private HttpConnection _connection;
/// Provides access to the HTTP request and response objects used by the <see cref="HttpListener"/> class. private string _error;
/// </summary> private int _errorStatus;
/// <remarks> private HttpListenerRequest _request;
/// The HttpListenerContext class cannot be inherited. private HttpListenerResponse _response;
/// </remarks> private IPrincipal _user;
public sealed class HttpListenerContext {
#region Private Fields #endregion
private HttpConnection _connection; #region Internal Fields
private string _error;
private int _errorStatus;
private HttpListenerRequest _request;
private HttpListenerResponse _response;
private IPrincipal _user;
#endregion internal HttpListener Listener;
#region Internal Fields #endregion
internal HttpListener Listener; #region Internal Constructors
#endregion internal HttpListenerContext (HttpConnection connection)
{
_connection = connection;
_errorStatus = 400;
_request = new HttpListenerRequest (this);
_response = new HttpListenerResponse (this);
}
#region Constructor #endregion
internal HttpListenerContext (HttpConnection connection) #region Internal Properties
{
_connection = connection;
_errorStatus = 400;
_request = new HttpListenerRequest (this);
_response = new HttpListenerResponse (this);
}
#endregion internal HttpConnection Connection {
get {
return _connection;
}
}
#region Internal Properties internal string ErrorMessage {
get {
return _error;
}
internal HttpConnection Connection { set {
get { _error = value;
return _connection; }
} }
}
internal string ErrorMessage { internal int ErrorStatus {
get { get {
return _error; return _errorStatus;
} }
set { set {
_error = value; _errorStatus = value;
} }
} }
internal int ErrorStatus { internal bool HaveError {
get { get {
return _errorStatus; return _error != null && _error.Length > 0;
} }
}
set { #endregion
_errorStatus = value;
}
}
internal bool HaveError { #region Public Properties
get {
return _error != null;
}
}
#endregion /// <summary>
/// Gets the <see cref="HttpListenerRequest"/> that contains the HTTP
/// request information from a client.
/// </summary>
/// <value>
/// A <see cref="HttpListenerRequest"/> that contains the HTTP request
/// information.
/// </value>
public HttpListenerRequest Request {
get {
return _request;
}
}
#region Public Properties /// <summary>
/// Gets the <see cref="HttpListenerResponse"/> that contains the HTTP
/// response information to send to the client in response to the client's
/// request.
/// </summary>
/// <value>
/// A <see cref="HttpListenerResponse"/> that contains the HTTP response
/// information.
/// </value>
public HttpListenerResponse Response {
get {
return _response;
}
}
/// <summary> /// <summary>
/// Gets the <see cref="HttpListenerRequest"/> that contains the HTTP request from a client. /// Gets the client information (identity, authentication information, and
/// </summary> /// security roles).
/// <value> /// </summary>
/// A <see cref="HttpListenerRequest"/> that contains the HTTP request objects. /// <value>
/// </value> /// A <see cref="IPrincipal"/> contains the client information.
public HttpListenerRequest Request { /// </value>
get { public IPrincipal User {
return _request; get {
} return _user;
} }
}
/// <summary> #endregion
/// Gets the <see cref="HttpListenerResponse"/> that contains the HTTP response to send to
/// the client in response to the client's request.
/// </summary>
/// <value>
/// A <see cref="HttpListenerResponse"/> that contains the HTTP response objects.
/// </value>
public HttpListenerResponse Response {
get {
return _response;
}
}
/// <summary> #region Internal Methods
/// Gets the client information (identity, authentication information and security roles).
/// </summary>
/// <value>
/// A <see cref="IPrincipal"/> contains the client information.
/// </value>
public IPrincipal User {
get {
return _user;
}
}
#endregion internal void SetUser (
AuthenticationSchemes expectedScheme,
string realm,
Func<IIdentity, NetworkCredential> credentialsFinder)
{
var authRes = AuthenticationResponse.Parse (_request.Headers ["Authorization"]);
if (authRes == null)
return;
#region Internal Methods var identity = authRes.ToIdentity ();
if (identity == null)
return;
internal void ParseAuthentication (AuthenticationSchemes expectedSchemes) NetworkCredential credentials = null;
{ try {
if (expectedSchemes == AuthenticationSchemes.Anonymous) credentials = credentialsFinder (identity);
return; }
catch {
}
// TODO: Handle NTLM/Digest modes. if (credentials == null)
var header = _request.Headers ["Authorization"]; return;
if (header == null || header.Length < 2)
return;
var authData = header.Split (new char [] {' '}, 2); var valid = expectedScheme == AuthenticationSchemes.Basic
if (authData [0].ToLower () == "basic") ? ((HttpBasicIdentity) identity).Password == credentials.Password
_user = ParseBasicAuthentication (authData [1]); : expectedScheme == AuthenticationSchemes.Digest
? ((HttpDigestIdentity) identity).IsValid (
credentials.Password, realm, _request.HttpMethod, null)
: false;
// TODO: Throw if malformed -> 400 bad request. if (valid)
} _user = new GenericPrincipal (identity, credentials.Roles);
}
internal IPrincipal ParseBasicAuthentication (string authData) #endregion
{
try {
// HTTP Basic Authentication data is a formatted Base64 string.
var authString = Encoding.Default.GetString (Convert.FromBase64String (authData));
// The format is domain\username:password. #region Public Method
// Domain is optional.
var pos = authString.IndexOf (':'); /// <summary>
var user = authString.Substring (0, pos); /// Accepts a WebSocket connection request.
var password = authString.Substring (pos + 1); /// </summary>
/// <returns>
/// A <see cref="HttpListenerWebSocketContext"/> that contains a WebSocket
/// connection request information.
/// </returns>
/// <param name="logger">
/// A <see cref="Logger"/> that provides the logging functions used in the
/// WebSocket attempts.
/// </param>
public HttpListenerWebSocketContext AcceptWebSocket (Logger logger)
{
return new HttpListenerWebSocketContext (this, logger);
}
// Check if there is a domain. #endregion
pos = user.IndexOf ('\\'); }
if (pos > 0)
user = user.Substring (pos + 1);
var identity = new System.Net.HttpListenerBasicIdentity (user, password);
// TODO: What are the roles MS sets?
return new GenericPrincipal (identity, new string [0]);
} catch {
return null;
}
}
#endregion
#region Public Method
/// <summary>
/// Accepts a WebSocket connection by the <see cref="HttpListener"/>.
/// </summary>
/// <returns>
/// A <see cref="HttpListenerWebSocketContext"/> that contains a WebSocket connection.
/// </returns>
public HttpListenerWebSocketContext AcceptWebSocket ()
{
return new HttpListenerWebSocketContext (this);
}
#endregion
}
} }

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

View File

@ -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
{
internal class ListenerAsyncResult : IAsyncResult
{
#region Private Fields
class ListenerAsyncResult : IAsyncResult private AsyncCallback _callback;
{ private bool _completed;
#region Private Static Field private HttpListenerContext _context;
private Exception _exception;
private ManualResetEvent _waitHandle;
private object _state;
private object _sync;
private bool _syncCompleted;
static WaitCallback InvokeCB = new WaitCallback (InvokeCallback); #endregion
#endregion #region Internal Fields
#region Private Fields internal bool EndCalled;
internal bool InGet;
AsyncCallback cb; #endregion
bool completed;
HttpListenerContext context;
Exception exception;
ListenerAsyncResult forward;
ManualResetEvent handle;
object locker;
object state;
bool synch;
#endregion #region Public Constructors
#region Internal Fields public ListenerAsyncResult (AsyncCallback callback, object state)
{
_callback = callback;
_state = state;
_sync = new object ();
}
internal bool EndCalled; #endregion
internal bool InGet;
#endregion #region Public Properties
#region Constructor public object AsyncState {
get {
return _state;
}
}
public ListenerAsyncResult (AsyncCallback cb, object state) public WaitHandle AsyncWaitHandle {
{ get {
this.cb = cb; lock (_sync)
this.state = state; return _waitHandle ?? (_waitHandle = new ManualResetEvent (_completed));
this.locker = new object(); }
} }
#endregion public bool CompletedSynchronously {
get {
return _syncCompleted;
}
}
#region Properties public bool IsCompleted {
get {
lock (_sync)
return _completed;
}
}
public object AsyncState { #endregion
get {
if (forward != null)
return forward.AsyncState;
return state; #region Private Methods
}
}
public WaitHandle AsyncWaitHandle { private static void invokeCallback (object state)
get { {
if (forward != null) try {
return forward.AsyncWaitHandle; var ares = (ListenerAsyncResult) state;
ares._callback (ares);
}
catch {
}
}
lock (locker) { #endregion
if (handle == null)
handle = new ManualResetEvent (completed);
}
return handle; #region Internal Methods
}
}
public bool CompletedSynchronously { internal void Complete (Exception exception)
get { {
if (forward != null) _exception = InGet && (exception is ObjectDisposedException)
return forward.CompletedSynchronously; ? new HttpListenerException (500, "Listener closed")
: exception;
return synch; lock (_sync) {
} _completed = true;
} if (_waitHandle != null)
_waitHandle.Set ();
public bool IsCompleted { if (_callback != null)
get { ThreadPool.UnsafeQueueUserWorkItem (invokeCallback, this);
if (forward != null) }
return forward.IsCompleted; }
lock (locker) { internal void Complete (HttpListenerContext context)
return completed; {
} Complete (context, false);
} }
}
#endregion internal void Complete (HttpListenerContext context, bool syncCompleted)
{
var listener = context.Listener;
var scheme = listener.SelectAuthenticationScheme (context);
if (scheme == AuthenticationSchemes.None) {
context.Response.Close (HttpStatusCode.Forbidden);
listener.BeginGetContext (this);
#region Private Method return;
}
static void InvokeCallback (object o) var header = context.Request.Headers ["Authorization"];
{ if (scheme == AuthenticationSchemes.Basic &&
ListenerAsyncResult ares = (ListenerAsyncResult) o; (header == null ||
if (ares.forward != null) { !header.StartsWith ("basic", StringComparison.OrdinalIgnoreCase))) {
InvokeCallback (ares.forward); context.Response.CloseWithAuthChallenge (
return; HttpUtility.CreateBasicAuthChallenge (listener.Realm));
} listener.BeginGetContext (this);
try { return;
ares.cb (ares); }
} catch {
}
}
#endregion if (scheme == AuthenticationSchemes.Digest &&
(header == null ||
!header.StartsWith ("digest", StringComparison.OrdinalIgnoreCase))) {
context.Response.CloseWithAuthChallenge (
HttpUtility.CreateDigestAuthChallenge (listener.Realm));
listener.BeginGetContext (this);
#region Internal Methods return;
}
internal void Complete (Exception exc) _context = context;
{ _syncCompleted = syncCompleted;
if (forward != null) {
forward.Complete (exc);
return;
}
exception = exc; lock (_sync) {
if (InGet && (exc is ObjectDisposedException)) _completed = true;
exception = new HttpListenerException (500, "Listener closed"); if (_waitHandle != null)
_waitHandle.Set ();
lock (locker) { if (_callback != null)
completed = true; ThreadPool.UnsafeQueueUserWorkItem (invokeCallback, this);
if (handle != null) }
handle.Set (); }
if (cb != null) internal HttpListenerContext GetContext ()
ThreadPool.UnsafeQueueUserWorkItem (InvokeCB, this); {
} if (_exception != null)
} throw _exception;
internal void Complete (HttpListenerContext context) return _context;
{ }
Complete (context, false);
}
internal void Complete (HttpListenerContext context, bool synch) #endregion
{ }
if (forward != null) {
forward.Complete (context, synch);
return;
}
this.synch = synch;
this.context = context;
lock (locker) {
AuthenticationSchemes schemes = context.Listener.SelectAuthenticationScheme (context);
if ((schemes == AuthenticationSchemes.Basic || context.Listener.AuthenticationSchemes == AuthenticationSchemes.Negotiate) && context.Request.Headers ["Authorization"] == null) {
context.Response.StatusCode = 401;
context.Response.Headers ["WWW-Authenticate"] = schemes + " realm=\"" + context.Listener.Realm + "\"";
context.Response.OutputStream.Close ();
IAsyncResult ares = context.Listener.BeginGetContext (cb, state);
this.forward = (ListenerAsyncResult) ares;
lock (forward.locker) {
if (handle != null)
forward.handle = handle;
}
ListenerAsyncResult next = forward;
for (int i = 0; next.forward != null; i++) {
if (i > 20)
Complete (new HttpListenerException (400, "Too many authentication errors"));
next = next.forward;
}
} else {
completed = true;
if (handle != null)
handle.Set ();
if (cb != null)
ThreadPool.UnsafeQueueUserWorkItem (InvokeCB, this);
}
}
}
internal HttpListenerContext GetContext ()
{
if (forward != null)
return forward.GetContext ();
if (exception != null)
throw exception;
return context;
}
#endregion
}
} }

View File

@ -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
}
}

View File

@ -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
{
internal class RequestStream : Stream
{
#region Private Fields
class RequestStream : Stream { private byte [] _buffer;
private bool _disposed;
private int _length;
private int _offset;
private long _remainingBody;
private Stream _stream;
#region Fields #endregion
byte [] buffer; #region Internal Constructors
bool disposed;
int length;
int offset;
long remaining_body;
Stream stream;
#endregion internal RequestStream (
Stream stream, byte [] buffer, int offset, int length)
: this (stream, buffer, offset, length, -1)
{
}
#region Constructors internal RequestStream (
Stream stream, byte [] buffer, int offset, int length, long contentlength)
{
_stream = stream;
_buffer = buffer;
_offset = offset;
_length = length;
_remainingBody = contentlength;
}
internal RequestStream (Stream stream, byte [] buffer, int offset, int length) #endregion
: this (stream, buffer, offset, length, -1)
{
}
internal RequestStream (Stream stream, byte [] buffer, int offset, int length, long contentlength) #region Public Properties
{
this.stream = stream;
this.buffer = buffer;
this.offset = offset;
this.length = length;
this.remaining_body = contentlength;
}
#endregion public override bool CanRead {
get {
return true;
}
}
#region Properties public override bool CanSeek {
get {
return false;
}
}
public override bool CanRead { public override bool CanWrite {
get { return true; } get {
} return false;
}
}
public override bool CanSeek { public override long Length {
get { return false; } get {
} throw new NotSupportedException ();
}
}
public override bool CanWrite { public override long Position {
get { return false; } get {
} throw new NotSupportedException ();
}
public override long Length { set {
get { throw new NotSupportedException (); } throw new NotSupportedException ();
} }
}
public override long Position { #endregion
get { throw new NotSupportedException (); }
set { throw new NotSupportedException (); }
}
#endregion #region Private Methods
#region Private Method // Returns 0 if we can keep reading from the base stream,
// > 0 if we read something from the buffer.
// -1 if we had a content length set and we finished reading that many bytes.
private int fillFromBuffer (byte [] buffer, int offset, int count)
{
if (buffer == null)
throw new ArgumentNullException ("buffer");
// Returns 0 if we can keep reading from the base stream, if (offset < 0)
// > 0 if we read something from the buffer. throw new ArgumentOutOfRangeException ("offset", "Less than zero.");
// -1 if we had a content length set and we finished reading that many bytes.
int FillFromBuffer (byte [] buffer, int offset, int count)
{
if (buffer == null)
throw new ArgumentNullException ("buffer");
if (offset < 0) if (count < 0)
throw new ArgumentOutOfRangeException ("offset", "< 0"); throw new ArgumentOutOfRangeException ("count", "Less than zero.");
if (count < 0) var len = buffer.Length;
throw new ArgumentOutOfRangeException ("count", "< 0"); if (offset > len)
throw new ArgumentException ("'offset' is greater than 'buffer' size.");
int len = buffer.Length; if (offset > len - count)
if (offset > len) throw new ArgumentException ("Reading would overrun 'buffer'.");
throw new ArgumentException ("Destination offset is beyond array size.");
if (offset > len - count) if (_remainingBody == 0)
throw new ArgumentException ("Reading would overrun buffer."); return -1;
if (this.remaining_body == 0) if (_length == 0)
return -1; return 0;
if (this.length == 0) var size = _length < count ? _length : count;
return 0; if (_remainingBody > 0 && _remainingBody < size)
size = (int) _remainingBody;
int size = Math.Min (this.length, count); var remainingBuffer = _buffer.Length - _offset;
if (this.remaining_body > 0) if (remainingBuffer < size)
size = (int) Math.Min (size, this.remaining_body); size = remainingBuffer;
if (this.offset > this.buffer.Length - size) { if (size == 0)
size = Math.Min (size, this.buffer.Length - this.offset); return 0;
}
if (size == 0) Buffer.BlockCopy (_buffer, _offset, buffer, offset, size);
return 0; _offset += size;
_length -= size;
if (_remainingBody > 0)
_remainingBody -= size;
Buffer.BlockCopy (this.buffer, this.offset, buffer, offset, size); return size;
this.offset += size; }
this.length -= size;
if (this.remaining_body > 0)
remaining_body -= size;
return size; #endregion
}
#endregion #region Public Methods
#region Public Methods public override IAsyncResult BeginRead (
byte [] buffer,
int offset,
int count,
AsyncCallback callback,
object state)
{
if (_disposed)
throw new ObjectDisposedException (GetType ().ToString ());
public override IAsyncResult BeginRead ( var read = fillFromBuffer (buffer, offset, count);
byte [] buffer, int offset, int count, AsyncCallback cback, object state) if (read > 0 || read == -1) {
{ var ares = new HttpStreamAsyncResult ();
if (disposed) ares.Buffer = buffer;
throw new ObjectDisposedException (GetType ().ToString ()); ares.Offset = offset;
ares.Count = count;
ares.Callback = callback;
ares.State = state;
ares.SyncRead = read;
ares.Complete ();
int nread = FillFromBuffer (buffer, offset, count); return ares;
if (nread > 0 || nread == -1) { }
var ares = new HttpStreamAsyncResult ();
ares.Buffer = buffer;
ares.Offset = offset;
ares.Count = count;
ares.Callback = cback;
ares.State = state;
ares.SyncRead = nread;
ares.Complete ();
return ares;
}
// Avoid reading past the end of the request to allow // Avoid reading past the end of the request to allow for HTTP pipelining.
// for HTTP pipelining if (_remainingBody >= 0 && _remainingBody < count)
if (remaining_body >= 0 && count > remaining_body) count = (int) _remainingBody;
count = (int) Math.Min (Int32.MaxValue, remaining_body);
return stream.BeginRead (buffer, offset, count, cback, state); return _stream.BeginRead (buffer, offset, count, callback, state);
} }
public override IAsyncResult BeginWrite ( public override IAsyncResult BeginWrite (
byte [] buffer, int offset, int count, AsyncCallback cback, object state) byte [] buffer, int offset, int count, AsyncCallback callback, object state)
{ {
throw new NotSupportedException (); throw new NotSupportedException ();
} }
public override void Close () public override void Close ()
{ {
disposed = true; _disposed = true;
} }
public override int EndRead (IAsyncResult ares) public override int EndRead (IAsyncResult asyncResult)
{ {
if (disposed) if (_disposed)
throw new ObjectDisposedException (GetType ().ToString ()); throw new ObjectDisposedException (GetType ().ToString ());
if (ares == null) if (asyncResult == null)
throw new ArgumentNullException ("ares"); throw new ArgumentNullException ("asyncResult");
if (ares is HttpStreamAsyncResult) { if (asyncResult is HttpStreamAsyncResult) {
var ares_ = (HttpStreamAsyncResult) ares; var ares = (HttpStreamAsyncResult) asyncResult;
if (!ares.IsCompleted) if (!ares.IsCompleted)
ares.AsyncWaitHandle.WaitOne (); ares.AsyncWaitHandle.WaitOne ();
return ares_.SyncRead; return ares.SyncRead;
} }
// Close on exception? // Close on exception?
int nread = stream.EndRead (ares); var read = _stream.EndRead (asyncResult);
if (remaining_body > 0 && nread > 0) if (read > 0 && _remainingBody > 0)
remaining_body -= nread; _remainingBody -= read;
return nread; return read;
} }
public override void EndWrite (IAsyncResult async_result) public override void EndWrite (IAsyncResult asyncResult)
{ {
throw new NotSupportedException (); throw new NotSupportedException ();
} }
public override void Flush () public override void Flush ()
{ {
} }
public override int Read ([In,Out] byte[] buffer, int offset, int count) public override int Read (byte [] buffer, int offset, int count)
{ {
if (disposed) if (_disposed)
throw new ObjectDisposedException (GetType () .ToString ()); throw new ObjectDisposedException (GetType ().ToString ());
// Call FillFromBuffer to check for buffer boundaries even when remaining_body is 0 // Call fillFromBuffer to check for buffer boundaries even when
int nread = FillFromBuffer (buffer, offset, count); // _remainingBody is 0.
if (nread == -1) { // No more bytes available (Content-Length) var read = fillFromBuffer (buffer, offset, count);
return 0; if (read == -1) // No more bytes available (Content-Length).
} else if (nread > 0) { return 0;
return nread; else if (read > 0)
} return read;
nread = stream.Read (buffer, offset, count); read = _stream.Read (buffer, offset, count);
if (nread > 0 && remaining_body > 0) if (read > 0 && _remainingBody > 0)
remaining_body -= nread; _remainingBody -= read;
return nread; return read;
} }
public override long Seek (long offset, SeekOrigin origin) public override long Seek (long offset, SeekOrigin origin)
{ {
throw new NotSupportedException (); throw new NotSupportedException ();
} }
public override void SetLength (long value) public override void SetLength (long value)
{ {
throw new NotSupportedException (); throw new NotSupportedException ();
} }
public override void Write (byte[] buffer, int offset, int count) public override void Write (byte [] buffer, int offset, int count)
{ {
throw new NotSupportedException (); throw new NotSupportedException ();
} }
#endregion #endregion
} }
} }

View File

@ -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?
// Update: we send a single packet for the first non-chunked Write
// What happens when we set content-length to X and write X-1 bytes then close?
// what if we don't set content-length at all?
internal class ResponseStream : Stream
{
#region Private Static Fields
// FIXME: Does this buffer the response until Close? private static byte [] _crlf = new byte [] { 13, 10 };
// Update: we send a single packet for the first non-chunked Write
// What happens when we set content-length to X and write X-1 bytes then close?
// what if we don't set content-length at all?
class ResponseStream : Stream {
#region Private Static Field #endregion
static byte [] crlf = new byte [] { 13, 10 }; #region Private Fields
#endregion private bool _disposed;
private bool _ignoreErrors;
private HttpListenerResponse _response;
private Stream _stream;
private bool _trailerSent;
#region Private Fields #endregion
bool disposed; #region Internal Constructors
bool ignore_errors;
HttpListenerResponse response;
Stream stream;
bool trailer_sent;
#endregion internal ResponseStream (
Stream stream, HttpListenerResponse response, bool ignoreErrors)
{
_stream = stream;
_response = response;
_ignoreErrors = ignoreErrors;
}
#region Constructor #endregion
internal ResponseStream (System.IO.Stream stream, HttpListenerResponse response, bool ignore_errors) #region Public Properties
{
this.stream = stream;
this.response = response;
this.ignore_errors = ignore_errors;
}
#endregion public override bool CanRead {
get {
return false;
}
}
#region Properties public override bool CanSeek {
get {
return false;
}
}
public override bool CanRead { public override bool CanWrite {
get { return false; } get {
} return true;
}
}
public override bool CanSeek { public override long Length {
get { return false; } get {
} throw new NotSupportedException ();
}
}
public override bool CanWrite { public override long Position {
get { return true; } get {
} throw new NotSupportedException ();
}
public override long Length { set {
get { throw new NotSupportedException (); } throw new NotSupportedException ();
} }
}
public override long Position { #endregion
get { throw new NotSupportedException (); }
set { throw new NotSupportedException (); }
}
#endregion #region Private Methods
#region Private Methods private static byte [] getChunkSizeBytes (int size, bool final)
{
return Encoding.ASCII.GetBytes (
String.Format ("{0:x}\r\n{1}", size, final ? "\r\n" : ""));
}
static byte [] GetChunkSizeBytes (int size, bool final) private MemoryStream getHeaders (bool closing)
{ {
string str = String.Format ("{0:x}\r\n{1}", size, final ? "\r\n" : ""); if (_response.HeadersSent)
return Encoding.ASCII.GetBytes (str); return null;
}
MemoryStream GetHeaders (bool closing) var stream = new MemoryStream ();
{ _response.SendHeaders (closing, stream);
if (response.HeadersSent)
return null;
MemoryStream ms = new MemoryStream (); return stream;
response.SendHeaders (closing, ms); }
return ms; #endregion
}
#endregion #region Internal Methods
#region Internal Method internal void InternalWrite (byte [] buffer, int offset, int count)
{
if (_ignoreErrors) {
try {
_stream.Write (buffer, offset, count);
}
catch {
}
}
else {
_stream.Write (buffer, offset, count);
}
}
internal void InternalWrite (byte [] buffer, int offset, int count) #endregion
{
if (ignore_errors) {
try {
stream.Write (buffer, offset, count);
} catch {
}
} else {
stream.Write (buffer, offset, count);
}
}
#endregion #region Public Methods
#region Public Methods public override IAsyncResult BeginRead (
byte [] buffer,
int offset,
int count,
AsyncCallback callback,
object state)
{
throw new NotSupportedException ();
}
public override IAsyncResult BeginRead ( public override IAsyncResult BeginWrite (
byte [] buffer, byte [] buffer,
int offset, int offset,
int count, int count,
AsyncCallback cback, AsyncCallback callback,
object state) object state)
{ {
throw new NotSupportedException (); if (_disposed)
} throw new ObjectDisposedException (GetType ().ToString ());
public override IAsyncResult BeginWrite ( var stream = getHeaders (false);
byte [] buffer, var chunked = _response.SendChunked;
int offset, byte [] bytes = null;
int count, if (stream != null) {
AsyncCallback cback, var start = stream.Position;
object state) stream.Position = stream.Length;
{ if (chunked) {
if (disposed) bytes = getChunkSizeBytes (count, false);
throw new ObjectDisposedException (GetType ().ToString ()); stream.Write (bytes, 0, bytes.Length);
}
byte [] bytes = null; stream.Write (buffer, offset, count);
MemoryStream ms = GetHeaders (false); buffer = stream.GetBuffer ();
bool chunked = response.SendChunked; offset = (int) start;
if (ms != null) { count = (int) (stream.Position - start);
long start = ms.Position; }
ms.Position = ms.Length; else if (chunked) {
if (chunked) { bytes = getChunkSizeBytes (count, false);
bytes = GetChunkSizeBytes (count, false); InternalWrite (bytes, 0, bytes.Length);
ms.Write (bytes, 0, bytes.Length); }
}
ms.Write (buffer, offset, count);
buffer = ms.GetBuffer ();
offset = (int) start;
count = (int) (ms.Position - start);
} else if (chunked) {
bytes = GetChunkSizeBytes (count, false);
InternalWrite (bytes, 0, bytes.Length);
}
return stream.BeginWrite (buffer, offset, count, cback, state); return _stream.BeginWrite (buffer, offset, count, callback, state);
} }
public override void Close () public override void Close ()
{ {
if (disposed == false) { if (_disposed)
disposed = true; return;
byte [] bytes = null;
MemoryStream ms = GetHeaders (true);
bool chunked = response.SendChunked;
if (ms != null) {
long start = ms.Position;
if (chunked && !trailer_sent) {
bytes = GetChunkSizeBytes (0, true);
ms.Position = ms.Length;
ms.Write (bytes, 0, bytes.Length);
}
InternalWrite (ms.GetBuffer (), (int) start, (int) (ms.Length - start));
trailer_sent = true;
} else if (chunked && !trailer_sent) {
bytes = GetChunkSizeBytes (0, true);
InternalWrite (bytes, 0, bytes.Length);
trailer_sent = true;
}
response.Close (); _disposed = true;
}
}
public override int EndRead (IAsyncResult ares) var stream = getHeaders (true);
{ var chunked = _response.SendChunked;
throw new NotSupportedException (); byte [] bytes = null;
} if (stream != null) {
var start = stream.Position;
if (chunked && !_trailerSent) {
bytes = getChunkSizeBytes (0, true);
stream.Position = stream.Length;
stream.Write (bytes, 0, bytes.Length);
}
public override void EndWrite (IAsyncResult ares) InternalWrite (
{ stream.GetBuffer (), (int) start, (int) (stream.Length - start));
if (disposed) _trailerSent = true;
throw new ObjectDisposedException (GetType ().ToString ()); }
else if (chunked && !_trailerSent) {
bytes = getChunkSizeBytes (0, true);
InternalWrite (bytes, 0, bytes.Length);
_trailerSent = true;
}
if (ignore_errors) { _response.Close ();
try { }
stream.EndWrite (ares);
if (response.SendChunked)
stream.Write (crlf, 0, 2);
} catch {
}
} else {
stream.EndWrite (ares);
if (response.SendChunked)
stream.Write (crlf, 0, 2);
}
}
public override void Flush () public override int EndRead (IAsyncResult asyncResult)
{ {
} 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) {
{ try {
throw new NotSupportedException (); endWrite (asyncResult);
} }
catch {
}
}
else {
endWrite (asyncResult);
}
}
public override void Write (byte [] buffer, int offset, int count) public override void Flush ()
{ {
if (disposed) }
throw new ObjectDisposedException (GetType ().ToString ());
byte [] bytes = null; public override int Read (byte [] buffer, int offset, int count)
MemoryStream ms = GetHeaders (false); {
bool chunked = response.SendChunked; throw new NotSupportedException ();
if (ms != null) { }
long start = ms.Position; // After the possible preamble for the encoding
ms.Position = ms.Length;
if (chunked) {
bytes = GetChunkSizeBytes (count, false);
ms.Write (bytes, 0, bytes.Length);
}
int new_count = Math.Min (count, 16384 - (int) ms.Position + (int) start); public override long Seek (long offset, SeekOrigin origin)
ms.Write (buffer, offset, new_count); {
count -= new_count; throw new NotSupportedException ();
offset += new_count; }
InternalWrite (ms.GetBuffer (), (int) start, (int) (ms.Length - start));
ms.SetLength (0);
ms.Capacity = 0; // 'dispose' the buffer in ms.
} else if (chunked) {
bytes = GetChunkSizeBytes (count, false);
InternalWrite (bytes, 0, bytes.Length);
}
if (count > 0) public override void SetLength (long value)
InternalWrite (buffer, offset, count); {
throw new NotSupportedException ();
}
if (chunked) public override void Write (byte [] buffer, int offset, int count)
InternalWrite (crlf, 0, 2); {
} if (_disposed)
throw new ObjectDisposedException (GetType ().ToString ());
#endregion var stream = getHeaders (false);
} var chunked = _response.SendChunked;
byte [] bytes = null;
if (stream != null) {
// After the possible preamble for the encoding.
var start = stream.Position;
stream.Position = stream.Length;
if (chunked) {
bytes = getChunkSizeBytes (count, false);
stream.Write (bytes, 0, bytes.Length);
}
var newCount = Math.Min (
count, 16384 - (int) stream.Position + (int) start);
stream.Write (buffer, offset, newCount);
count -= newCount;
offset += newCount;
InternalWrite (
stream.GetBuffer (), (int) start, (int) (stream.Length - start));
stream.SetLength (0);
stream.Capacity = 0; // 'dispose' the buffer in stream.
}
else if (chunked) {
bytes = getChunkSizeBytes (count, false);
InternalWrite (bytes, 0, bytes.Length);
}
if (count > 0)
InternalWrite (buffer, offset, count);
if (chunked)
InternalWrite (_crlf, 0, 2);
}
#endregion
}
} }

View File

@ -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 ()
{ {

View File

@ -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 ()
{ {

View File

@ -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"/>.

View File

@ -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 ();
init (); 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);
} }
#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> /// </summary>
/// <value> /// <value>
/// A <see cref="WebSocketServiceHostManager"/> that manages the WebSocket services. /// A Func&lt;<see cref="IIdentity"/>, <see cref="NetworkCredential"/>&gt;
/// delegate that references the method(s) used to find the credentials. The
/// default value is a function that only returns <see langword="null"/>.
/// </value>
public Func<IIdentity, NetworkCredential> UserCredentialsFinder {
get {
return _listener.UserCredentialsFinder;
}
set {
if (!canSet ("UserCredentialsFinder"))
return;
_listener.UserCredentialsFinder = value;
}
}
/// <summary>
/// Gets the functions for the WebSocket services provided by the
/// <see cref="HttpServer"/>.
/// </summary>
/// <value>
/// A <see cref="WebSocketServiceHostManager"/> that manages the WebSocket
/// services.
/// </value> /// </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,12 +405,136 @@ 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 void acceptHttpRequest (HttpListenerContext context)
{
var args = new HttpRequestEventArgs (context);
var method = context.Request.HttpMethod;
if (method == "GET" && OnGet != null) {
OnGet (this, args);
return;
}
if (method == "HEAD" && OnHead != null) {
OnHead (this, args);
return;
}
if (method == "POST" && OnPost != null) {
OnPost (this, args);
return;
}
if (method == "PUT" && OnPut != null) {
OnPut (this, args);
return;
}
if (method == "DELETE" && OnDelete != null) {
OnDelete (this, args);
return;
}
if (method == "OPTIONS" && OnOptions != null) {
OnOptions (this, args);
return;
}
if (method == "TRACE" && OnTrace != null) {
OnTrace (this, args);
return;
}
if (method == "CONNECT" && OnConnect != null) {
OnConnect (this, args);
return;
}
if (method == "PATCH" && OnPatch != null) {
OnPatch (this, args);
return;
}
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 void acceptWebSocketRequest (HttpListenerWebSocketContext context)
{
var path = context.Path;
WebSocketServiceHost host;
if (path == null ||
!_serviceHosts.TryGetServiceHostInternally (path, out host)) {
context.Close (HttpStatusCode.NotImplemented);
return;
}
host.StartSession (context);
}
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 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 && return _secure &&
@ -346,137 +544,17 @@ namespace WebSocketSharp.Server
: null; : 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 method = context.Request.HttpMethod;
if (method == "GET" && OnGet != null)
{
OnGet (this, args);
return;
}
if (method == "HEAD" && OnHead != null)
{
OnHead (this, args);
return;
}
if (method == "POST" && OnPost != null)
{
OnPost (this, args);
return;
}
if (method == "PUT" && OnPut != null)
{
OnPut (this, args);
return;
}
if (method == "DELETE" && OnDelete != null)
{
OnDelete (this, args);
return;
}
if (method == "OPTIONS" && OnOptions != null)
{
OnOptions (this, args);
return;
}
if (method == "TRACE" && OnTrace != null)
{
OnTrace (this, args);
return;
}
if (method == "CONNECT" && OnConnect != null)
{
OnConnect (this, args);
return;
}
if (method == "PATCH" && OnPatch != null)
{
OnPatch (this, args);
return;
}
context.Response.StatusCode = (int) HttpStatusCode.NotImplemented;
}
private bool processWebSocketRequest (HttpListenerContext context)
{
var wsContext = context.AcceptWebSocket ();
var path = wsContext.Path;
WebSocketServiceHost host;
if (path == null || !_serviceHosts.TryGetServiceHostInternally (path, out host))
{
context.Response.StatusCode = (int) HttpStatusCode.NotImplemented;
return false;
}
wsContext.WebSocket.Log = _logger;
host.StartSession (wsContext);
return true;
}
private void processRequestAsync (HttpListenerContext context)
{
WaitCallback callback = state =>
{
try {
if (context.Request.IsUpgradeTo ("websocket"))
{
if (processWebSocketRequest (context))
return;
}
else
{
processHttpRequest (context);
}
context.Response.Close ();
}
catch (Exception ex) {
_logger.Fatal (ex.ToString ());
context.Connection.Close (true);
}
};
ThreadPool.QueueUserWorkItem (callback);
}
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;
} }

View File

@ -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 acceptRequestAsync (TcpClient client)
{
ThreadPool.QueueUserWorkItem (
state => {
try {
var context = client.GetWebSocketContext (_cert, _secure, _logger);
if (_authSchemes != AuthenticationSchemes.Anonymous &&
!authenticateRequest (_authSchemes, context))
return;
acceptWebSocket (context);
}
catch (Exception ex) {
_logger.Fatal (ex.ToString ());
client.Close ();
}
});
}
private void acceptWebSocket (TcpListenerWebSocketContext context) private void acceptWebSocket (TcpListenerWebSocketContext context)
{ {
var websocket = context.WebSocket;
websocket.Log = _logger;
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;
} }

View File

@ -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;
if (_origin.Length > 0) headers ["Host"] = host;
req.AddHeader ("Origin", _origin);
req.AddHeader ("Sec-WebSocket-Key", _base64key); if (!_origin.IsNullOrEmpty ())
headers ["Origin"] = _origin;
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;
} }

View File

@ -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
}
}

View File

@ -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);

View File

@ -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>