Fix due to the added HttpServer

This commit is contained in:
sta
2012-09-10 01:36:22 +09:00
parent 022368dabb
commit d0e5ae3979
108 changed files with 8032 additions and 81 deletions

View File

@@ -69,19 +69,18 @@ namespace WebSocketSharp
public CloseEventArgs(PayloadData data)
: base(Opcode.CLOSE, data)
{
_code = data.ToBytes().SubArray(0, 2).To<ushort>(ByteOrder.BIG);
_code = (ushort)CloseStatusCode.NO_STATUS_CODE;
_reason = String.Empty;
_wasClean = false;
if (data.Length >= 2)
_code = data.ToBytes().SubArray(0, 2).To<ushort>(ByteOrder.BIG);
if (data.Length > 2)
{
var buffer = data.ToBytes().SubArray(2, (int)(data.Length - 2));
_reason = Encoding.UTF8.GetString(buffer);
}
else
{
_reason = String.Empty;
}
_wasClean = false;
}
}
}

View File

@@ -1,11 +1,17 @@
#region MIT License
/**
* Ext.cs
* IsPredefinedScheme and MaybeUri methods derived from System.Uri
*
* The MIT License
*
* (C) 2001 Garrett Rooney (System.Uri)
* (C) 2003 Ian MacLean (System.Uri)
* (C) 2003 Ben Maurer (System.Uri)
* Copyright (C) 2003,2009 Novell, Inc (http://www.novell.com) (System.Uri)
* Copyright (c) 2009 Stephane Delcroix (System.Uri)
* Copyright (c) 2010-2012 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
@@ -28,9 +34,11 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Text;
using WebSocketSharp.Net;
namespace WebSocketSharp
{
@@ -64,6 +72,26 @@ namespace WebSocketSharp
return b == Convert.ToByte(c);
}
public static bool Exists(this NameValueCollection headers, string name)
{
return headers[name] != null
? true
: false;
}
public static bool Exists(this NameValueCollection headers, string name, string value)
{
var values = headers[name];
if (values == null)
return false;
foreach (string v in values.Split(','))
if (String.Compare(v.Trim(), value, true) == 0)
return true;
return false;
}
public static string GetHeaderValue(this string src, string separater)
{
int i = src.IndexOf(separater);
@@ -94,6 +122,39 @@ namespace WebSocketSharp
return false;
}
// Derived from System.Uri.IsPredefinedScheme method
public static bool IsPredefinedScheme(this string scheme)
{
if (scheme == null && scheme.Length < 3)
return false;
char c = scheme[0];
if (c == 'h')
return (scheme == "http" || scheme == "https");
if (c == 'f')
return (scheme == "file" || scheme == "ftp");
if (c == 'n')
{
c = scheme[1];
if (c == 'e')
return (scheme == "news" || scheme == "net.pipe" || scheme == "net.tcp");
if (scheme == "nntp")
return true;
return false;
}
if ((c == 'g' && scheme == "gopher") || (c == 'm' && scheme == "mailto"))
return true;
return false;
}
public static bool NotEqualsDo(
this string expected,
string actual,
@@ -111,6 +172,19 @@ namespace WebSocketSharp
return false;
}
// Derived from System.Uri.MaybeUri method
public static bool MaybeUri(this string uriString)
{
int p = uriString.IndexOf(':');
if (p == -1)
return false;
if (p >= 10)
return false;
return uriString.Substring(0, p).IsPredefinedScheme();
}
public static byte[] ReadBytes<TStream>(this TStream stream, ulong length, int bufferLength)
where TStream : System.IO.Stream
{
@@ -320,5 +394,21 @@ namespace WebSocketSharp
return sb.ToString();
}
public static Uri ToUri(this string uriString)
{
if (!uriString.MaybeUri())
return new Uri(uriString, UriKind.Relative);
return new Uri(uriString);
}
public static void WriteContent(this HttpListenerResponse response, byte[] content)
{
var output = response.OutputStream;
response.ContentLength64 = content.Length;
output.Write(content, 0, content.Length);
output.Close();
}
}
}

View File

@@ -29,19 +29,36 @@
using System;
using System.Collections.Specialized;
using System.Text;
using WebSocketSharp.Net;
namespace WebSocketSharp {
public abstract class Handshake {
#region Field
protected const string _crlf = "\r\n";
#endregion
#region Constructor
protected Handshake()
{
ProtocolVersion = HttpVersion.Version11;
Headers = new NameValueCollection();
}
public NameValueCollection Headers { get; protected set; }
public string Version { get; protected set; }
#endregion
#region Properties
public NameValueCollection Headers { get; protected set; }
public Version ProtocolVersion { get; protected set; }
#endregion
#region Methods
public void AddHeader(string name, string value)
{
@@ -55,27 +72,19 @@ namespace WebSocketSharp {
public bool HeaderExists(string name)
{
return Headers[name] != null
? true
: false;
return Headers.Exists(name);
}
public bool HeaderExists(string name, string value)
{
var values = GetHeaderValues(name);
if (values == null)
return false;
foreach (string v in values)
if (String.Compare(value, v, true) == 0)
return true;
return false;
return Headers.Exists(name, value);
}
public byte[] ToBytes()
{
return Encoding.UTF8.GetBytes(ToString());
}
#endregion
}
}

View File

@@ -0,0 +1,35 @@
//
// AuthenticationSchemeSelector.cs
// Copied from System.Net.AuthenticationSchemeSelector
//
// Author:
// Gonzalo Paniagua Javier <gonzalo@novell.com>
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
namespace WebSocketSharp.Net {
public delegate AuthenticationSchemes AuthenticationSchemeSelector (HttpListenerRequest httpRequest);
}

View File

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

View File

@@ -0,0 +1,342 @@
//
// ChunkStream.cs
// Copied from System.Net.ChunkStream
//
// Authors:
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
//
// (C) 2003 Ximian, Inc (http://www.ximian.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
namespace WebSocketSharp.Net {
class ChunkStream {
enum State {
None,
Body,
BodyFinished,
Trailer
}
class Chunk {
public byte [] Bytes;
public int Offset;
public Chunk (byte [] chunk)
{
this.Bytes = chunk;
}
public int Read (byte [] buffer, int offset, int size)
{
int nread = (size > Bytes.Length - Offset) ? Bytes.Length - Offset : size;
Buffer.BlockCopy (Bytes, Offset, buffer, offset, nread);
Offset += nread;
return nread;
}
}
internal WebHeaderCollection headers;
int chunkSize;
int chunkRead;
State state;
//byte [] waitBuffer;
StringBuilder saved;
bool sawCR;
bool gotit;
int trailerState;
ArrayList chunks;
public ChunkStream (byte [] buffer, int offset, int size, WebHeaderCollection headers)
: this (headers)
{
Write (buffer, offset, size);
}
public ChunkStream (WebHeaderCollection headers)
{
this.headers = headers;
saved = new StringBuilder ();
chunks = new ArrayList ();
chunkSize = -1;
}
public void ResetBuffer ()
{
chunkSize = -1;
chunkRead = 0;
chunks.Clear ();
}
public void WriteAndReadBack (byte [] buffer, int offset, int size, ref int read)
{
if (offset + read > 0)
Write (buffer, offset, offset+read);
read = Read (buffer, offset, size);
}
public int Read (byte [] buffer, int offset, int size)
{
return ReadFromChunks (buffer, offset, size);
}
int ReadFromChunks (byte [] buffer, int offset, int size)
{
int count = chunks.Count;
int nread = 0;
for (int i = 0; i < count; i++) {
Chunk chunk = (Chunk) chunks [i];
if (chunk == null)
continue;
if (chunk.Offset == chunk.Bytes.Length) {
chunks [i] = null;
continue;
}
nread += chunk.Read (buffer, offset + nread, size - nread);
if (nread == size)
break;
}
return nread;
}
public void Write (byte [] buffer, int offset, int size)
{
InternalWrite (buffer, ref offset, size);
}
void InternalWrite (byte [] buffer, ref int offset, int size)
{
if (state == State.None) {
state = GetChunkSize (buffer, ref offset, size);
if (state == State.None)
return;
saved.Length = 0;
sawCR = false;
gotit = false;
}
if (state == State.Body && offset < size) {
state = ReadBody (buffer, ref offset, size);
if (state == State.Body)
return;
}
if (state == State.BodyFinished && offset < size) {
state = ReadCRLF (buffer, ref offset, size);
if (state == State.BodyFinished)
return;
sawCR = false;
}
if (state == State.Trailer && offset < size) {
state = ReadTrailer (buffer, ref offset, size);
if (state == State.Trailer)
return;
saved.Length = 0;
sawCR = false;
gotit = false;
}
if (offset < size)
InternalWrite (buffer, ref offset, size);
}
public bool WantMore {
get { return (chunkRead != chunkSize || chunkSize != 0 || state != State.None); }
}
public int ChunkLeft {
get { return chunkSize - chunkRead; }
}
State ReadBody (byte [] buffer, ref int offset, int size)
{
if (chunkSize == 0)
return State.BodyFinished;
int diff = size - offset;
if (diff + chunkRead > chunkSize)
diff = chunkSize - chunkRead;
byte [] chunk = new byte [diff];
Buffer.BlockCopy (buffer, offset, chunk, 0, diff);
chunks.Add (new Chunk (chunk));
offset += diff;
chunkRead += diff;
return (chunkRead == chunkSize) ? State.BodyFinished : State.Body;
}
State GetChunkSize (byte [] buffer, ref int offset, int size)
{
char c = '\0';
while (offset < size) {
c = (char) buffer [offset++];
if (c == '\r') {
if (sawCR)
ThrowProtocolViolation ("2 CR found");
sawCR = true;
continue;
}
if (sawCR && c == '\n')
break;
if (c == ' ')
gotit = true;
if (!gotit)
saved.Append (c);
if (saved.Length > 20)
ThrowProtocolViolation ("chunk size too long.");
}
if (!sawCR || c != '\n') {
if (offset < size)
ThrowProtocolViolation ("Missing \\n");
try {
if (saved.Length > 0) {
chunkSize = Int32.Parse (RemoveChunkExtension (saved.ToString ()), NumberStyles.HexNumber);
}
} catch (Exception) {
ThrowProtocolViolation ("Cannot parse chunk size.");
}
return State.None;
}
chunkRead = 0;
try {
chunkSize = Int32.Parse (RemoveChunkExtension (saved.ToString ()), NumberStyles.HexNumber);
} catch (Exception) {
ThrowProtocolViolation ("Cannot parse chunk size.");
}
if (chunkSize == 0) {
trailerState = 2;
return State.Trailer;
}
return State.Body;
}
static string RemoveChunkExtension (string input)
{
int idx = input.IndexOf (';');
if (idx == -1)
return input;
return input.Substring (0, idx);
}
State ReadCRLF (byte [] buffer, ref int offset, int size)
{
if (!sawCR) {
if ((char) buffer [offset++] != '\r')
ThrowProtocolViolation ("Expecting \\r");
sawCR = true;
if (offset == size)
return State.BodyFinished;
}
if (sawCR && (char) buffer [offset++] != '\n')
ThrowProtocolViolation ("Expecting \\n");
return State.None;
}
State ReadTrailer (byte [] buffer, ref int offset, int size)
{
char c = '\0';
// short path
if (trailerState == 2 && (char) buffer [offset] == '\r' && saved.Length == 0) {
offset++;
if (offset < size && (char) buffer [offset] == '\n') {
offset++;
return State.None;
}
offset--;
}
int st = trailerState;
string stString = "\r\n\r";
while (offset < size && st < 4) {
c = (char) buffer [offset++];
if ((st == 0 || st == 2) && c == '\r') {
st++;
continue;
}
if ((st == 1 || st == 3) && c == '\n') {
st++;
continue;
}
if (st > 0) {
saved.Append (stString.Substring (0, saved.Length == 0? st-2: st));
st = 0;
if (saved.Length > 4196)
ThrowProtocolViolation ("Error reading trailer (too long).");
}
}
if (st < 4) {
trailerState = st;
if (offset < size)
ThrowProtocolViolation ("Error reading trailer.");
return State.Trailer;
}
StringReader reader = new StringReader (saved.ToString ());
string line;
while ((line = reader.ReadLine ()) != null && line != "")
headers.Add (line);
return State.None;
}
static void ThrowProtocolViolation (string message)
{
WebException we = new WebException (message, null, WebExceptionStatus.ServerProtocolViolation, null);
throw we;
}
}
}

View File

@@ -0,0 +1,181 @@
//
// ChunkedInputStream.cs
// Copied from System.Net.ChunkedInputStream
//
// Authors:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.InteropServices;
namespace WebSocketSharp.Net {
class ChunkedInputStream : RequestStream
{
HttpListenerContext context;
ChunkStream decoder;
bool disposed;
bool no_more_data;
class ReadBufferState {
public HttpStreamAsyncResult Ares;
public byte [] Buffer;
public int Count;
public int InitialCount;
public int Offset;
public ReadBufferState (
byte [] buffer, int offset, int count, HttpStreamAsyncResult ares)
{
Buffer = buffer;
Offset = offset;
Count = count;
InitialCount = count;
Ares = ares;
}
}
public ChunkedInputStream (
HttpListenerContext context, Stream stream, byte [] buffer, int offset, int length)
: base (stream, buffer, offset, length)
{
this.context = context;
WebHeaderCollection coll = (WebHeaderCollection) context.Request.Headers;
decoder = new ChunkStream (coll);
}
public ChunkStream Decoder {
get { return decoder; }
set { decoder = value; }
}
void OnRead (IAsyncResult base_ares)
{
ReadBufferState rb = (ReadBufferState) base_ares.AsyncState;
HttpStreamAsyncResult ares = rb.Ares;
try {
int nread = base.EndRead (base_ares);
decoder.Write (ares.Buffer, ares.Offset, nread);
nread = decoder.Read (rb.Buffer, rb.Offset, rb.Count);
rb.Offset += nread;
rb.Count -= nread;
if (rb.Count == 0 || !decoder.WantMore || nread == 0) {
no_more_data = !decoder.WantMore && nread == 0;
ares.Count = rb.InitialCount - rb.Count;
ares.Complete ();
return;
}
ares.Offset = 0;
ares.Count = Math.Min (8192, decoder.ChunkLeft + 6);
base.BeginRead (ares.Buffer, ares.Offset, ares.Count, OnRead, rb);
} catch (Exception e) {
context.Connection.SendError (e.Message, 400);
ares.Complete (e);
}
}
public override IAsyncResult BeginRead (
byte [] buffer, int offset, int count, AsyncCallback cback, object state)
{
if (disposed)
throw new ObjectDisposedException (GetType ().ToString ());
if (buffer == null)
throw new ArgumentNullException ("buffer");
int len = buffer.Length;
if (offset < 0 || offset > len)
throw new ArgumentOutOfRangeException ("offset exceeds the size of buffer");
if (count < 0 || offset > len - count)
throw new ArgumentOutOfRangeException ("offset+size exceeds the size of buffer");
HttpStreamAsyncResult ares = new HttpStreamAsyncResult ();
ares.Callback = cback;
ares.State = state;
if (no_more_data) {
ares.Complete ();
return ares;
}
int nread = decoder.Read (buffer, offset, count);
offset += nread;
count -= nread;
if (count == 0) {
// got all we wanted, no need to bother the decoder yet
ares.Count = nread;
ares.Complete ();
return ares;
}
if (!decoder.WantMore) {
no_more_data = nread == 0;
ares.Count = nread;
ares.Complete ();
return ares;
}
ares.Buffer = new byte [8192];
ares.Offset = 0;
ares.Count = 8192;
ReadBufferState rb = new ReadBufferState (buffer, offset, count, ares);
rb.InitialCount += nread;
base.BeginRead (ares.Buffer, ares.Offset, ares.Count, OnRead, rb);
return ares;
}
public override void Close ()
{
if (!disposed) {
disposed = true;
base.Close ();
}
}
public override int EndRead (IAsyncResult ares)
{
if (disposed)
throw new ObjectDisposedException (GetType ().ToString ());
HttpStreamAsyncResult my_ares = ares as HttpStreamAsyncResult;
if (ares == null)
throw new ArgumentException ("Invalid IAsyncResult", "ares");
if (!ares.IsCompleted)
ares.AsyncWaitHandle.WaitOne ();
if (my_ares.Error != null)
throw new HttpListenerException (400, "I/O operation aborted.");
return my_ares.Count;
}
public override int Read ([In,Out] byte [] buffer, int offset, int count)
{
IAsyncResult ares = BeginRead (buffer, offset, count, null, null);
return EndRead (ares);
}
}
}

View File

@@ -0,0 +1,350 @@
//
// Cookie.cs
// Copied from System.Net.Cookie
//
// Authors:
// Lawrence Pit (loz@cable.a2000.nl)
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
// Daniel Nauck (dna@mono-project.de)
// Sebastien Pouliot (sebastien@ximian.com)
//
// Copyright (C) 2004,2009 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Text;
using System.Globalization;
using System.Collections;
using System.Net;
namespace WebSocketSharp.Net {
// Supported cookie formats are:
// Netscape: http://home.netscape.com/newsref/std/cookie_spec.html
// RFC 2109: http://www.ietf.org/rfc/rfc2109.txt
// RFC 2965: http://www.ietf.org/rfc/rfc2965.txt
[Serializable]
public sealed class Cookie
{
string comment;
Uri commentUri;
bool discard;
string domain;
DateTime expires;
bool httpOnly;
string name;
string path;
string port;
int [] ports;
bool secure;
DateTime timestamp;
string val;
int version;
static char [] reservedCharsName = new char [] {' ', '=', ';', ',', '\n', '\r', '\t'};
static char [] portSeparators = new char [] {'"', ','};
static string tspecials = "()<>@,;:\\\"/[]?={} \t"; // from RFC 2965, 2068
public Cookie ()
{
expires = DateTime.MinValue;
timestamp = DateTime.Now;
domain = String.Empty;
name = String.Empty;
val = String.Empty;
comment = String.Empty;
port = String.Empty;
}
public Cookie (string name, string value)
: this ()
{
Name = name;
Value = value;
}
public Cookie (string name, string value, string path)
: this (name, value)
{
Path = path;
}
public Cookie (string name, string value, string path, string domain)
: this (name, value, path)
{
Domain = domain;
}
public string Comment {
get { return comment; }
set { comment = value == null ? String.Empty : value; }
}
public Uri CommentUri {
get { return commentUri; }
set { commentUri = value; }
}
public bool Discard {
get { return discard; }
set { discard = value; }
}
public string Domain {
get { return domain; }
set {
if (String.IsNullOrEmpty (value)) {
domain = String.Empty;
ExactDomain = true;
} else {
domain = value;
ExactDomain = (value [0] != '.');
}
}
}
internal bool ExactDomain { get; set; }
public bool Expired {
get {
return expires <= DateTime.Now &&
expires != DateTime.MinValue;
}
set {
if (value)
expires = DateTime.Now;
}
}
public DateTime Expires {
get { return expires; }
set { expires = value; }
}
public bool HttpOnly {
get { return httpOnly; }
set { httpOnly = value; }
}
public string Name {
get { return name; }
set {
if (String.IsNullOrEmpty (value))
throw new CookieException ("Name cannot be empty");
if (value [0] == '$' || value.IndexOfAny (reservedCharsName) != -1) {
// see CookieTest, according to MS implementation
// the name value changes even though it's incorrect
name = String.Empty;
throw new CookieException ("Name contains invalid characters");
}
name = value;
}
}
public string Path {
get { return (path == null) ? String.Empty : path; }
set { path = (value == null) ? String.Empty : value; }
}
public string Port {
get { return port; }
set {
if (String.IsNullOrEmpty (value)) {
port = String.Empty;
return;
}
if (value [0] != '"' || value [value.Length - 1] != '"') {
throw new CookieException("The 'Port'='" + value + "' part of the cookie is invalid. Port must be enclosed by double quotes.");
}
port = value;
string [] values = port.Split (portSeparators);
ports = new int[values.Length];
for (int i = 0; i < ports.Length; i++) {
ports [i] = Int32.MinValue;
if (values [i].Length == 0)
continue;
try {
ports [i] = Int32.Parse (values [i]);
} catch (Exception e) {
throw new CookieException("The 'Port'='" + value + "' part of the cookie is invalid. Invalid value: " + values [i], e);
}
}
Version = 1;
}
}
internal int [] Ports {
get { return ports; }
}
public bool Secure {
get { return secure; }
set { secure = value; }
}
public DateTime TimeStamp {
get { return timestamp; }
}
public string Value {
get { return val; }
set {
if (value == null) {
val = String.Empty;
return;
}
// LAMESPEC: According to .Net specs the Value property should not accept
// the semicolon and comma characters, yet it does. For now we'll follow
// the behaviour of MS.Net instead of the specs.
/*
if (value.IndexOfAny(reservedCharsValue) != -1)
throw new CookieException("Invalid value. Value cannot contain semicolon or comma characters.");
*/
val = value;
}
}
public int Version {
get { return version; }
set {
if ((value < 0) || (value > 10))
version = 0;
else
version = value;
}
}
public override bool Equals (Object obj)
{
System.Net.Cookie c = obj as System.Net.Cookie;
return c != null &&
String.Compare (this.name, c.Name, true, CultureInfo.InvariantCulture) == 0 &&
String.Compare (this.val, c.Value, false, CultureInfo.InvariantCulture) == 0 &&
String.Compare (this.Path, c.Path, false, CultureInfo.InvariantCulture) == 0 &&
String.Compare (this.domain, c.Domain, true, CultureInfo.InvariantCulture) == 0 &&
this.version == c.Version;
}
public override int GetHashCode ()
{
return hash (
StringComparer.InvariantCultureIgnoreCase.GetHashCode (name),
val.GetHashCode (),
Path.GetHashCode (),
StringComparer.InvariantCultureIgnoreCase.GetHashCode (domain),
version);
}
private static int hash (int i, int j, int k, int l, int m)
{
return i ^ (j << 13 | j >> 19) ^ (k << 26 | k >> 6) ^ (l << 7 | l >> 25) ^ (m << 20 | m >> 12);
}
// returns a string that can be used to send a cookie to an Origin Server
// i.e., only used for clients
// see para 4.2.2 of RFC 2109 and para 3.3.4 of RFC 2965
// see also bug #316017
public override string ToString ()
{
return ToString (null);
}
internal string ToString (Uri uri)
{
if (name.Length == 0)
return String.Empty;
StringBuilder result = new StringBuilder (64);
if (version > 0)
result.Append ("$Version=").Append (version).Append ("; ");
result.Append (name).Append ("=").Append (val);
if (version == 0)
return result.ToString ();
if (!String.IsNullOrEmpty (path))
result.Append ("; $Path=").Append (path);
else if (uri != null)
result.Append ("; $Path=/").Append (path);
bool append_domain = (uri == null) || (uri.Host != domain);
if (append_domain && !String.IsNullOrEmpty (domain))
result.Append ("; $Domain=").Append (domain);
if (port != null && port.Length != 0)
result.Append ("; $Port=").Append (port);
return result.ToString ();
}
internal string ToClientString ()
{
if (name.Length == 0)
return String.Empty;
StringBuilder result = new StringBuilder (64);
if (version > 0)
result.Append ("Version=").Append (version).Append (";");
result.Append (name).Append ("=").Append (val);
if (path != null && path.Length != 0)
result.Append (";Path=").Append (QuotedString (path));
if (domain != null && domain.Length != 0)
result.Append (";Domain=").Append (QuotedString (domain));
if (port != null && port.Length != 0)
result.Append (";Port=").Append (port);
return result.ToString ();
}
// See par 3.6 of RFC 2616
string QuotedString (string value)
{
if (version == 0 || IsToken (value))
return value;
else
return "\"" + value.Replace("\"", "\\\"") + "\"";
}
bool IsToken (string value)
{
int len = value.Length;
for (int i = 0; i < len; i++) {
char c = value [i];
if (c < 0x20 || c >= 0x7f || tspecials.IndexOf (c) != -1)
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,176 @@
//
// CookieCollection.cs
// Copied from System.Net.CookieCollection
//
// Authors:
// Lawrence Pit (loz@cable.a2000.nl)
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
// Sebastien Pouliot <sebastien@ximian.com>
//
// Copyright (C) 2004,2009 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Runtime.Serialization;
namespace WebSocketSharp.Net {
[Serializable]
public class CookieCollection : ICollection, IEnumerable
{
// not 100% identical to MS implementation
sealed class CookieCollectionComparer : IComparer<Cookie>
{
public int Compare (Cookie x, Cookie y)
{
if (x == null || y == null)
return 0;
int c1 = x.Name.Length + x.Value.Length;
int c2 = y.Name.Length + y.Value.Length;
return (c1 - c2);
}
}
static CookieCollectionComparer Comparer = new CookieCollectionComparer ();
List<Cookie> list = new List<Cookie> ();
internal IList<Cookie> List {
get { return list; }
}
// ICollection
public int Count {
get { return list.Count; }
}
public bool IsSynchronized {
get { return false; }
}
public Object SyncRoot {
get { return this; }
}
public void CopyTo (Array array, int index)
{
(list as IList).CopyTo (array, index);
}
public void CopyTo (Cookie [] array, int index)
{
list.CopyTo (array, index);
}
// IEnumerable
public IEnumerator GetEnumerator ()
{
return list.GetEnumerator ();
}
// This
// LAMESPEC: So how is one supposed to create a writable CookieCollection
// instance?? We simply ignore this property, as this collection is always
// writable.
public bool IsReadOnly {
get { return true; }
}
public void Add (Cookie cookie)
{
if (cookie == null)
throw new ArgumentNullException ("cookie");
int pos = SearchCookie (cookie);
if (pos == -1)
list.Add (cookie);
else
list [pos] = cookie;
}
internal void Sort ()
{
if (list.Count > 0)
list.Sort (Comparer);
}
int SearchCookie (Cookie cookie)
{
string name = cookie.Name;
string domain = cookie.Domain;
string path = cookie.Path;
for (int i = list.Count - 1; i >= 0; i--) {
Cookie c = list [i];
if (c.Version != cookie.Version)
continue;
if (0 != String.Compare (domain, c.Domain, true, CultureInfo.InvariantCulture))
continue;
if (0 != String.Compare (name, c.Name, true, CultureInfo.InvariantCulture))
continue;
if (0 != String.Compare (path, c.Path, true, CultureInfo.InvariantCulture))
continue;
return i;
}
return -1;
}
public void Add (CookieCollection cookies)
{
if (cookies == null)
throw new ArgumentNullException ("cookies");
foreach (Cookie c in cookies)
Add (c);
}
public Cookie this [int index] {
get {
if (index < 0 || index >= list.Count)
throw new ArgumentOutOfRangeException ("index");
return list [index];
}
}
public Cookie this [string name] {
get {
foreach (Cookie c in list) {
if (0 == String.Compare (c.Name, name, true, CultureInfo.InvariantCulture))
return c;
}
return null;
}
}
}
}

View File

@@ -0,0 +1,69 @@
//
// CookieException.cs
// Copied from System.Net.CookieException
//
// Author:
// Lawrence Pit (loz@cable.a2000.nl)
//
// 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.
//
using System;
using System.Globalization;
using System.Runtime.Serialization;
namespace WebSocketSharp.Net {
[Serializable]
public class CookieException : FormatException, ISerializable
{
// Constructors
public CookieException ()
: base ()
{
}
internal CookieException (string msg)
: base (msg)
{
}
internal CookieException (string msg, Exception e)
: base (msg, e)
{
}
protected CookieException (SerializationInfo info, StreamingContext context)
: base (info, context)
{
}
// Methods
void ISerializable.GetObjectData (SerializationInfo info, StreamingContext context)
{
base.GetObjectData (info, context);
}
public override void GetObjectData (SerializationInfo serializationInfo, StreamingContext streamingContext)
{
base.GetObjectData (serializationInfo, streamingContext);
}
}
}

View File

@@ -0,0 +1,404 @@
//
// EndPointListener.cs
// Copied from System.Net.EndPointListener
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
// Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
namespace WebSocketSharp.Net {
sealed class EndPointListener {
#region Fields
List<ListenerPrefix> all; // host = '+'
X509Certificate2 cert;
IPEndPoint endpoint;
AsymmetricAlgorithm key;
Dictionary<ListenerPrefix, HttpListener> prefixes;
bool secure;
Socket sock;
List<ListenerPrefix> unhandled; // host = '*'
Hashtable unregistered;
#endregion
#region Constructor
public EndPointListener (IPAddress addr, int port, bool secure)
{
if (secure) {
this.secure = secure;
LoadCertificateAndKey (addr, port);
}
endpoint = new IPEndPoint (addr, port);
sock = new Socket (addr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
sock.Bind (endpoint);
sock.Listen (500);
var args = new SocketAsyncEventArgs ();
args.UserToken = this;
args.Completed += OnAccept;
sock.AcceptAsync (args);
prefixes = new Dictionary<ListenerPrefix, HttpListener> ();
unregistered = Hashtable.Synchronized (new Hashtable ());
}
#endregion
#region Private Static Methods
static void OnAccept (object sender, EventArgs e)
{
SocketAsyncEventArgs args = (SocketAsyncEventArgs) e;
EndPointListener epl = (EndPointListener) args.UserToken;
Socket accepted = null;
if (args.SocketError == SocketError.Success) {
accepted = args.AcceptSocket;
args.AcceptSocket = null;
}
try {
if (epl.sock != null)
epl.sock.AcceptAsync (args);
} catch {
if (accepted != null) {
try {
accepted.Close ();
} catch {}
accepted = null;
}
}
if (accepted == null)
return;
if (epl.secure && (epl.cert == null || epl.key == null)) {
accepted.Close ();
return;
}
HttpConnection conn = new HttpConnection (accepted, epl, epl.secure, epl.cert, epl.key);
epl.unregistered [conn] = conn;
conn.BeginReadRequest ();
}
#endregion
#region Private Methods
void AddSpecial (List<ListenerPrefix> coll, ListenerPrefix prefix)
{
if (coll == null)
return;
foreach (ListenerPrefix p in coll) {
if (p.Path == prefix.Path) // TODO: code
throw new HttpListenerException (400, "Prefix already in use.");
}
coll.Add (prefix);
}
void CheckIfRemove ()
{
if (prefixes.Count > 0)
return;
var list = unhandled;
if (list != null && list.Count > 0)
return;
list = all;
if (list != null && list.Count > 0)
return;
EndPointManager.RemoveEndPoint (this, endpoint);
}
RSACryptoServiceProvider CreateRSAFromFile (string filename)
{
if (filename == null)
throw new ArgumentNullException ("filename");
var rsa = new RSACryptoServiceProvider ();
byte[] pvk = null;
using (FileStream fs = File.Open (filename, FileMode.Open, FileAccess.Read, FileShare.Read))
{
pvk = new byte [fs.Length];
fs.Read (pvk, 0, pvk.Length);
}
rsa.ImportCspBlob (pvk);
return rsa;
}
void LoadCertificateAndKey (IPAddress addr, int port)
{
// Actually load the certificate
try {
string dirname = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData);
string path = Path.Combine (dirname, ".mono");
path = Path.Combine (path, "httplistener");
string cert_file = Path.Combine (path, String.Format ("{0}.cer", port));
string pvk_file = Path.Combine (path, String.Format ("{0}.pvk", port));
cert = new X509Certificate2 (cert_file);
key = CreateRSAFromFile (pvk_file);
} catch {
// ignore errors
}
}
HttpListener MatchFromList (
string host, string path, List<ListenerPrefix> list, out ListenerPrefix prefix)
{
prefix = null;
if (list == null)
return null;
HttpListener best_match = null;
int best_length = -1;
foreach (ListenerPrefix p in list) {
string ppath = p.Path;
if (ppath.Length < best_length)
continue;
if (path.StartsWith (ppath)) {
best_length = ppath.Length;
best_match = p.Listener;
prefix = p;
}
}
return best_match;
}
bool RemoveSpecial (List<ListenerPrefix> coll, ListenerPrefix prefix)
{
if (coll == null)
return false;
int c = coll.Count;
for (int i = 0; i < c; i++) {
ListenerPrefix p = coll [i];
if (p.Path == prefix.Path) {
coll.RemoveAt (i);
return true;
}
}
return false;
}
HttpListener SearchListener (Uri uri, out ListenerPrefix prefix)
{
prefix = null;
if (uri == null)
return null;
string host = uri.Host;
int port = uri.Port;
string path = HttpUtility.UrlDecode (uri.AbsolutePath);
string path_slash = path [path.Length - 1] == '/' ? path : path + "/";
HttpListener best_match = null;
int best_length = -1;
if (host != null && host != "") {
var p_ro = prefixes;
foreach (ListenerPrefix p in p_ro.Keys) {
string ppath = p.Path;
if (ppath.Length < best_length)
continue;
if (p.Host != host || p.Port != port)
continue;
if (path.StartsWith (ppath) || path_slash.StartsWith (ppath)) {
best_length = ppath.Length;
best_match = p_ro [p];
prefix = p;
}
}
if (best_length != -1)
return best_match;
}
var list = unhandled;
best_match = MatchFromList (host, path, list, out prefix);
if (path != path_slash && best_match == null)
best_match = MatchFromList (host, path_slash, list, out prefix);
if (best_match != null)
return best_match;
list = all;
best_match = MatchFromList (host, path, list, out prefix);
if (path != path_slash && best_match == null)
best_match = MatchFromList (host, path_slash, list, out prefix);
if (best_match != null)
return best_match;
return null;
}
#endregion
#region Internal Method
internal void RemoveConnection (HttpConnection conn)
{
unregistered.Remove (conn);
}
#endregion
#region Public Methods
public void AddPrefix (ListenerPrefix prefix, HttpListener listener)
{
List<ListenerPrefix> current;
List<ListenerPrefix> future;
if (prefix.Host == "*") {
do {
current = unhandled;
future = (current != null)
? new List<ListenerPrefix> (current)
: new List<ListenerPrefix> ();
prefix.Listener = listener;
AddSpecial (future, prefix);
} while (Interlocked.CompareExchange (ref unhandled, future, current) != current);
return;
}
if (prefix.Host == "+") {
do {
current = all;
future = (current != null)
? new List<ListenerPrefix> (current)
: new List<ListenerPrefix> ();
prefix.Listener = listener;
AddSpecial (future, prefix);
} while (Interlocked.CompareExchange (ref all, future, current) != current);
return;
}
Dictionary<ListenerPrefix, HttpListener> prefs, p2;
do {
prefs = prefixes;
if (prefs.ContainsKey (prefix)) {
HttpListener other = prefs [prefix];
if (other != listener) // TODO: code.
throw new HttpListenerException (400, "There's another listener for " + prefix);
return;
}
p2 = new Dictionary<ListenerPrefix, HttpListener> (prefs);
p2 [prefix] = listener;
} while (Interlocked.CompareExchange (ref prefixes, p2, prefs) != prefs);
}
public bool BindContext (HttpListenerContext context)
{
HttpListenerRequest req = context.Request;
ListenerPrefix prefix;
HttpListener listener = SearchListener (req.Url, out prefix);
if (listener == null)
return false;
context.Listener = listener;
context.Connection.Prefix = prefix;
return true;
}
public void Close ()
{
sock.Close ();
lock (unregistered.SyncRoot) {
foreach (HttpConnection c in unregistered.Keys)
c.Close (true);
unregistered.Clear ();
}
}
public void RemovePrefix (ListenerPrefix prefix, HttpListener listener)
{
List<ListenerPrefix> current;
List<ListenerPrefix> future;
if (prefix.Host == "*") {
do {
current = unhandled;
future = (current != null)
? new List<ListenerPrefix> (current)
: new List<ListenerPrefix> ();
if (!RemoveSpecial (future, prefix))
break; // Prefix not found
} while (Interlocked.CompareExchange (ref unhandled, future, current) != current);
CheckIfRemove ();
return;
}
if (prefix.Host == "+") {
do {
current = all;
future = (current != null)
? new List<ListenerPrefix> (current)
: new List<ListenerPrefix> ();
if (!RemoveSpecial (future, prefix))
break; // Prefix not found
} while (Interlocked.CompareExchange (ref all, future, current) != current);
CheckIfRemove ();
return;
}
Dictionary<ListenerPrefix, HttpListener> prefs, p2;
do {
prefs = prefixes;
if (!prefs.ContainsKey (prefix))
break;
p2 = new Dictionary<ListenerPrefix, HttpListener> (prefs);
p2.Remove (prefix);
} while (Interlocked.CompareExchange (ref prefixes, p2, prefs) != prefs);
CheckIfRemove ();
}
public void UnbindContext (HttpListenerContext context)
{
if (context == null || context.Request == null)
return;
context.Listener.UnregisterContext (context);
}
#endregion
}
}

View File

@@ -0,0 +1,148 @@
//
// EndPointManager.cs
// Copied from System.Net.EndPointManager
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
// Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
namespace WebSocketSharp.Net {
sealed class EndPointManager {
static Dictionary<IPAddress, Dictionary<int, EndPointListener>> ip_to_endpoints = new Dictionary<IPAddress, Dictionary<int, EndPointListener>> ();
private EndPointManager ()
{
}
static void AddPrefixInternal (string p, HttpListener listener)
{
ListenerPrefix lp = new ListenerPrefix (p);
if (lp.Path.IndexOf ('%') != -1)
throw new HttpListenerException (400, "Invalid path.");
if (lp.Path.IndexOf ("//", StringComparison.Ordinal) != -1) // TODO: Code?
throw new HttpListenerException (400, "Invalid path.");
// Always listens on all the interfaces, no matter the host name/ip used.
EndPointListener epl = GetEPListener (IPAddress.Any, lp.Port, listener, lp.Secure);
epl.AddPrefix (lp, listener);
}
static EndPointListener GetEPListener (IPAddress addr, int port, HttpListener listener, bool secure)
{
Dictionary<int, EndPointListener> p = null;
if (ip_to_endpoints.ContainsKey (addr)) {
p = ip_to_endpoints [addr];
} else {
p = new Dictionary<int, EndPointListener> ();
ip_to_endpoints [addr] = p;
}
EndPointListener epl = null;
if (p.ContainsKey (port)) {
epl = p [port];
} else {
epl = new EndPointListener (addr, port, secure);
p [port] = epl;
}
return epl;
}
static void RemovePrefixInternal (string prefix, HttpListener listener)
{
ListenerPrefix lp = new ListenerPrefix (prefix);
if (lp.Path.IndexOf ('%') != -1)
return;
if (lp.Path.IndexOf ("//", StringComparison.Ordinal) != -1)
return;
EndPointListener epl = GetEPListener (IPAddress.Any, lp.Port, listener, lp.Secure);
epl.RemovePrefix (lp, listener);
}
public static void AddListener (HttpListener listener)
{
List<string> added = new List<string> ();
try {
lock (((ICollection)ip_to_endpoints).SyncRoot) {
foreach (string prefix in listener.Prefixes) {
AddPrefixInternal (prefix, listener);
added.Add (prefix);
}
}
} catch {
foreach (string prefix in added) {
RemovePrefix (prefix, listener);
}
throw;
}
}
public static void AddPrefix (string prefix, HttpListener listener)
{
lock (((ICollection)ip_to_endpoints).SyncRoot) {
AddPrefixInternal (prefix, listener);
}
}
public static void RemoveEndPoint (EndPointListener epl, IPEndPoint ep)
{
lock (((ICollection)ip_to_endpoints).SyncRoot) {
Dictionary<int, EndPointListener> p = null;
p = ip_to_endpoints [ep.Address];
p.Remove (ep.Port);
if (p.Count == 0) {
ip_to_endpoints.Remove (ep.Address);
}
epl.Close ();
}
}
public static void RemoveListener (HttpListener listener)
{
lock (((ICollection)ip_to_endpoints).SyncRoot) {
foreach (string prefix in listener.Prefixes) {
RemovePrefixInternal (prefix, listener);
}
}
}
public static void RemovePrefix (string prefix, HttpListener listener)
{
lock (((ICollection)ip_to_endpoints).SyncRoot) {
RemovePrefixInternal (prefix, listener);
}
}
}
}

View File

@@ -0,0 +1,522 @@
//
// HttpConnection.cs
// Copied from System.Net.HttpConnection
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
// Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Reflection;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
namespace WebSocketSharp.Net {
sealed class HttpConnection {
#region Enums
enum InputState {
RequestLine,
Headers
}
enum LineState {
None,
CR,
LF
}
#endregion
#region Private Const Field
const int BufferSize = 8192;
#endregion
#region Private Static Field
static AsyncCallback onread_cb = new AsyncCallback (OnRead);
#endregion
#region Private Fields
byte [] buffer;
bool chunked;
HttpListenerContext context;
bool context_bound;
StringBuilder current_line;
EndPointListener epl;
InputState input_state;
RequestStream i_stream;
AsymmetricAlgorithm key;
HttpListener last_listener;
LineState line_state;
// IPEndPoint local_ep; // never used
MemoryStream ms;
ResponseStream o_stream;
int position;
ListenerPrefix prefix;
int reuses;
bool secure;
Socket sock;
Stream stream;
int s_timeout;
Timer timer;
#endregion
#region Constructor
public HttpConnection (
Socket sock,
EndPointListener epl,
bool secure,
X509Certificate2 cert,
AsymmetricAlgorithm key
)
{
this.sock = sock;
this.epl = epl;
this.secure = secure;
this.key = key;
// if (secure == false) {
// stream = new NetworkStream (sock, false);
// } else {
// var ssl_stream = new SslServerStream (new NetworkStream (sock, false), cert, false, false);
// ssl_stream.PrivateKeyCertSelectionDelegate += OnPVKSelection;
// stream = ssl_stream;
// }
var net_stream = new NetworkStream (sock, false);
if (!secure) {
stream = net_stream;
} else {
var ssl_stream = new SslStream(net_stream);
ssl_stream.AuthenticateAsServer(cert);
stream = ssl_stream;
}
timer = new Timer (OnTimeout, null, Timeout.Infinite, Timeout.Infinite);
Init ();
}
#endregion
#region Properties
public bool IsClosed {
get { return (sock == null); }
}
public bool IsSecure {
get { return secure; }
}
public IPEndPoint LocalEndPoint {
get { return (IPEndPoint) sock.LocalEndPoint; }
}
public ListenerPrefix Prefix {
get { return prefix; }
set { prefix = value; }
}
public IPEndPoint RemoteEndPoint {
get { return (IPEndPoint) sock.RemoteEndPoint; }
}
public int Reuses {
get { return reuses; }
}
public Stream Stream {
get { return stream; }
}
#endregion
#region Private Methods
void CloseSocket ()
{
if (sock == null)
return;
try {
sock.Close ();
} catch {
} finally {
sock = null;
}
RemoveConnection ();
}
void Init ()
{
context_bound = false;
i_stream = null;
o_stream = null;
prefix = null;
chunked = false;
ms = new MemoryStream ();
position = 0;
input_state = InputState.RequestLine;
line_state = LineState.None;
context = new HttpListenerContext (this);
s_timeout = 90000; // 90k ms for first request, 15k ms from then on
}
AsymmetricAlgorithm OnPVKSelection (X509Certificate certificate, string targetHost)
{
return key;
}
static void OnRead (IAsyncResult ares)
{
HttpConnection cnc = (HttpConnection) ares.AsyncState;
cnc.OnReadInternal (ares);
}
void OnReadInternal (IAsyncResult ares)
{
timer.Change (Timeout.Infinite, Timeout.Infinite);
int nread = -1;
try {
nread = stream.EndRead (ares);
ms.Write (buffer, 0, nread);
if (ms.Length > 32768) {
SendError ("Bad request", 400);
Close (true);
return;
}
} catch {
if (ms != null && ms.Length > 0)
SendError ();
if (sock != null) {
CloseSocket ();
Unbind ();
}
return;
}
if (nread == 0) {
//if (ms.Length > 0)
// SendError (); // Why bother?
CloseSocket ();
Unbind ();
return;
}
if (ProcessInput (ms)) {
if (!context.HaveError)
context.Request.FinishInitialization ();
if (context.HaveError) {
SendError ();
Close (true);
return;
}
if (!epl.BindContext (context)) {
SendError ("Invalid host", 400);
Close (true);
return;
}
HttpListener listener = context.Listener;
if (last_listener != listener) {
RemoveConnection ();
listener.AddConnection (this);
last_listener = listener;
}
context_bound = true;
listener.RegisterContext (context);
return;
}
stream.BeginRead (buffer, 0, BufferSize, onread_cb, this);
}
void OnTimeout (object unused)
{
CloseSocket ();
Unbind ();
}
// true -> done processing
// false -> need more input
bool ProcessInput (MemoryStream ms)
{
byte [] buffer = ms.GetBuffer ();
int len = (int) ms.Length;
int used = 0;
string line;
try {
line = ReadLine (buffer, position, len - position, ref used);
position += used;
} catch {
context.ErrorMessage = "Bad request";
context.ErrorStatus = 400;
return true;
}
do {
if (line == null)
break;
if (line == "") {
if (input_state == InputState.RequestLine)
continue;
current_line = null;
ms = null;
return true;
}
if (input_state == InputState.RequestLine) {
context.Request.SetRequestLine (line);
input_state = InputState.Headers;
} else {
try {
context.Request.AddHeader (line);
} catch (Exception e) {
context.ErrorMessage = e.Message;
context.ErrorStatus = 400;
return true;
}
}
if (context.HaveError)
return true;
if (position >= len)
break;
try {
line = ReadLine (buffer, position, len - position, ref used);
position += used;
} catch {
context.ErrorMessage = "Bad request";
context.ErrorStatus = 400;
return true;
}
} while (line != null);
if (used == len) {
ms.SetLength (0);
position = 0;
}
return false;
}
string ReadLine (byte [] buffer, int offset, int len, ref int used)
{
if (current_line == null)
current_line = new StringBuilder ();
int last = offset + len;
used = 0;
for (int i = offset; i < last && line_state != LineState.LF; i++) {
used++;
byte b = buffer [i];
if (b == 13) {
line_state = LineState.CR;
} else if (b == 10) {
line_state = LineState.LF;
} else {
current_line.Append ((char) b);
}
}
string result = null;
if (line_state == LineState.LF) {
line_state = LineState.None;
result = current_line.ToString ();
current_line.Length = 0;
}
return result;
}
void RemoveConnection ()
{
if (last_listener == null)
epl.RemoveConnection (this);
else
last_listener.RemoveConnection (this);
}
void Unbind ()
{
if (context_bound) {
epl.UnbindContext (context);
context_bound = false;
}
}
#endregion
#region Internal Method
internal void Close (bool force_close)
{
if (sock != null) {
Stream st = GetResponseStream ();
st.Close ();
o_stream = null;
}
if (sock != null) {
force_close |= !context.Request.KeepAlive;
if (!force_close)
force_close = (context.Response.Headers ["connection"] == "close");
/*
if (!force_close) {
// bool conn_close = (status_code == 400 || status_code == 408 || status_code == 411 ||
// status_code == 413 || status_code == 414 || status_code == 500 ||
// status_code == 503);
force_close |= (context.Request.ProtocolVersion <= HttpVersion.Version10);
}
*/
if (!force_close && context.Request.FlushInput ()) {
if (chunked && context.Response.ForceCloseChunked == false) {
// Don't close. Keep working.
reuses++;
Unbind ();
Init ();
BeginReadRequest ();
return;
}
reuses++;
Unbind ();
Init ();
BeginReadRequest ();
return;
}
Socket s = sock;
sock = null;
try {
if (s != null)
s.Shutdown (SocketShutdown.Both);
} catch {
} finally {
if (s != null)
s.Close ();
}
Unbind ();
RemoveConnection ();
return;
}
}
#endregion
#region Public Methods
public void BeginReadRequest ()
{
if (buffer == null)
buffer = new byte [BufferSize];
try {
if (reuses == 1)
s_timeout = 15000;
timer.Change (s_timeout, Timeout.Infinite);
stream.BeginRead (buffer, 0, BufferSize, onread_cb, this);
} catch {
timer.Change (Timeout.Infinite, Timeout.Infinite);
CloseSocket ();
Unbind ();
}
}
public void Close ()
{
Close (false);
}
public RequestStream GetRequestStream (bool chunked, long contentlength)
{
if (i_stream == null) {
byte [] buffer = ms.GetBuffer ();
int length = (int) ms.Length;
ms = null;
if (chunked) {
this.chunked = true;
context.Response.SendChunked = true;
i_stream = new ChunkedInputStream (context, stream, buffer, position, length - position);
} else {
i_stream = new RequestStream (stream, buffer, position, length - position, contentlength);
}
}
return i_stream;
}
public ResponseStream GetResponseStream ()
{
// TODO: can we get this stream before reading the input?
if (o_stream == null) {
HttpListener listener = context.Listener;
bool ign = (listener == null) ? true : listener.IgnoreWriteExceptions;
o_stream = new ResponseStream (stream, context.Response, ign);
}
return o_stream;
}
public void SendError ()
{
SendError (context.ErrorMessage, context.ErrorStatus);
}
public void SendError (string msg, int status)
{
try {
HttpListenerResponse response = context.Response;
response.StatusCode = status;
response.ContentType = "text/html";
string description = HttpListenerResponse.GetStatusDescription (status);
string str;
if (msg != null)
str = String.Format ("<h1>{0} ({1})</h1>", description, msg);
else
str = String.Format ("<h1>{0}</h1>", description);
byte [] error = context.Response.ContentEncoding.GetBytes (str);
response.Close (error, false);
} catch {
// response was already closed
}
}
#endregion
}
}

View File

@@ -0,0 +1,372 @@
//
// HttpListener.cs
// Copied from System.Net.HttpListener
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
// Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Threading;
// TODO: logging
namespace WebSocketSharp.Net {
public sealed class HttpListener : IDisposable {
#region Fields
AuthenticationSchemes auth_schemes;
AuthenticationSchemeSelector auth_selector;
Dictionary<HttpConnection, HttpConnection> connections;
List<HttpListenerContext> ctx_queue;
bool disposed;
bool ignore_write_exceptions;
bool listening;
HttpListenerPrefixCollection prefixes;
string realm;
Dictionary<HttpListenerContext, HttpListenerContext> registry;
bool unsafe_ntlm_auth;
List<ListenerAsyncResult> wait_queue;
#endregion
#region Constructor
public HttpListener ()
{
prefixes = new HttpListenerPrefixCollection (this);
registry = new Dictionary<HttpListenerContext, HttpListenerContext> ();
connections = new Dictionary<HttpConnection, HttpConnection> ();
ctx_queue = new List<HttpListenerContext> ();
wait_queue = new List<ListenerAsyncResult> ();
auth_schemes = AuthenticationSchemes.Anonymous;
}
#endregion
#region Properties
// TODO: Digest, NTLM and Negotiate require ControlPrincipal
public AuthenticationSchemes AuthenticationSchemes {
get { return auth_schemes; }
set {
CheckDisposed ();
auth_schemes = value;
}
}
public AuthenticationSchemeSelector AuthenticationSchemeSelectorDelegate {
get { return auth_selector; }
set {
CheckDisposed ();
auth_selector = value;
}
}
public bool IgnoreWriteExceptions {
get { return ignore_write_exceptions; }
set {
CheckDisposed ();
ignore_write_exceptions = value;
}
}
public bool IsListening {
get { return listening; }
}
public static bool IsSupported {
get { return true; }
}
public HttpListenerPrefixCollection Prefixes {
get {
CheckDisposed ();
return prefixes;
}
}
// TODO: Use this
public string Realm {
get { return realm; }
set {
CheckDisposed ();
realm = value;
}
}
// TODO: Support for NTLM needs some loving.
public bool UnsafeConnectionNtlmAuthentication {
get { return unsafe_ntlm_auth; }
set {
CheckDisposed ();
unsafe_ntlm_auth = value;
}
}
#endregion
#region Private Methods
void Cleanup (bool close_existing)
{
lock (((ICollection)registry).SyncRoot) {
if (close_existing) {
// Need to copy this since closing will call UnregisterContext
ICollection keys = registry.Keys;
var all = new HttpListenerContext [keys.Count];
keys.CopyTo (all, 0);
registry.Clear ();
for (int i = all.Length - 1; i >= 0; i--)
all [i].Connection.Close (true);
}
lock (((ICollection)connections).SyncRoot) {
ICollection keys = connections.Keys;
var conns = new HttpConnection [keys.Count];
keys.CopyTo (conns, 0);
connections.Clear ();
for (int i = conns.Length - 1; i >= 0; i--)
conns [i].Close (true);
}
lock (((ICollection)ctx_queue).SyncRoot) {
var ctxs = ctx_queue.ToArray ();
ctx_queue.Clear ();
for (int i = ctxs.Length - 1; i >= 0; i--)
ctxs [i].Connection.Close (true);
}
lock (((ICollection)wait_queue).SyncRoot) {
Exception exc = new ObjectDisposedException ("listener");
foreach (ListenerAsyncResult ares in wait_queue) {
ares.Complete (exc);
}
wait_queue.Clear ();
}
}
}
void Close (bool force)
{
CheckDisposed ();
EndPointManager.RemoveListener (this);
Cleanup (force);
}
// Must be called with a lock on ctx_queue
HttpListenerContext GetContextFromQueue ()
{
if (ctx_queue.Count == 0)
return null;
var context = ctx_queue [0];
ctx_queue.RemoveAt (0);
return context;
}
void IDisposable.Dispose ()
{
if (disposed)
return;
Close (true); //TODO: Should we force here or not?
disposed = true;
}
#endregion
#region Internal Methods
internal void AddConnection (HttpConnection cnc)
{
connections [cnc] = cnc;
}
internal void CheckDisposed ()
{
if (disposed)
throw new ObjectDisposedException (GetType ().ToString ());
}
internal void RegisterContext (HttpListenerContext context)
{
lock (((ICollection)registry).SyncRoot)
registry [context] = context;
ListenerAsyncResult ares = null;
lock (((ICollection)wait_queue).SyncRoot) {
if (wait_queue.Count == 0) {
lock (((ICollection)ctx_queue).SyncRoot)
ctx_queue.Add (context);
} else {
ares = wait_queue [0];
wait_queue.RemoveAt (0);
}
}
if (ares != null)
ares.Complete (context);
}
internal void RemoveConnection (HttpConnection cnc)
{
connections.Remove (cnc);
}
internal AuthenticationSchemes SelectAuthenticationScheme (HttpListenerContext context)
{
if (AuthenticationSchemeSelectorDelegate != null)
return AuthenticationSchemeSelectorDelegate (context.Request);
else
return auth_schemes;
}
internal void UnregisterContext (HttpListenerContext context)
{
lock (((ICollection)registry).SyncRoot)
registry.Remove (context);
lock (((ICollection)ctx_queue).SyncRoot) {
int idx = ctx_queue.IndexOf (context);
if (idx >= 0)
ctx_queue.RemoveAt (idx);
}
}
#endregion
#region Public Methods
public void Abort ()
{
if (disposed)
return;
if (!listening) {
return;
}
Close (true);
}
public IAsyncResult BeginGetContext (AsyncCallback callback, Object state)
{
CheckDisposed ();
if (!listening)
throw new InvalidOperationException ("Please, call Start before using this method.");
ListenerAsyncResult ares = new ListenerAsyncResult (callback, state);
// lock wait_queue early to avoid race conditions
lock (((ICollection)wait_queue).SyncRoot) {
lock (((ICollection)ctx_queue).SyncRoot) {
HttpListenerContext ctx = GetContextFromQueue ();
if (ctx != null) {
ares.Complete (ctx, true);
return ares;
}
}
wait_queue.Add (ares);
}
return ares;
}
public void Close ()
{
if (disposed)
return;
if (!listening) {
disposed = true;
return;
}
Close (true);
disposed = true;
}
public HttpListenerContext EndGetContext (IAsyncResult asyncResult)
{
CheckDisposed ();
if (asyncResult == null)
throw new ArgumentNullException ("asyncResult");
ListenerAsyncResult ares = asyncResult as ListenerAsyncResult;
if (ares == null)
throw new ArgumentException ("Wrong IAsyncResult.", "asyncResult");
if (ares.EndCalled)
throw new ArgumentException ("Cannot reuse this IAsyncResult");
ares.EndCalled = true;
if (!ares.IsCompleted)
ares.AsyncWaitHandle.WaitOne ();
lock (((ICollection)wait_queue).SyncRoot) {
int idx = wait_queue.IndexOf (ares);
if (idx >= 0)
wait_queue.RemoveAt (idx);
}
HttpListenerContext context = ares.GetContext ();
context.ParseAuthentication (SelectAuthenticationScheme (context));
return context; // This will throw on error.
}
public HttpListenerContext GetContext ()
{
// The prefixes are not checked when using the async interface!?
if (prefixes.Count == 0)
throw new InvalidOperationException ("Please, call AddPrefix before using this method.");
ListenerAsyncResult ares = (ListenerAsyncResult) BeginGetContext (null, null);
ares.InGet = true;
return EndGetContext (ares);
}
public void Start ()
{
CheckDisposed ();
if (listening)
return;
EndPointManager.AddListener (this);
listening = true;
}
public void Stop ()
{
CheckDisposed ();
listening = false;
Close (false);
}
#endregion
}
}

View File

@@ -0,0 +1,179 @@
//
// HttpListenerContext.cs
// Copied from System.Net.HttpListenerContext
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
// Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Specialized;
using System.IO;
using System.Net;
using System.Security.Principal;
using System.Text;
namespace WebSocketSharp.Net {
public sealed class HttpListenerContext {
#region Private Fields
HttpConnection cnc;
string error;
int err_status;
HttpListenerRequest request;
HttpListenerResponse response;
IPrincipal user;
#endregion
#region Internal Fields
internal HttpListener Listener;
#endregion
#region Constructor
internal HttpListenerContext (HttpConnection cnc)
{
this.cnc = cnc;
err_status = 400;
request = new HttpListenerRequest (this);
response = new HttpListenerResponse (this);
}
#endregion
#region Internal Properties
internal HttpConnection Connection {
get { return cnc; }
}
internal string ErrorMessage {
get { return error; }
set { error = value; }
}
internal int ErrorStatus {
get { return err_status; }
set { err_status = value; }
}
internal bool HaveError {
get { return (error != null); }
}
#endregion
#region Public Properties
public HttpListenerRequest Request {
get { return request; }
}
public HttpListenerResponse Response {
get { return response; }
}
public IPrincipal User {
get { return user; }
}
#endregion
#region Internal Methods
internal void ParseAuthentication (AuthenticationSchemes expectedSchemes)
{
if (expectedSchemes == AuthenticationSchemes.Anonymous)
return;
// TODO: Handle NTLM/Digest modes
string header = request.Headers ["Authorization"];
if (header == null || header.Length < 2)
return;
string [] authenticationData = header.Split (new char [] {' '}, 2);
if (string.Compare (authenticationData [0], "basic", true) == 0) {
user = ParseBasicAuthentication (authenticationData [1]);
}
// TODO: throw if malformed -> 400 bad request
}
internal IPrincipal ParseBasicAuthentication (string authData)
{
try {
// Basic AUTH Data is a formatted Base64 String
//string domain = null;
string user = null;
string password = null;
int pos = -1;
string authString = Encoding.Default.GetString (Convert.FromBase64String (authData));
// The format is DOMAIN\username:password
// Domain is optional
pos = authString.IndexOf (':');
// parse the password off the end
password = authString.Substring (pos+1);
// discard the password
authString = authString.Substring (0, pos);
// check if there is a domain
pos = authString.IndexOf ('\\');
if (pos > 0) {
//domain = authString.Substring (0, pos);
user = authString.Substring (pos);
} else {
user = authString;
}
HttpListenerBasicIdentity identity = new HttpListenerBasicIdentity (user, password);
// TODO: What are the roles MS sets
return new GenericPrincipal (identity, new string [0]);
} catch (Exception) {
// Invalid auth data is swallowed silently
return null;
}
}
#endregion
#region Public Method
public HttpListenerWebSocketContext AcceptWebSocket ()
{
return new HttpListenerWebSocketContext (this);
}
#endregion
}
}

View File

@@ -0,0 +1,59 @@
//
// HttpListenerException.cs
// Copied from System.Net.HttpListenerException
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.ComponentModel;
using System.Runtime.Serialization;
namespace WebSocketSharp.Net {
[Serializable]
public class HttpListenerException : Win32Exception
{
public HttpListenerException ()
{
}
public HttpListenerException (int errorCode) : base (errorCode)
{
}
public HttpListenerException (int errorCode, string message) : base (errorCode, message)
{
}
protected HttpListenerException (SerializationInfo serializationInfo, StreamingContext streamingContext) : base (serializationInfo, streamingContext)
{
}
public override int ErrorCode {
get { return base.ErrorCode; }
}
}
}

View File

@@ -0,0 +1,127 @@
//
// HttpListenerPrefixCollection.cs
// Copied from System.Net.HttpListenerPrefixCollection
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
// Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
namespace WebSocketSharp.Net {
public class HttpListenerPrefixCollection : ICollection<string>, IEnumerable<string>, IEnumerable
{
HttpListener listener;
List<string> prefixes;
private HttpListenerPrefixCollection ()
{
prefixes = new List<string> ();
}
internal HttpListenerPrefixCollection (HttpListener listener)
: this ()
{
this.listener = listener;
}
public int Count {
get { return prefixes.Count; }
}
public bool IsReadOnly {
get { return false; }
}
public bool IsSynchronized {
get { return false; }
}
public void Add (string uriPrefix)
{
listener.CheckDisposed ();
ListenerPrefix.CheckUri (uriPrefix);
if (prefixes.Contains (uriPrefix))
return;
prefixes.Add (uriPrefix);
if (listener.IsListening)
EndPointManager.AddPrefix (uriPrefix, listener);
}
public void Clear ()
{
listener.CheckDisposed ();
prefixes.Clear ();
if (listener.IsListening)
EndPointManager.RemoveListener (listener);
}
public bool Contains (string uriPrefix)
{
listener.CheckDisposed ();
return prefixes.Contains (uriPrefix);
}
public void CopyTo (string [] array, int offset)
{
listener.CheckDisposed ();
prefixes.CopyTo (array, offset);
}
public void CopyTo (Array array, int offset)
{
listener.CheckDisposed ();
((ICollection) prefixes).CopyTo (array, offset);
}
public IEnumerator<string> GetEnumerator ()
{
return prefixes.GetEnumerator ();
}
IEnumerator IEnumerable.GetEnumerator ()
{
return prefixes.GetEnumerator ();
}
public bool Remove (string uriPrefix)
{
listener.CheckDisposed ();
if (uriPrefix == null)
throw new ArgumentNullException ("uriPrefix");
bool result = prefixes.Remove (uriPrefix);
if (result && listener.IsListening)
EndPointManager.RemovePrefix (uriPrefix, listener);
return result;
}
}
}

View File

@@ -0,0 +1,546 @@
//
// HttpListenerRequest.cs
// Copied from System.Net.HttpListenerRequest
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
// Copyright (c) 2012 sta.blockhead (sta.blockhead@gmail.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Net;
using System.Security.Cryptography.X509Certificates;
using System.Text;
namespace WebSocketSharp.Net {
public sealed class HttpListenerRequest {
#region Private Static Fields
static char [] separators = new char [] { ' ' };
static byte [] _100continue = Encoding.ASCII.GetBytes ("HTTP/1.1 100 Continue\r\n\r\n");
#endregion
#region Private Fields
string [] accept_types;
// int client_cert_error;
bool cl_set;
Encoding content_encoding;
long content_length;
HttpListenerContext context;
CookieCollection cookies;
WebHeaderCollection headers;
Stream input_stream;
bool is_chunked;
bool ka_set;
bool keep_alive;
string method;
// bool no_get_certificate;
Version version;
NameValueCollection query_string; // check if null is ok, check if read-only, check case-sensitiveness
string raw_url;
Uri referrer;
Uri url;
string [] user_languages;
#endregion
#region Constructor
internal HttpListenerRequest (HttpListenerContext context)
{
this.context = context;
headers = new WebHeaderCollection ();
version = HttpVersion.Version10;
}
#endregion
#region Properties
public string [] AcceptTypes {
get { return accept_types; }
}
// TODO: Always returns 0
public int ClientCertificateError {
get {
/*
if (no_get_certificate)
throw new InvalidOperationException (
"Call GetClientCertificate() before calling this method.");
return client_cert_error;
*/
return 0;
}
}
public Encoding ContentEncoding {
get {
if (content_encoding == null)
content_encoding = Encoding.Default;
return content_encoding;
}
}
public long ContentLength64 {
get { return content_length; }
}
public string ContentType {
get { return headers ["content-type"]; }
}
public CookieCollection Cookies {
get {
// TODO: check if the collection is read-only
if (cookies == null)
cookies = new CookieCollection ();
return cookies;
}
}
public bool HasEntityBody {
get { return (content_length > 0 || is_chunked); }
}
public NameValueCollection Headers {
get { return headers; }
}
public string HttpMethod {
get { return method; }
}
public Stream InputStream {
get {
if (input_stream == null) {
if (is_chunked || content_length > 0)
input_stream = context.Connection.GetRequestStream (is_chunked, content_length);
else
input_stream = Stream.Null;
}
return input_stream;
}
}
// TODO: Always returns false
public bool IsAuthenticated {
get { return false; }
}
public bool IsLocal {
get { return IPAddress.IsLoopback (RemoteEndPoint.Address); }
}
public bool IsSecureConnection {
get { return context.Connection.IsSecure; }
}
public bool IsWebSocketRequest {
get {
if (method != "GET")
return false;
if (version != HttpVersion.Version11)
return false;
if (!headers.Exists("Upgrade", "websocket"))
return false;
if (!headers.Exists("Connection", "Upgrade"))
return false;
if (!headers.Exists("Host"))
return false;
if (!headers.Exists("Sec-WebSocket-Key"))
return false;
if (!headers.Exists("Sec-WebSocket-Version"))
return false;
return true;
}
}
public bool KeepAlive {
get {
if (ka_set)
return keep_alive;
ka_set = true;
// 1. Connection header
// 2. Protocol (1.1 == keep-alive by default)
// 3. Keep-Alive header
string cnc = headers ["Connection"];
if (!String.IsNullOrEmpty (cnc)) {
keep_alive = (0 == String.Compare (cnc, "keep-alive", StringComparison.OrdinalIgnoreCase));
} else if (version == HttpVersion.Version11) {
keep_alive = true;
} else {
cnc = headers ["keep-alive"];
if (!String.IsNullOrEmpty (cnc))
keep_alive = (0 != String.Compare (cnc, "closed", StringComparison.OrdinalIgnoreCase));
}
return keep_alive;
}
}
public IPEndPoint LocalEndPoint {
get { return context.Connection.LocalEndPoint; }
}
public Version ProtocolVersion {
get { return version; }
}
public NameValueCollection QueryString {
get { return query_string; }
}
public string RawUrl {
get { return raw_url; }
}
public IPEndPoint RemoteEndPoint {
get { return context.Connection.RemoteEndPoint; }
}
// TODO: Always returns Guid.Empty
public Guid RequestTraceIdentifier {
get { return Guid.Empty; }
}
public Uri Url {
get { return url; }
}
public Uri UrlReferrer {
get { return referrer; }
}
public string UserAgent {
get { return headers ["user-agent"]; }
}
public string UserHostAddress {
get { return LocalEndPoint.ToString (); }
}
public string UserHostName {
get { return headers ["host"]; }
}
public string [] UserLanguages {
get { return user_languages; }
}
#endregion
#region Private Methods
void CreateQueryString (string query)
{
if (query == null || query.Length == 0) {
query_string = new NameValueCollection (1);
return;
}
query_string = new NameValueCollection ();
if (query [0] == '?')
query = query.Substring (1);
string [] components = query.Split ('&');
foreach (string kv in components) {
int pos = kv.IndexOf ('=');
if (pos == -1) {
query_string.Add (null, HttpUtility.UrlDecode (kv));
} else {
string key = HttpUtility.UrlDecode (kv.Substring (0, pos));
string val = HttpUtility.UrlDecode (kv.Substring (pos + 1));
query_string.Add (key, val);
}
}
}
#endregion
#region Internal Methods
internal void AddHeader (string header)
{
int colon = header.IndexOf (':');
if (colon == -1 || colon == 0) {
context.ErrorMessage = "Bad Request";
context.ErrorStatus = 400;
return;
}
string name = header.Substring (0, colon).Trim ();
string val = header.Substring (colon + 1).Trim ();
string lower = name.ToLower (CultureInfo.InvariantCulture);
headers.SetInternal (name, val);
switch (lower) {
case "accept-language":
user_languages = val.Split (','); // yes, only split with a ','
break;
case "accept":
accept_types = val.Split (','); // yes, only split with a ','
break;
case "content-length":
try {
//TODO: max. content_length?
content_length = Int64.Parse (val.Trim ());
if (content_length < 0)
context.ErrorMessage = "Invalid Content-Length.";
cl_set = true;
} catch {
context.ErrorMessage = "Invalid Content-Length.";
}
break;
case "referer":
try {
referrer = new Uri (val);
} catch {
referrer = new Uri ("http://someone.is.screwing.with.the.headers.com/");
}
break;
case "cookie":
if (cookies == null)
cookies = new CookieCollection();
string[] cookieStrings = val.Split(new char[] {',', ';'});
Cookie current = null;
int version = 0;
foreach (string cookieString in cookieStrings) {
string str = cookieString.Trim ();
if (str.Length == 0)
continue;
if (str.StartsWith ("$Version")) {
version = Int32.Parse (Unquote (str.Substring (str.IndexOf ('=') + 1)));
} else if (str.StartsWith ("$Path")) {
if (current != null)
current.Path = str.Substring (str.IndexOf ('=') + 1).Trim ();
} else if (str.StartsWith ("$Domain")) {
if (current != null)
current.Domain = str.Substring (str.IndexOf ('=') + 1).Trim ();
} else if (str.StartsWith ("$Port")) {
if (current != null)
current.Port = str.Substring (str.IndexOf ('=') + 1).Trim ();
} else {
if (current != null) {
cookies.Add (current);
}
current = new Cookie ();
int idx = str.IndexOf ('=');
if (idx > 0) {
current.Name = str.Substring (0, idx).Trim ();
current.Value = str.Substring (idx + 1).Trim ();
} else {
current.Name = str.Trim ();
current.Value = String.Empty;
}
current.Version = version;
}
}
if (current != null) {
cookies.Add (current);
}
break;
}
}
internal void FinishInitialization ()
{
string host = UserHostName;
if (version > HttpVersion.Version10 && (host == null || host.Length == 0)) {
context.ErrorMessage = "Invalid host name";
return;
}
string path;
Uri raw_uri = null;
if (raw_url.MaybeUri () && Uri.TryCreate (raw_url, UriKind.Absolute, out raw_uri))
path = raw_uri.PathAndQuery;
else
path = HttpUtility.UrlDecode (raw_url);
if ((host == null || host.Length == 0))
host = UserHostAddress;
if (raw_uri != null)
host = raw_uri.Host;
int colon = host.IndexOf (':');
if (colon >= 0)
host = host.Substring (0, colon);
string base_uri = String.Format ("{0}://{1}:{2}",
(IsSecureConnection) ? "https" : "http",
host,
LocalEndPoint.Port);
if (!Uri.TryCreate (base_uri + path, UriKind.Absolute, out url)){
context.ErrorMessage = "Invalid url: " + base_uri + path;
return;
}
CreateQueryString (url.Query);
if (version >= HttpVersion.Version11) {
string t_encoding = Headers ["Transfer-Encoding"];
is_chunked = (t_encoding != null && String.Compare (t_encoding, "chunked", StringComparison.OrdinalIgnoreCase) == 0);
// 'identity' is not valid!
if (t_encoding != null && !is_chunked) {
context.Connection.SendError (null, 501);
return;
}
}
if (!is_chunked && !cl_set) {
if (String.Compare (method, "POST", StringComparison.OrdinalIgnoreCase) == 0 ||
String.Compare (method, "PUT", StringComparison.OrdinalIgnoreCase) == 0) {
context.Connection.SendError (null, 411);
return;
}
}
if (String.Compare (Headers ["Expect"], "100-continue", StringComparison.OrdinalIgnoreCase) == 0) {
ResponseStream output = context.Connection.GetResponseStream ();
output.InternalWrite (_100continue, 0, _100continue.Length);
}
}
// returns true is the stream could be reused.
internal bool FlushInput ()
{
if (!HasEntityBody)
return true;
int length = 2048;
if (content_length > 0)
length = (int) Math.Min (content_length, (long) length);
byte [] bytes = new byte [length];
while (true) {
// TODO: test if MS has a timeout when doing this
try {
IAsyncResult ares = InputStream.BeginRead (bytes, 0, length, null, null);
if (!ares.IsCompleted && !ares.AsyncWaitHandle.WaitOne (100))
return false;
if (InputStream.EndRead (ares) <= 0)
return true;
} catch {
return false;
}
}
}
internal void SetRequestLine (string req)
{
string [] parts = req.Split (separators, 3);
if (parts.Length != 3) {
context.ErrorMessage = "Invalid request line (parts).";
return;
}
method = parts [0];
foreach (char c in method){
int ic = (int) c;
if ((ic >= 'A' && ic <= 'Z') ||
(ic > 32 && c < 127 && c != '(' && c != ')' && c != '<' &&
c != '<' && c != '>' && c != '@' && c != ',' && c != ';' &&
c != ':' && c != '\\' && c != '"' && c != '/' && c != '[' &&
c != ']' && c != '?' && c != '=' && c != '{' && c != '}'))
continue;
context.ErrorMessage = "(Invalid verb)";
return;
}
raw_url = parts [1];
if (parts [2].Length != 8 || !parts [2].StartsWith ("HTTP/")) {
context.ErrorMessage = "Invalid request line (version).";
return;
}
try {
version = new Version (parts [2].Substring (5));
if (version.Major < 1)
throw new Exception ();
} catch {
context.ErrorMessage = "Invalid request line (version).";
return;
}
}
internal static string Unquote (String str) {
int start = str.IndexOf ('\"');
int end = str.LastIndexOf ('\"');
if (start >= 0 && end >=0)
str = str.Substring (start + 1, end - 1);
return str.Trim ();
}
#endregion
#region Public Methods
// TODO: Always returns null
public IAsyncResult BeginGetClientCertificate (AsyncCallback requestCallback, Object state)
{
return null;
}
// TODO: Always returns null
public X509Certificate2 EndGetClientCertificate (IAsyncResult asyncResult)
{
// set no_client_certificate once done.
return null;
}
// TODO: Always returns null
public X509Certificate2 GetClientCertificate ()
{
// set no_client_certificate once done.
// InvalidOp if call in progress.
return null;
}
#endregion
}
}

View File

@@ -0,0 +1,510 @@
//
// HttpListenerResponse.cs
// Copied from System.Net.HttpListenerResponse
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
namespace WebSocketSharp.Net {
public sealed class HttpListenerResponse : IDisposable
{
bool disposed;
Encoding content_encoding;
long content_length;
bool cl_set;
string content_type;
CookieCollection cookies;
WebHeaderCollection headers = new WebHeaderCollection ();
bool keep_alive = true;
ResponseStream output_stream;
Version version = HttpVersion.Version11;
string location;
int status_code = 200;
string status_description = "OK";
bool chunked;
HttpListenerContext context;
internal bool HeadersSent;
bool force_close_chunked;
internal HttpListenerResponse (HttpListenerContext context)
{
this.context = context;
}
internal bool ForceCloseChunked {
get { return force_close_chunked; }
}
public Encoding ContentEncoding {
get {
if (content_encoding == null)
content_encoding = Encoding.Default;
return content_encoding;
}
set {
if (disposed)
throw new ObjectDisposedException (GetType ().ToString ());
//TODO: is null ok?
if (HeadersSent)
throw new InvalidOperationException ("Cannot be changed after headers are sent.");
content_encoding = value;
}
}
public long ContentLength64 {
get { return content_length; }
set {
if (disposed)
throw new ObjectDisposedException (GetType ().ToString ());
if (HeadersSent)
throw new InvalidOperationException ("Cannot be changed after headers are sent.");
if (value < 0)
throw new ArgumentOutOfRangeException ("Must be >= 0", "value");
cl_set = true;
content_length = value;
}
}
public string ContentType {
get { return content_type; }
set {
// TODO: is null ok?
if (disposed)
throw new ObjectDisposedException (GetType ().ToString ());
if (HeadersSent)
throw new InvalidOperationException ("Cannot be changed after headers are sent.");
content_type = value;
}
}
// RFC 2109, 2965 + the netscape specification at http://wp.netscape.com/newsref/std/cookie_spec.html
public CookieCollection Cookies {
get {
if (cookies == null)
cookies = new CookieCollection ();
return cookies;
}
set { cookies = value; } // null allowed?
}
public WebHeaderCollection Headers {
get { return headers; }
set {
/**
* "If you attempt to set a Content-Length, Keep-Alive, Transfer-Encoding, or
* WWW-Authenticate header using the Headers property, an exception will be
* thrown. Use the KeepAlive or ContentLength64 properties to set these headers.
* You cannot set the Transfer-Encoding or WWW-Authenticate headers manually."
*/
// TODO: check if this is marked readonly after headers are sent.
headers = value;
}
}
public bool KeepAlive {
get { return keep_alive; }
set {
if (disposed)
throw new ObjectDisposedException (GetType ().ToString ());
if (HeadersSent)
throw new InvalidOperationException ("Cannot be changed after headers are sent.");
keep_alive = value;
}
}
public Stream OutputStream {
get {
if (output_stream == null)
output_stream = context.Connection.GetResponseStream ();
return output_stream;
}
}
public Version ProtocolVersion {
get { return version; }
set {
if (disposed)
throw new ObjectDisposedException (GetType ().ToString ());
if (HeadersSent)
throw new InvalidOperationException ("Cannot be changed after headers are sent.");
if (value == null)
throw new ArgumentNullException ("value");
if (value.Major != 1 || (value.Minor != 0 && value.Minor != 1))
throw new ArgumentException ("Must be 1.0 or 1.1", "value");
if (disposed)
throw new ObjectDisposedException (GetType ().ToString ());
version = value;
}
}
public string RedirectLocation {
get { return location; }
set {
if (disposed)
throw new ObjectDisposedException (GetType ().ToString ());
if (HeadersSent)
throw new InvalidOperationException ("Cannot be changed after headers are sent.");
location = value;
}
}
public bool SendChunked {
get { return chunked; }
set {
if (disposed)
throw new ObjectDisposedException (GetType ().ToString ());
if (HeadersSent)
throw new InvalidOperationException ("Cannot be changed after headers are sent.");
chunked = value;
}
}
public int StatusCode {
get { return status_code; }
set {
if (disposed)
throw new ObjectDisposedException (GetType ().ToString ());
if (HeadersSent)
throw new InvalidOperationException ("Cannot be changed after headers are sent.");
if (value < 100 || value > 999)
throw new ProtocolViolationException ("StatusCode must be between 100 and 999.");
status_code = value;
status_description = GetStatusDescription (value);
}
}
internal static string GetStatusDescription (int code)
{
switch (code){
case 100: return "Continue";
case 101: return "Switching Protocols";
case 102: return "Processing";
case 200: return "OK";
case 201: return "Created";
case 202: return "Accepted";
case 203: return "Non-Authoritative Information";
case 204: return "No Content";
case 205: return "Reset Content";
case 206: return "Partial Content";
case 207: return "Multi-Status";
case 300: return "Multiple Choices";
case 301: return "Moved Permanently";
case 302: return "Found";
case 303: return "See Other";
case 304: return "Not Modified";
case 305: return "Use Proxy";
case 307: return "Temporary Redirect";
case 400: return "Bad Request";
case 401: return "Unauthorized";
case 402: return "Payment Required";
case 403: return "Forbidden";
case 404: return "Not Found";
case 405: return "Method Not Allowed";
case 406: return "Not Acceptable";
case 407: return "Proxy Authentication Required";
case 408: return "Request Timeout";
case 409: return "Conflict";
case 410: return "Gone";
case 411: return "Length Required";
case 412: return "Precondition Failed";
case 413: return "Request Entity Too Large";
case 414: return "Request-Uri Too Long";
case 415: return "Unsupported Media Type";
case 416: return "Requested Range Not Satisfiable";
case 417: return "Expectation Failed";
case 422: return "Unprocessable Entity";
case 423: return "Locked";
case 424: return "Failed Dependency";
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Timeout";
case 505: return "Http Version Not Supported";
case 507: return "Insufficient Storage";
}
return "";
}
public string StatusDescription {
get { return status_description; }
set {
status_description = value;
}
}
void IDisposable.Dispose ()
{
Close (true); //TODO: Abort or Close?
}
public void Abort ()
{
if (disposed)
return;
Close (true);
}
public void AddHeader (string name, string value)
{
if (name == null)
throw new ArgumentNullException ("name");
if (name == "")
throw new ArgumentException ("'name' cannot be empty", "name");
//TODO: check for forbidden headers and invalid characters
if (value.Length > 65535)
throw new ArgumentOutOfRangeException ("value");
headers.Set (name, value);
}
public void AppendCookie (Cookie cookie)
{
if (cookie == null)
throw new ArgumentNullException ("cookie");
Cookies.Add (cookie);
}
public void AppendHeader (string name, string value)
{
if (name == null)
throw new ArgumentNullException ("name");
if (name == "")
throw new ArgumentException ("'name' cannot be empty", "name");
if (value.Length > 65535)
throw new ArgumentOutOfRangeException ("value");
headers.Add (name, value);
}
void Close (bool force)
{
disposed = true;
context.Connection.Close (force);
}
public void Close ()
{
if (disposed)
return;
Close (false);
}
public void Close (byte [] responseEntity, bool willBlock)
{
if (disposed)
return;
if (responseEntity == null)
throw new ArgumentNullException ("responseEntity");
//TODO: if willBlock -> BeginWrite + Close ?
ContentLength64 = responseEntity.Length;
OutputStream.Write (responseEntity, 0, (int) content_length);
Close (false);
}
public void CopyFrom (HttpListenerResponse templateResponse)
{
headers.Clear ();
headers.Add (templateResponse.headers);
content_length = templateResponse.content_length;
status_code = templateResponse.status_code;
status_description = templateResponse.status_description;
keep_alive = templateResponse.keep_alive;
version = templateResponse.version;
}
public void Redirect (string url)
{
StatusCode = 302; // Found
location = url;
}
bool FindCookie (Cookie cookie)
{
string name = cookie.Name;
string domain = cookie.Domain;
string path = cookie.Path;
foreach (Cookie c in cookies) {
if (name != c.Name)
continue;
if (domain != c.Domain)
continue;
if (path == c.Path)
return true;
}
return false;
}
internal void SendHeaders (bool closing, MemoryStream ms)
{
Encoding encoding = content_encoding;
if (encoding == null)
encoding = Encoding.Default;
if (content_type != null) {
if (content_encoding != null && content_type.IndexOf ("charset=", StringComparison.Ordinal) == -1) {
string enc_name = content_encoding.WebName;
headers.SetInternal ("Content-Type", content_type + "; charset=" + enc_name);
} else {
headers.SetInternal ("Content-Type", content_type);
}
}
if (headers ["Server"] == null)
headers.SetInternal ("Server", "Mono-HTTPAPI/1.0");
CultureInfo inv = CultureInfo.InvariantCulture;
if (headers ["Date"] == null)
headers.SetInternal ("Date", DateTime.UtcNow.ToString ("r", inv));
if (!chunked) {
if (!cl_set && closing) {
cl_set = true;
content_length = 0;
}
if (cl_set)
headers.SetInternal ("Content-Length", content_length.ToString (inv));
}
Version v = context.Request.ProtocolVersion;
if (!cl_set && !chunked && v >= HttpVersion.Version11)
chunked = true;
/* Apache forces closing the connection for these status codes:
* HttpStatusCode.BadRequest 400
* HttpStatusCode.RequestTimeout 408
* HttpStatusCode.LengthRequired 411
* HttpStatusCode.RequestEntityTooLarge 413
* HttpStatusCode.RequestUriTooLong 414
* HttpStatusCode.InternalServerError 500
* HttpStatusCode.ServiceUnavailable 503
*/
bool conn_close = (status_code == 400 || status_code == 408 || status_code == 411 ||
status_code == 413 || status_code == 414 || status_code == 500 ||
status_code == 503);
if (conn_close == false)
conn_close = !context.Request.KeepAlive;
// They sent both KeepAlive: true and Connection: close!?
if (!keep_alive || conn_close) {
headers.SetInternal ("Connection", "close");
conn_close = true;
}
if (chunked)
headers.SetInternal ("Transfer-Encoding", "chunked");
int reuses = context.Connection.Reuses;
if (reuses >= 100) {
force_close_chunked = true;
if (!conn_close) {
headers.SetInternal ("Connection", "close");
conn_close = true;
}
}
if (!conn_close) {
headers.SetInternal ("Keep-Alive", String.Format ("timeout=15,max={0}", 100 - reuses));
if (context.Request.ProtocolVersion <= HttpVersion.Version10)
headers.SetInternal ("Connection", "keep-alive");
}
if (location != null)
headers.SetInternal ("Location", location);
if (cookies != null) {
foreach (Cookie cookie in cookies)
headers.SetInternal ("Set-Cookie", cookie.ToClientString ());
}
StreamWriter writer = new StreamWriter (ms, encoding, 256);
writer.Write ("HTTP/{0} {1} {2}\r\n", version, status_code, status_description);
string headers_str = headers.ToStringMultiValue ();
writer.Write (headers_str);
writer.Flush ();
int preamble = (encoding.CodePage == 65001) ? 3 : encoding.GetPreamble ().Length;
if (output_stream == null)
output_stream = context.Connection.GetResponseStream ();
/* Assumes that the ms was at position 0 */
ms.Position = preamble;
HeadersSent = true;
}
public void SetCookie (Cookie cookie)
{
if (cookie == null)
throw new ArgumentNullException ("cookie");
if (cookies != null) {
if (FindCookie (cookie))
throw new ArgumentException ("The cookie already exists.");
} else {
cookies = new CookieCollection ();
}
cookies.Add (cookie);
}
}
}

View File

@@ -0,0 +1,101 @@
#region MIT License
/**
* HttpListenerWebSocketContext.cs
*
* The MIT License
*
* Copyright (c) 2012 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.Generic;
using System.Collections.Specialized;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Principal;
namespace WebSocketSharp.Net {
public class HttpListenerWebSocketContext : WebSocketContext
{
private HttpListenerContext _context;
private WebSocket _socket;
internal HttpListenerWebSocketContext(HttpListenerContext context)
{
_context = context;
_socket = new WebSocket(this);
}
internal HttpListenerContext BaseContext {
get { return _context; }
}
public override CookieCollection CookieCollection {
get { return _context.Request.Cookies; }
}
public override NameValueCollection Headers {
get { return _context.Request.Headers; }
}
public override bool IsAuthenticated {
get { return _context.Request.IsAuthenticated; }
}
public override bool IsSecureConnection {
get { return _context.Request.IsSecureConnection; }
}
public override bool IsLocal {
get { return _context.Request.IsLocal; }
}
public override string Origin {
get { return Headers["Origin"]; }
}
public override Uri RequestUri {
get { return _context.Request.RawUrl.ToUri(); }
}
public override string SecWebSocketKey {
get { return Headers["Sec-WebSocket-Key"]; }
}
public override IEnumerable<string> SecWebSocketProtocols {
get { return Headers.GetValues("Sec-WebSocket-Protocol"); }
}
public override string SecWebSocketVersion {
get { return Headers["Sec-WebSocket-Version"]; }
}
public override IPrincipal User {
get { return _context.User; }
}
public override WebSocket WebSocket {
get { return _socket; }
}
}
}

View File

@@ -0,0 +1,84 @@
//
// HttpStatusCode.cs
// Copied from System.Net.HttpStatusCode
//
// This code was automatically generated from
// ECMA CLI XML Library Specification.
// Generator: libgen.xsl [1.0; (C) Sergey Chaban (serge@wildwestsoftware.com)]
// Created: Wed, 5 Sep 2001 06:32:05 UTC
// Source file: AllTypes.xml
// URL: http://msdn.microsoft.com/net/ecma/AllTypes.xml
//
// Copyright (C) 2001 Ximian, Inc. (http://www.ximian.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
namespace WebSocketSharp.Net {
public enum HttpStatusCode {
Continue = 100,
SwitchingProtocols = 101,
OK = 200,
Created = 201,
Accepted = 202,
NonAuthoritativeInformation = 203,
NoContent = 204,
ResetContent = 205,
PartialContent = 206,
MultipleChoices = 300,
Ambiguous = 300,
MovedPermanently = 301,
Moved = 301,
Found = 302,
Redirect = 302,
SeeOther = 303,
RedirectMethod = 303,
NotModified = 304,
UseProxy = 305,
Unused = 306,
TemporaryRedirect = 307,
RedirectKeepVerb = 307,
BadRequest = 400,
Unauthorized = 401,
PaymentRequired = 402,
Forbidden = 403,
NotFound = 404,
MethodNotAllowed = 405,
NotAcceptable = 406,
ProxyAuthenticationRequired = 407,
RequestTimeout = 408,
Conflict = 409,
Gone = 410,
LengthRequired = 411,
PreconditionFailed = 412,
RequestEntityTooLarge = 413,
RequestUriTooLong = 414,
UnsupportedMediaType = 415,
RequestedRangeNotSatisfiable = 416,
ExpectationFailed = 417,
InternalServerError = 500,
NotImplemented = 501,
BadGateway = 502,
ServiceUnavailable = 503,
GatewayTimeout = 504,
HttpVersionNotSupported = 505,
}
}

View File

@@ -0,0 +1,98 @@
//
// HttpStreamAsyncResult.cs
// Copied from System.Net.HttpStreamAsyncResult
//
// Authors:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
//
// Copyright (C) 2005 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Net;
using System.Threading;
namespace WebSocketSharp.Net {
class HttpStreamAsyncResult : IAsyncResult
{
bool completed;
ManualResetEvent handle;
object locker = new object ();
internal AsyncCallback Callback;
internal int Count;
internal byte [] Buffer;
internal Exception Error;
internal int Offset;
internal object State;
internal int SynchRead;
public object AsyncState {
get { return State; }
}
public WaitHandle AsyncWaitHandle {
get {
lock (locker) {
if (handle == null)
handle = new ManualResetEvent (completed);
}
return handle;
}
}
public bool CompletedSynchronously {
get { return (SynchRead == Count); }
}
public bool IsCompleted {
get {
lock (locker) {
return completed;
}
}
}
public void Complete ()
{
lock (locker) {
if (completed)
return;
completed = true;
if (handle != null)
handle.Set ();
if (Callback != null)
Callback.BeginInvoke (this, null, null);
}
}
public void Complete (Exception e)
{
Error = e;
Complete ();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,42 @@
//
// HttpVersion.cs
// Copied from System.Net.HttpVersion
//
// Author:
// Lawrence Pit (loz@cable.a2000.nl)
//
// 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.
//
using System;
namespace WebSocketSharp.Net {
// <remarks>
// </remarks>
public class HttpVersion {
public static readonly Version Version10 = new Version (1, 0);
public static readonly Version Version11 = new Version (1, 1);
// pretty useless..
public HttpVersion () {}
}
}

View File

@@ -0,0 +1,186 @@
//
// ListenerAsyncResult.cs
// Copied from System.Net.ListenerAsyncResult
//
// Authors:
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
//
// Copyright (c) 2005 Ximian, Inc (http://www.ximian.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Net;
using System.Threading;
namespace WebSocketSharp.Net {
class ListenerAsyncResult : IAsyncResult
{
static WaitCallback InvokeCB = new WaitCallback (InvokeCallback);
AsyncCallback cb;
bool completed;
HttpListenerContext context;
Exception exception;
ListenerAsyncResult forward;
ManualResetEvent handle;
object locker;
object state;
bool synch;
internal bool EndCalled;
internal bool InGet;
public ListenerAsyncResult (AsyncCallback cb, object state)
{
this.cb = cb;
this.state = state;
this.locker = new object();
}
public object AsyncState {
get {
if (forward != null)
return forward.AsyncState;
return state;
}
}
public WaitHandle AsyncWaitHandle {
get {
if (forward != null)
return forward.AsyncWaitHandle;
lock (locker) {
if (handle == null)
handle = new ManualResetEvent (completed);
}
return handle;
}
}
public bool CompletedSynchronously {
get {
if (forward != null)
return forward.CompletedSynchronously;
return synch;
}
}
public bool IsCompleted {
get {
if (forward != null)
return forward.IsCompleted;
lock (locker) {
return completed;
}
}
}
static void InvokeCallback (object o)
{
ListenerAsyncResult ares = (ListenerAsyncResult) o;
if (ares.forward != null) {
InvokeCallback (ares.forward);
return;
}
try {
ares.cb (ares);
} catch {
}
}
internal void Complete (Exception exc)
{
if (forward != null) {
forward.Complete (exc);
return;
}
exception = exc;
if (InGet && (exc is ObjectDisposedException))
exception = new HttpListenerException (500, "Listener closed");
lock (locker) {
completed = true;
if (handle != null)
handle.Set ();
if (cb != null)
ThreadPool.UnsafeQueueUserWorkItem (InvokeCB, this);
}
}
internal void Complete (HttpListenerContext context)
{
Complete (context, false);
}
internal void Complete (HttpListenerContext context, bool synch)
{
if (forward != null) {
forward.Complete (context, synch);
return;
}
this.synch = synch;
this.context = context;
lock (locker) {
AuthenticationSchemes schemes = context.Listener.SelectAuthenticationScheme (context);
if ((schemes == AuthenticationSchemes.Basic || context.Listener.AuthenticationSchemes == AuthenticationSchemes.Negotiate) && context.Request.Headers ["Authorization"] == null) {
context.Response.StatusCode = 401;
context.Response.Headers ["WWW-Authenticate"] = schemes + " realm=\"" + context.Listener.Realm + "\"";
context.Response.OutputStream.Close ();
IAsyncResult ares = context.Listener.BeginGetContext (cb, state);
this.forward = (ListenerAsyncResult) ares;
lock (forward.locker) {
if (handle != null)
forward.handle = handle;
}
ListenerAsyncResult next = forward;
for (int i = 0; next.forward != null; i++) {
if (i > 20)
Complete (new HttpListenerException (400, "Too many authentication errors"));
next = next.forward;
}
} else {
completed = true;
if (handle != null)
handle.Set ();
if (cb != null)
ThreadPool.UnsafeQueueUserWorkItem (InvokeCB, this);
}
}
}
internal HttpListenerContext GetContext ()
{
if (forward != null)
return forward.GetContext ();
if (exception != null)
throw exception;
return context;
}
}
}

View File

@@ -0,0 +1,166 @@
//
// ListenerPrefix.cs
// Copied from System.ListenerPrefix
//
// Author:
// Gonzalo Paniagua Javier (gonzalo@novell.com)
// Oleg Mihailik (mihailik gmail co_m)
//
// Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Net;
namespace WebSocketSharp.Net {
sealed class ListenerPrefix {
IPAddress [] addresses;
string host;
string original;
string path;
ushort port;
bool secure;
public HttpListener Listener;
public ListenerPrefix (string prefix)
{
original = prefix;
Parse (prefix);
}
public IPAddress [] Addresses {
get { return addresses; }
set { addresses = value; }
}
public string Host {
get { return host; }
}
public int Port {
get { return (int) port; }
}
public string Path {
get { return path; }
}
public bool Secure {
get { return secure; }
}
void Parse (string uri)
{
int default_port = (uri.StartsWith ("http://")) ? 80 : -1;
if (default_port == -1) {
default_port = (uri.StartsWith ("https://")) ? 443 : -1;
secure = true;
}
int length = uri.Length;
int start_host = uri.IndexOf (':') + 3;
if (start_host >= length)
throw new ArgumentException ("No host specified.");
int colon = uri.IndexOf (':', start_host, length - start_host);
int root;
if (colon > 0) {
host = uri.Substring (start_host, colon - start_host);
root = uri.IndexOf ('/', colon, length - colon);
port = (ushort) Int32.Parse (uri.Substring (colon + 1, root - colon - 1));
path = uri.Substring (root);
} else {
root = uri.IndexOf ('/', start_host, length - start_host);
host = uri.Substring (start_host, root - start_host);
path = uri.Substring (root);
}
if (path.Length != 1)
path = path.Substring (0, path.Length - 1);
}
public static void CheckUri (string uri)
{
if (uri == null)
throw new ArgumentNullException ("uriPrefix");
int default_port = (uri.StartsWith ("http://")) ? 80 : -1;
if (default_port == -1)
default_port = (uri.StartsWith ("https://")) ? 443 : -1;
if (default_port == -1)
throw new ArgumentException ("Only 'http' and 'https' schemes are supported.");
int length = uri.Length;
int start_host = uri.IndexOf (':') + 3;
if (start_host >= length)
throw new ArgumentException ("No host specified.");
int colon = uri.IndexOf (':', start_host, length - start_host);
if (start_host == colon)
throw new ArgumentException ("No host specified.");
int root;
if (colon > 0) {
root = uri.IndexOf ('/', colon, length - colon);
if (root == -1)
throw new ArgumentException ("No path specified.");
try {
int p = Int32.Parse (uri.Substring (colon + 1, root - colon - 1));
if (p <= 0 || p >= 65536)
throw new Exception ();
} catch {
throw new ArgumentException ("Invalid port.");
}
} else {
root = uri.IndexOf ('/', start_host, length - start_host);
if (root == -1)
throw new ArgumentException ("No path specified.");
}
if (uri [uri.Length - 1] != '/')
throw new ArgumentException ("The prefix must end with '/'");
}
// Equals and GetHashCode are required to detect duplicates in HttpListenerPrefixCollection.
public override bool Equals (object o)
{
ListenerPrefix other = o as ListenerPrefix;
if (other == null)
return false;
return (original == other.original);
}
public override int GetHashCode ()
{
return original.GetHashCode ();
}
public override string ToString ()
{
return original;
}
}
}

View File

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

View File

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

View File

@@ -0,0 +1,730 @@
//
// WebHeaderCollection.cs
// Copied from System.Net.WebHeaderCollection
//
// Authors:
// Lawrence Pit (loz@cable.a2000.nl)
// Gonzalo Paniagua Javier (gonzalo@ximian.com)
// Miguel de Icaza (miguel@novell.com)
//
// Copyright 2003 Ximian, Inc. (http://www.ximian.com)
// Copyright 2007 Novell, Inc. (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Net;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Text;
// See RFC 2068 par 4.2 Message Headers
namespace WebSocketSharp.Net {
[Serializable]
[ComVisible(true)]
public class WebHeaderCollection : NameValueCollection, ISerializable
{
private static readonly Dictionary<string, bool> multiValue;
private static readonly Dictionary<string, bool> restricted;
private static readonly Dictionary<string, bool> restricted_response;
private bool internallyCreated = false;
// Static Initializer
static WebHeaderCollection ()
{
// the list of restricted header names as defined
// by the ms.net spec
restricted = new Dictionary<string, bool> (StringComparer.InvariantCultureIgnoreCase);
restricted.Add ("accept", true);
restricted.Add ("connection", true);
restricted.Add ("content-length", true);
restricted.Add ("content-type", true);
restricted.Add ("date", true);
restricted.Add ("expect", true);
restricted.Add ("host", true);
restricted.Add ("if-modified-since", true);
restricted.Add ("range", true);
restricted.Add ("referer", true);
restricted.Add ("transfer-encoding", true);
restricted.Add ("user-agent", true);
restricted.Add ("proxy-connection", true);
//
restricted_response = new Dictionary<string, bool> (StringComparer.InvariantCultureIgnoreCase);
restricted_response.Add ("Content-Length", true);
restricted_response.Add ("Transfer-Encoding", true);
restricted_response.Add ("WWW-Authenticate", true);
// see par 14 of RFC 2068 to see which header names
// accept multiple values each separated by a comma
multiValue = new Dictionary<string, bool> (StringComparer.InvariantCultureIgnoreCase);
multiValue.Add ("accept", true);
multiValue.Add ("accept-charset", true);
multiValue.Add ("accept-encoding", true);
multiValue.Add ("accept-language", true);
multiValue.Add ("accept-ranges", true);
multiValue.Add ("allow", true);
multiValue.Add ("authorization", true);
multiValue.Add ("cache-control", true);
multiValue.Add ("connection", true);
multiValue.Add ("content-encoding", true);
multiValue.Add ("content-language", true);
multiValue.Add ("expect", true);
multiValue.Add ("if-match", true);
multiValue.Add ("if-none-match", true);
multiValue.Add ("proxy-authenticate", true);
multiValue.Add ("public", true);
multiValue.Add ("range", true);
multiValue.Add ("transfer-encoding", true);
multiValue.Add ("upgrade", true);
multiValue.Add ("vary", true);
multiValue.Add ("via", true);
multiValue.Add ("warning", true);
multiValue.Add ("www-authenticate", true);
// Extra
multiValue.Add ("set-cookie", true);
multiValue.Add ("set-cookie2", true);
}
// Constructors
public WebHeaderCollection () { }
protected WebHeaderCollection (
SerializationInfo serializationInfo,
StreamingContext streamingContext)
{
int count;
try {
count = serializationInfo.GetInt32("Count");
for (int i = 0; i < count; i++)
this.Add (serializationInfo.GetString (i.ToString ()),
serializationInfo.GetString ((count + i).ToString ()));
} catch (SerializationException){
count = serializationInfo.GetInt32("count");
for (int i = 0; i < count; i++)
this.Add (serializationInfo.GetString ("k" + i),
serializationInfo.GetString ("v" + i));
}
}
internal WebHeaderCollection (bool internallyCreated)
{
this.internallyCreated = internallyCreated;
}
// Methods
public void Add (string header)
{
if (header == null)
throw new ArgumentNullException ("header");
int pos = header.IndexOf (':');
if (pos == -1)
throw new ArgumentException ("no colon found", "header");
this.Add (header.Substring (0, pos),
header.Substring (pos + 1));
}
public override void Add (string name, string value)
{
if (name == null)
throw new ArgumentNullException ("name");
if (internallyCreated && IsRestricted (name))
throw new ArgumentException ("This header must be modified with the appropiate property.");
this.AddWithoutValidate (name, value);
}
protected void AddWithoutValidate (string headerName, string headerValue)
{
if (!IsHeaderName (headerName))
throw new ArgumentException ("invalid header name: " + headerName, "headerName");
if (headerValue == null)
headerValue = String.Empty;
else
headerValue = headerValue.Trim ();
if (!IsHeaderValue (headerValue))
throw new ArgumentException ("invalid header value: " + headerValue, "headerValue");
base.Add (headerName, headerValue);
}
public override string [] GetValues (string header)
{
if (header == null)
throw new ArgumentNullException ("header");
string [] values = base.GetValues (header);
if (values == null || values.Length == 0)
return null;
/*
if (IsMultiValue (header)) {
values = GetMultipleValues (values);
}
*/
return values;
}
public override string[] GetValues (int index)
{
string[] values = base.GetValues (index);
if (values == null || values.Length == 0) {
return(null);
}
return(values);
}
/* Now i wonder why this is here...
static string [] GetMultipleValues (string [] values)
{
ArrayList mvalues = new ArrayList (values.Length);
StringBuilder sb = null;
for (int i = 0; i < values.Length; ++i) {
string val = values [i];
if (val.IndexOf (',') == -1) {
mvalues.Add (val);
continue;
}
if (sb == null)
sb = new StringBuilder ();
bool quote = false;
for (int k = 0; k < val.Length; k++) {
char c = val [k];
if (c == '"') {
quote = !quote;
} else if (!quote && c == ',') {
mvalues.Add (sb.ToString ().Trim ());
sb.Length = 0;
continue;
}
sb.Append (c);
}
if (sb.Length > 0) {
mvalues.Add (sb.ToString ().Trim ());
sb.Length = 0;
}
}
return (string []) mvalues.ToArray (typeof (string));
}
*/
public static bool IsRestricted (string headerName)
{
if (headerName == null)
throw new ArgumentNullException ("headerName");
if (headerName == "") // MS throw nullexception here!
throw new ArgumentException ("empty string", "headerName");
if (!IsHeaderName (headerName))
throw new ArgumentException ("Invalid character in header");
return restricted.ContainsKey (headerName);
}
public static bool IsRestricted (string headerName, bool response)
{
if (String.IsNullOrEmpty (headerName))
throw new ArgumentNullException ("headerName");
if (!IsHeaderName (headerName))
throw new ArgumentException ("Invalid character in header");
if (response)
return restricted_response.ContainsKey (headerName);
return restricted.ContainsKey (headerName);
}
public override void OnDeserialization (object sender)
{
}
public override void Remove (string name)
{
if (name == null)
throw new ArgumentNullException ("name");
if (internallyCreated && IsRestricted (name))
throw new ArgumentException ("restricted header");
base.Remove (name);
}
public override void Set (string name, string value)
{
if (name == null)
throw new ArgumentNullException ("name");
if (internallyCreated && IsRestricted (name))
throw new ArgumentException ("restricted header");
if (!IsHeaderName (name))
throw new ArgumentException ("invalid header name");
if (value == null)
value = String.Empty;
else
value = value.Trim ();
if (!IsHeaderValue (value))
throw new ArgumentException ("invalid header value");
base.Set (name, value);
}
public byte[] ToByteArray ()
{
return Encoding.UTF8.GetBytes(ToString ());
}
internal string ToStringMultiValue ()
{
StringBuilder sb = new StringBuilder();
int count = base.Count;
for (int i = 0; i < count ; i++) {
string key = GetKey (i);
if (IsMultiValue (key)) {
foreach (string v in GetValues (i)) {
sb.Append (key)
.Append (": ")
.Append (v)
.Append ("\r\n");
}
} else {
sb.Append (key)
.Append (": ")
.Append (Get (i))
.Append ("\r\n");
}
}
return sb.Append("\r\n").ToString();
}
public override string ToString ()
{
StringBuilder sb = new StringBuilder();
int count = base.Count;
for (int i = 0; i < count ; i++)
sb.Append (GetKey (i))
.Append (": ")
.Append (Get (i))
.Append ("\r\n");
return sb.Append("\r\n").ToString();
}
void ISerializable.GetObjectData (
SerializationInfo serializationInfo,
StreamingContext streamingContext)
{
GetObjectData (serializationInfo, streamingContext);
}
public override void GetObjectData (SerializationInfo serializationInfo, StreamingContext streamingContext)
{
int count = base.Count;
serializationInfo.AddValue ("Count", count);
for (int i = 0; i < count; i++) {
serializationInfo.AddValue (i.ToString (), GetKey (i));
serializationInfo.AddValue ((count + i).ToString (), Get (i));
}
}
public override string[] AllKeys
{
get {
return(base.AllKeys);
}
}
public override int Count
{
get {
return(base.Count);
}
}
public override KeysCollection Keys
{
get {
return(base.Keys);
}
}
public override string Get (int index)
{
return(base.Get (index));
}
public override string Get (string name)
{
return(base.Get (name));
}
public override string GetKey (int index)
{
return(base.GetKey (index));
}
public void Add (HttpRequestHeader header, string value)
{
Add (RequestHeaderToString (header), value);
}
public void Remove (HttpRequestHeader header)
{
Remove (RequestHeaderToString (header));
}
public void Set (HttpRequestHeader header, string value)
{
Set (RequestHeaderToString (header), value);
}
public void Add (HttpResponseHeader header, string value)
{
Add (ResponseHeaderToString (header), value);
}
public void Remove (HttpResponseHeader header)
{
Remove (ResponseHeaderToString (header));
}
public void Set (HttpResponseHeader header, string value)
{
Set (ResponseHeaderToString (header), value);
}
string RequestHeaderToString (HttpRequestHeader value)
{
switch (value){
case HttpRequestHeader.CacheControl:
return "Cache-Control";
case HttpRequestHeader.Connection:
return "Connection";
case HttpRequestHeader.Date:
return "Date";
case HttpRequestHeader.KeepAlive:
return "Keep-Alive";
case HttpRequestHeader.Pragma:
return "Pragma";
case HttpRequestHeader.Trailer:
return "Trailer";
case HttpRequestHeader.TransferEncoding:
return "Transfer-Encoding";
case HttpRequestHeader.Upgrade:
return "Upgrade";
case HttpRequestHeader.Via:
return "Via";
case HttpRequestHeader.Warning:
return "Warning";
case HttpRequestHeader.Allow:
return "Allow";
case HttpRequestHeader.ContentLength:
return "Content-Length";
case HttpRequestHeader.ContentType:
return "Content-Type";
case HttpRequestHeader.ContentEncoding:
return "Content-Encoding";
case HttpRequestHeader.ContentLanguage:
return "Content-Language";
case HttpRequestHeader.ContentLocation:
return "Content-Location";
case HttpRequestHeader.ContentMd5:
return "Content-MD5";
case HttpRequestHeader.ContentRange:
return "Content-Range";
case HttpRequestHeader.Expires:
return "Expires";
case HttpRequestHeader.LastModified:
return "Last-Modified";
case HttpRequestHeader.Accept:
return "Accept";
case HttpRequestHeader.AcceptCharset:
return "Accept-Charset";
case HttpRequestHeader.AcceptEncoding:
return "Accept-Encoding";
case HttpRequestHeader.AcceptLanguage:
return "accept-language";
case HttpRequestHeader.Authorization:
return "Authorization";
case HttpRequestHeader.Cookie:
return "Cookie";
case HttpRequestHeader.Expect:
return "Expect";
case HttpRequestHeader.From:
return "From";
case HttpRequestHeader.Host:
return "Host";
case HttpRequestHeader.IfMatch:
return "If-Match";
case HttpRequestHeader.IfModifiedSince:
return "If-Modified-Since";
case HttpRequestHeader.IfNoneMatch:
return "If-None-Match";
case HttpRequestHeader.IfRange:
return "If-Range";
case HttpRequestHeader.IfUnmodifiedSince:
return "If-Unmodified-Since";
case HttpRequestHeader.MaxForwards:
return "Max-Forwards";
case HttpRequestHeader.ProxyAuthorization:
return "Proxy-Authorization";
case HttpRequestHeader.Referer:
return "Referer";
case HttpRequestHeader.Range:
return "Range";
case HttpRequestHeader.Te:
return "TE";
case HttpRequestHeader.Translate:
return "Translate";
case HttpRequestHeader.UserAgent:
return "User-Agent";
default:
throw new InvalidOperationException ();
}
}
public string this[HttpRequestHeader hrh]
{
get {
return Get (RequestHeaderToString (hrh));
}
set {
Add (RequestHeaderToString (hrh), value);
}
}
string ResponseHeaderToString (HttpResponseHeader value)
{
switch (value){
case HttpResponseHeader.CacheControl:
return "Cache-Control";
case HttpResponseHeader.Connection:
return "Connection";
case HttpResponseHeader.Date:
return "Date";
case HttpResponseHeader.KeepAlive:
return "Keep-Alive";
case HttpResponseHeader.Pragma:
return "Pragma";
case HttpResponseHeader.Trailer:
return "Trailer";
case HttpResponseHeader.TransferEncoding:
return "Transfer-Encoding";
case HttpResponseHeader.Upgrade:
return "Upgrade";
case HttpResponseHeader.Via:
return "Via";
case HttpResponseHeader.Warning:
return "Warning";
case HttpResponseHeader.Allow:
return "Allow";
case HttpResponseHeader.ContentLength:
return "Content-Length";
case HttpResponseHeader.ContentType:
return "Content-Type";
case HttpResponseHeader.ContentEncoding:
return "Content-Encoding";
case HttpResponseHeader.ContentLanguage:
return "Content-Language";
case HttpResponseHeader.ContentLocation:
return "Content-Location";
case HttpResponseHeader.ContentMd5:
return "Content-MD5";
case HttpResponseHeader.ContentRange:
return "Content-Range";
case HttpResponseHeader.Expires:
return "Expires";
case HttpResponseHeader.LastModified:
return "Last-Modified";
case HttpResponseHeader.AcceptRanges:
return "Accept-Ranges";
case HttpResponseHeader.Age:
return "Age";
case HttpResponseHeader.ETag:
return "ETag";
case HttpResponseHeader.Location:
return "Location";
case HttpResponseHeader.ProxyAuthenticate:
return "Proxy-Authenticate";
case HttpResponseHeader.RetryAfter:
return "Retry-After";
case HttpResponseHeader.Server:
return "Server";
case HttpResponseHeader.SetCookie:
return "Set-Cookie";
case HttpResponseHeader.Vary:
return "Vary";
case HttpResponseHeader.WwwAuthenticate:
return "WWW-Authenticate";
default:
throw new InvalidOperationException ();
}
}
public string this[HttpResponseHeader hrh]
{
get
{
return Get (ResponseHeaderToString (hrh));
}
set
{
Add (ResponseHeaderToString (hrh), value);
}
}
public override void Clear ()
{
base.Clear ();
}
public override IEnumerator GetEnumerator ()
{
return(base.GetEnumerator ());
}
// Internal Methods
// With this we don't check for invalid characters in header. See bug #55994.
internal void SetInternal (string header)
{
int pos = header.IndexOf (':');
if (pos == -1)
throw new ArgumentException ("no colon found", "header");
SetInternal (header.Substring (0, pos), header.Substring (pos + 1));
}
internal void SetInternal (string name, string value)
{
if (value == null)
value = String.Empty;
else
value = value.Trim ();
if (!IsHeaderValue (value))
throw new ArgumentException ("invalid header value");
if (IsMultiValue (name)) {
base.Add (name, value);
} else {
base.Remove (name);
base.Set (name, value);
}
}
internal void RemoveAndAdd (string name, string value)
{
if (value == null)
value = String.Empty;
else
value = value.Trim ();
base.Remove (name);
base.Set (name, value);
}
internal void RemoveInternal (string name)
{
if (name == null)
throw new ArgumentNullException ("name");
base.Remove (name);
}
// Private Methods
internal static bool IsMultiValue (string headerName)
{
if (headerName == null || headerName == "")
return false;
return multiValue.ContainsKey (headerName);
}
internal static bool IsHeaderValue (string value)
{
// TEXT any 8 bit value except CTL's (0-31 and 127)
// but including \r\n space and \t
// after a newline at least one space or \t must follow
// certain header fields allow comments ()
int len = value.Length;
for (int i = 0; i < len; i++) {
char c = value [i];
if (c == 127)
return false;
if (c < 0x20 && (c != '\r' && c != '\n' && c != '\t'))
return false;
if (c == '\n' && ++i < len) {
c = value [i];
if (c != ' ' && c != '\t')
return false;
}
}
return true;
}
internal static bool IsHeaderName (string name)
{
if (name == null || name.Length == 0)
return false;
int len = name.Length;
for (int i = 0; i < len; i++) {
char c = name [i];
if (c > 126 || !allowed_chars [(int) c])
return false;
}
return true;
}
static bool [] allowed_chars = new bool [126] {
false, false, false, false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false, false, false, false, false, false, false, false, false, false,
false, false, false, false, false, true, false, true, true, true, true, false, false, false, true,
true, false, true, true, false, true, true, true, true, true, true, true, true, true, true, false,
false, false, false, false, false, false, true, true, true, true, true, true, true, true, true,
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
false, false, false, true, true, true, true, true, true, true, true, true, true, true, true, true,
true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true,
false, true, false
};
}
}

View File

@@ -0,0 +1,55 @@
#region MIT License
/**
* WebSocketContext.cs
*
* The MIT License
*
* Copyright (c) 2012 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.Generic;
using System.Collections.Specialized;
using System.Security.Principal;
namespace WebSocketSharp.Net {
public abstract class WebSocketContext {
protected WebSocketContext()
{
}
public abstract CookieCollection CookieCollection { get; }
public abstract NameValueCollection Headers { get; }
public abstract bool IsAuthenticated { get; }
public abstract bool IsSecureConnection { get; }
public abstract bool IsLocal { get; }
public abstract string Origin { get; }
public abstract Uri RequestUri { get; }
public abstract string SecWebSocketKey { get; }
public abstract IEnumerable<string> SecWebSocketProtocols { get; }
public abstract string SecWebSocketVersion { get; }
public abstract IPrincipal User { get; }
public abstract WebSocket WebSocket { get; }
}
}

View File

@@ -27,36 +27,47 @@
#endregion
using System;
using System.Net;
using System.Collections.Specialized;
using System.Text;
using WebSocketSharp.Net;
namespace WebSocketSharp {
public class RequestHandshake : Handshake
{
#region Private Constructor
private RequestHandshake()
{
}
public RequestHandshake(string uri)
#endregion
#region Public Constructor
public RequestHandshake(string uriString)
{
Method = "GET";
Uri = uri;
Version = "HTTP/1.1";
Headers = new NameValueCollection();
HttpMethod = "GET";
RequestUri = uriString.ToUri();
AddHeader("Upgrade", "websocket");
AddHeader("Connection", "Upgrade");
}
#endregion
#region Properties
public string HttpMethod { get; private set; }
public bool IsWebSocketRequest {
get {
if (Method != "GET")
if (HttpMethod != "GET")
return false;
if (Version != "HTTP/1.1")
if (ProtocolVersion != HttpVersion.Version11)
return false;
if (!HeaderExists("Upgrade", "websocket"))
@@ -78,8 +89,21 @@ namespace WebSocketSharp {
}
}
public string Method { get; private set; }
public string Uri { get; private set; }
public Uri RequestUri { get; private set; }
#endregion
#region Public Static Methods
public static RequestHandshake Parse(WebSocketContext context)
{
return new RequestHandshake {
Headers = context.Headers,
HttpMethod = "GET",
RequestUri = context.RequestUri,
ProtocolVersion = HttpVersion.Version11
};
}
public static RequestHandshake Parse(string[] request)
{
@@ -92,18 +116,22 @@ namespace WebSocketSharp {
headers.Add(request[i]);
return new RequestHandshake {
Headers = headers,
Method = requestLine[0],
Uri = requestLine[1],
Version = requestLine[2]
Headers = headers,
HttpMethod = requestLine[0],
RequestUri = requestLine[1].ToUri(),
ProtocolVersion = new Version(requestLine[2].Substring(5))
};
}
#endregion
#region Public Method
public override string ToString()
{
var buffer = new StringBuilder();
buffer.AppendFormat("{0} {1} {2}{3}", Method, Uri, Version, _crlf);
buffer.AppendFormat("{0} {1} HTTP/{2}{3}", HttpMethod, RequestUri, ProtocolVersion, _crlf);
foreach (string key in Headers.AllKeys)
buffer.AppendFormat("{0}: {1}{2}", key, Headers[key], _crlf);
@@ -112,5 +140,7 @@ namespace WebSocketSharp {
return buffer.ToString();
}
#endregion
}
}

View File

@@ -28,31 +28,35 @@
using System;
using System.Collections.Specialized;
using System.Net;
using System.Text;
using WebSocketSharp.Net;
namespace WebSocketSharp {
public class ResponseHandshake : Handshake
{
#region Public Constructor
public ResponseHandshake()
{
Version = "HTTP/1.1";
Status = "101";
Reason = "Switching Protocols";
Headers = new NameValueCollection();
StatusCode = "101";
Reason = "Switching Protocols";
AddHeader("Upgrade", "websocket");
AddHeader("Connection", "Upgrade");
}
#endregion
#region Properties
public bool IsWebSocketResponse {
get {
if (Version != "HTTP/1.1")
if (ProtocolVersion != HttpVersion.Version11)
return false;
if (Status != "101")
if (StatusCode != "101")
return false;
if (!HeaderExists("Upgrade", "websocket"))
@@ -68,8 +72,12 @@ namespace WebSocketSharp {
}
}
public string Reason { get; private set; }
public string Status { get; private set; }
public string Reason { get; private set; }
public string StatusCode { get; private set; }
#endregion
#region Public Static Methods
public static ResponseHandshake Parse(string[] response)
{
@@ -86,18 +94,22 @@ namespace WebSocketSharp {
headers.Add(response[i]);
return new ResponseHandshake {
Headers = headers,
Reason = reason.ToString(),
Status = statusLine[1],
Version = statusLine[0]
Headers = headers,
Reason = reason.ToString(),
StatusCode = statusLine[1],
ProtocolVersion = new Version(statusLine[0].Substring(5))
};
}
#endregion
#region Public Methods
public override string ToString()
{
var buffer = new StringBuilder();
buffer.AppendFormat("{0} {1} {2}{3}", Version, Status, Reason, _crlf);
buffer.AppendFormat("HTTP/{0} {1} {2}{3}", ProtocolVersion, StatusCode, Reason, _crlf);
foreach (string key in Headers.AllKeys)
buffer.AppendFormat("{0}: {1}{2}", key, Headers[key], _crlf);
@@ -106,5 +118,7 @@ namespace WebSocketSharp {
return buffer.ToString();
}
#endregion
}
}

View File

@@ -0,0 +1,187 @@
#region MIT License
/**
* HttpServer.cs
*
* The MIT License
*
* Copyright (c) 2012 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.Configuration;
using System.IO;
using System.Threading;
using WebSocketSharp.Net;
namespace WebSocketSharp.Server {
public class HttpServer<T>
where T : WebSocketService, new()
{
#region Fields
private Thread _acceptRequestThread;
private HttpListener _listener;
private int _port;
private string _rootPath;
private Uri _wsPath;
private WebSocketServer<T> _wsServer;
#endregion
#region Constructors
public HttpServer()
: this(80)
{
}
public HttpServer(int port)
: this(port, "/")
{
}
public HttpServer(int port, string wsPath)
{
_listener = new HttpListener();
_port = port;
var prefix = String.Format(
"http{0}://*:{1}/", _port == 443 ? "s" : String.Empty, _port);
_listener.Prefixes.Add(prefix);
_rootPath = ConfigurationManager.AppSettings["RootPath"];
_wsPath = wsPath.ToUri();
_wsServer = new WebSocketServer<T>();
}
#endregion
#region Property
public int Port {
get { return _port; }
}
#endregion
#region Events
public event EventHandler<ErrorEventArgs> OnError;
public event EventHandler<ResponseEventArgs> OnResponse;
#endregion
#region Private Methods
private void acceptRequest()
{
while (true)
{
try
{
var context = _listener.GetContext();
respond(context);
}
catch (HttpListenerException)
{
// HttpListener has been closed.
break;
}
catch (Exception ex)
{
OnError.Emit(this, new ErrorEventArgs(ex.Message));
break;
}
}
}
private void respond(HttpListenerContext context)
{
WaitCallback respondCb = (state) =>
{
try
{
var req = context.Request;
var res = context.Response;
if (req.IsWebSocketRequest)
{
upgradeToWebSocket(context);
}
else
{
OnResponse.Emit(this, new ResponseEventArgs(context));
res.Close();
}
}
catch (Exception ex)
{
OnError.Emit(this, new ErrorEventArgs(ex.Message));
}
};
ThreadPool.QueueUserWorkItem(respondCb);
}
private void startAcceptRequestThread()
{
_acceptRequestThread = new Thread(new ThreadStart(acceptRequest));
_acceptRequestThread.IsBackground = true;
_acceptRequestThread.Start();
}
private void upgradeToWebSocket(HttpListenerContext context)
{
var wsContext = context.AcceptWebSocket();
var socket = wsContext.WebSocket;
if (_wsPath.ToString() != "/")
socket.Url = _wsPath;
_wsServer.BindWebSocket(socket);
}
#endregion
#region Public Methods
public byte[] GetFile(string path)
{
var filePath = _rootPath + path;
if (File.Exists(filePath))
return File.ReadAllBytes(filePath);
return null;
}
public void Start()
{
_listener.Start();
startAcceptRequestThread();
}
public void Stop()
{
_listener.Close();
_acceptRequestThread.Join(5 * 1000);
_wsServer.StopServices();
}
#endregion
}
}

View File

@@ -0,0 +1,43 @@
#region MIT License
/**
* ResponseEventArgs.cs
*
* The MIT License
*
* Copyright (c) 2012 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 WebSocketSharp.Net;
namespace WebSocketSharp.Server {
public class ResponseEventArgs : EventArgs
{
public HttpListenerContext Context { get; private set; }
public ResponseEventArgs(HttpListenerContext context)
{
Context = context;
}
}
}

View File

@@ -42,16 +42,27 @@ namespace WebSocketSharp.Server {
public class WebSocketServer<T>
where T : WebSocketService, new()
{
#region Private Fields
#region Fields
private Thread _acceptClientThread;
private bool _isSelfHost;
private Dictionary<string, WebSocketService> _services;
private TcpListener _tcpListener;
private Uri _uri;
#endregion
#region Constructors
#region Internal Constructor
internal WebSocketServer()
{
_services = new Dictionary<string, WebSocketService>();
_isSelfHost = false;
}
#endregion
#region Public Constructors
public WebSocketServer(string url)
{
@@ -81,6 +92,7 @@ namespace WebSocketSharp.Server {
_tcpListener = new TcpListener(ips[0], port);
_services = new Dictionary<string, WebSocketService>();
_isSelfHost = true;
}
public WebSocketServer(int port)
@@ -97,6 +109,7 @@ namespace WebSocketSharp.Server {
_tcpListener = new TcpListener(IPAddress.Any, port);
_services = new Dictionary<string, WebSocketService>();
_isSelfHost = true;
}
#endregion
@@ -113,6 +126,10 @@ namespace WebSocketSharp.Server {
get { return (IPEndPoint)_tcpListener.LocalEndpoint; }
}
public bool IsSelfHost {
get { return _isSelfHost; }
}
public int Port
{
get { return Endpoint.Port; }
@@ -209,14 +226,21 @@ namespace WebSocketSharp.Server {
public void Start()
{
if (!_isSelfHost)
return;
_tcpListener.Start();
startAcceptClientThread();
}
public void Stop()
{
_tcpListener.Stop();
_acceptClientThread.Join(5 * 1000);
if (_isSelfHost)
{
_tcpListener.Stop();
_acceptClientThread.Join(5 * 1000);
}
StopServices();
}

View File

@@ -46,6 +46,7 @@ using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using WebSocketSharp.Frame;
using WebSocketSharp.Net;
namespace WebSocketSharp
{
@@ -62,6 +63,7 @@ namespace WebSocketSharp
private string _base64key;
private string _binaryType;
private HttpListenerWebSocketContext _context;
private IPEndPoint _endPoint;
private AutoResetEvent _exitedMessageLoop;
private string _extensions;
@@ -103,6 +105,15 @@ namespace WebSocketSharp
#region Internal Constructor
internal WebSocket(HttpListenerWebSocketContext context)
: this()
{
_uri = new Uri("/", UriKind.Relative);
_context = context;
_isClient = false;
_isSecure = _context.IsSecureConnection;
}
internal WebSocket(Uri uri, TcpClient tcpClient)
: this()
{
@@ -219,8 +230,14 @@ namespace WebSocketSharp
get { return _unTransmittedBuffer; }
}
public string Url {
get { return _uri.ToString(); }
public Uri Url {
get { return _uri; }
set {
if (_readyState != WsState.CONNECTING || _isClient)
return;
_uri = value;
}
}
#endregion
@@ -260,7 +277,7 @@ namespace WebSocketSharp
private void close(PayloadData data)
{
#if DEBUG
Console.WriteLine("WS: Info@close: Current thread IsBackground?: {0}", Thread.CurrentThread.IsBackground);
Console.WriteLine("WS: Info@close: Current thread IsBackground ?: {0}", Thread.CurrentThread.IsBackground);
#endif
lock(_forClose)
{
@@ -278,7 +295,7 @@ namespace WebSocketSharp
ReadyState = WsState.CLOSING;
}
OnClose.Emit(this, new CloseEventArgs(data));
var frame = createFrame(Fin.FINAL, Opcode.CLOSE, data);
closeHandshake(frame);
@@ -311,12 +328,20 @@ namespace WebSocketSharp
private void closeConnection()
{
if (_context != null)
{
_context.BaseContext.Response.Close();
_wsStream = null;
_context = null;
}
if (_wsStream != null)
{
_wsStream.Dispose();
_wsStream = null;
}
else if (_netStream != null)
if (_netStream != null)
{
_netStream.Dispose();
_netStream = null;
@@ -441,6 +466,31 @@ namespace WebSocketSharp
}
private void createServerStream()
{
if (_context != null)
{
_wsStream = createServerStreamFromContext();
return;
}
if (_tcpClient != null)
{
_wsStream = createServerStreamFromTcpClient();
return;
}
}
private IWsStream createServerStreamFromContext()
{
var stream = _context.BaseContext.Connection.Stream;
if (IsSecure)
return new WsStream<SslStream>((SslStream)stream);
return new WsStream<NetworkStream>((NetworkStream)stream);
}
private IWsStream createServerStreamFromTcpClient()
{
_netStream = _tcpClient.GetStream();
@@ -448,15 +498,13 @@ namespace WebSocketSharp
{
_sslStream = new SslStream(_netStream);
string certPath = ConfigurationManager.AppSettings["ServerCertPath"];
var certPath = ConfigurationManager.AppSettings["ServerCertPath"];
_sslStream.AuthenticateAsServer(new X509Certificate(certPath));
_wsStream = new WsStream<SslStream>(_sslStream);
return;
return new WsStream<SslStream>(_sslStream);
}
_wsStream = new WsStream<NetworkStream>(_netStream);
return new WsStream<NetworkStream>(_netStream);
}
private void doHandshake()
@@ -492,7 +540,7 @@ namespace WebSocketSharp
{
if (!request.IsWebSocketRequest)
{
message = "Not WebSocket request.";
message = "Invalid WebSocket request.";
return false;
}
@@ -504,18 +552,12 @@ namespace WebSocketSharp
};
};
if (!isValidRequestUri(request.RequestUri, func("Request URI"), out message))
return false;
if (_uri.IsAbsoluteUri)
{
if (_uri.PathAndQuery.NotEqualsDo(request.Uri, func("Request URI"), out message, false))
return false;
if (!isValidRequestHost(request.GetHeaderValues("Host")[0], func("Host"), out message))
return false;
}
if (!_uri.IsAbsoluteUri)
if (_uri.ToString().NotEqualsDo(request.Uri, func("Request URI"), out message, false))
return false;
if (!request.HeaderExists("Sec-WebSocket-Version", _version))
{
@@ -556,11 +598,33 @@ namespace WebSocketSharp
return true;
}
private bool isValidRequestUri(Uri requestUri, Func<string, string, string> func, out string message)
{
if (_uri.IsAbsoluteUri && requestUri.IsAbsoluteUri)
if (_uri.ToString().NotEqualsDo(requestUri.ToString(), func, out message, false))
return false;
if (_uri.IsAbsoluteUri && !requestUri.IsAbsoluteUri)
if (_uri.PathAndQuery.NotEqualsDo(requestUri.ToString(), func, out message, false))
return false;
if (!_uri.IsAbsoluteUri && requestUri.IsAbsoluteUri)
if (_uri.ToString().NotEqualsDo(requestUri.PathAndQuery, func, out message, false))
return false;
if (!_uri.IsAbsoluteUri && !requestUri.IsAbsoluteUri)
if (_uri.ToString().NotEqualsDo(requestUri.ToString(), func, out message, false))
return false;
message = String.Empty;
return true;
}
private bool isValidResponse(ResponseHandshake response, out string message)
{
if (!response.IsWebSocketResponse)
{
message = "Not WebSocket response.";
message = "Invalid WebSocket response.";
return false;
}
@@ -719,7 +783,7 @@ namespace WebSocketSharp
else
{
#if DEBUG
Console.WriteLine("WS: Info@receive: Client starts closing handshake.");
Console.WriteLine("WS: Info@receive: Start closing handshake.");
#endif
close(CloseStatusCode.INCORRECT_DATA, String.Empty);
return null;
@@ -733,7 +797,7 @@ namespace WebSocketSharp
else if (frame.Opcode == Opcode.CLOSE)
{// FINAL & CLOSE
#if DEBUG
Console.WriteLine("WS: Info@receive: Server starts closing handshake.");
Console.WriteLine("WS: Info@receive: Start closing handshake.");
#endif
close(frame.PayloadData);
return null;
@@ -753,7 +817,7 @@ namespace WebSocketSharp
else
{// FINAL & (TEXT | BINARY)
#if DEBUG
Console.WriteLine("WS: Info@receive: Client starts closing handshake.");
Console.WriteLine("WS: Info@receive: Start closing handshake.");
#endif
close(CloseStatusCode.INCORRECT_DATA, String.Empty);
return null;
@@ -773,7 +837,7 @@ namespace WebSocketSharp
goto default;
case Opcode.CLOSE:
#if DEBUG
Console.WriteLine("WS: Info@receive: Server starts closing handshake.");
Console.WriteLine("WS: Info@receive: Start closing handshake.");
#endif
close(payloadData);
break;
@@ -796,6 +860,9 @@ namespace WebSocketSharp
private RequestHandshake receiveOpeningHandshake()
{
if (_context != null)
return RequestHandshake.Parse(_context);
return RequestHandshake.Parse(readHandshake());
}

View File

@@ -79,10 +79,40 @@
<Compile Include="RequestHandshake.cs" />
<Compile Include="ResponseHandshake.cs" />
<Compile Include="Handshake.cs" />
<Compile Include="Net\AuthenticationSchemeSelector.cs" />
<Compile Include="Net\AuthenticationSchemes.cs" />
<Compile Include="Net\ChunkStream.cs" />
<Compile Include="Net\ChunkedInputStream.cs" />
<Compile Include="Net\Cookie.cs" />
<Compile Include="Net\CookieCollection.cs" />
<Compile Include="Net\CookieException.cs" />
<Compile Include="Net\EndPointListener.cs" />
<Compile Include="Net\EndPointManager.cs" />
<Compile Include="Net\HttpConnection.cs" />
<Compile Include="Net\HttpListener.cs" />
<Compile Include="Net\HttpListenerContext.cs" />
<Compile Include="Net\HttpListenerException.cs" />
<Compile Include="Net\HttpListenerPrefixCollection.cs" />
<Compile Include="Net\HttpListenerRequest.cs" />
<Compile Include="Net\HttpListenerResponse.cs" />
<Compile Include="Net\HttpListenerWebSocketContext.cs" />
<Compile Include="Net\HttpStreamAsyncResult.cs" />
<Compile Include="Net\HttpUtility.cs" />
<Compile Include="Net\ListenerAsyncResult.cs" />
<Compile Include="Net\ListenerPrefix.cs" />
<Compile Include="Net\RequestStream.cs" />
<Compile Include="Net\ResponseStream.cs" />
<Compile Include="Net\WebHeaderCollection.cs" />
<Compile Include="Net\WebSocketContext.cs" />
<Compile Include="Server\HttpServer.cs" />
<Compile Include="Server\ResponseEventArgs.cs" />
<Compile Include="Net\HttpVersion.cs" />
<Compile Include="Net\HttpStatusCode.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<Folder Include="Frame\" />
<Folder Include="Server\" />
<Folder Include="Net\" />
</ItemGroup>
</Project>

Binary file not shown.