Fix due to the added HttpServer
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
35
websocket-sharp/Net/AuthenticationSchemeSelector.cs
Normal file
35
websocket-sharp/Net/AuthenticationSchemeSelector.cs
Normal 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);
|
||||
}
|
45
websocket-sharp/Net/AuthenticationSchemes.cs
Normal file
45
websocket-sharp/Net/AuthenticationSchemes.cs
Normal 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,
|
||||
}
|
||||
}
|
342
websocket-sharp/Net/ChunkStream.cs
Normal file
342
websocket-sharp/Net/ChunkStream.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
181
websocket-sharp/Net/ChunkedInputStream.cs
Normal file
181
websocket-sharp/Net/ChunkedInputStream.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
350
websocket-sharp/Net/Cookie.cs
Normal file
350
websocket-sharp/Net/Cookie.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
176
websocket-sharp/Net/CookieCollection.cs
Normal file
176
websocket-sharp/Net/CookieCollection.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
69
websocket-sharp/Net/CookieException.cs
Normal file
69
websocket-sharp/Net/CookieException.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
404
websocket-sharp/Net/EndPointListener.cs
Normal file
404
websocket-sharp/Net/EndPointListener.cs
Normal 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
|
||||
}
|
||||
}
|
148
websocket-sharp/Net/EndPointManager.cs
Normal file
148
websocket-sharp/Net/EndPointManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
522
websocket-sharp/Net/HttpConnection.cs
Normal file
522
websocket-sharp/Net/HttpConnection.cs
Normal 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
|
||||
}
|
||||
}
|
372
websocket-sharp/Net/HttpListener.cs
Normal file
372
websocket-sharp/Net/HttpListener.cs
Normal 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
|
||||
}
|
||||
}
|
179
websocket-sharp/Net/HttpListenerContext.cs
Normal file
179
websocket-sharp/Net/HttpListenerContext.cs
Normal 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
|
||||
}
|
||||
}
|
59
websocket-sharp/Net/HttpListenerException.cs
Normal file
59
websocket-sharp/Net/HttpListenerException.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
127
websocket-sharp/Net/HttpListenerPrefixCollection.cs
Normal file
127
websocket-sharp/Net/HttpListenerPrefixCollection.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
546
websocket-sharp/Net/HttpListenerRequest.cs
Normal file
546
websocket-sharp/Net/HttpListenerRequest.cs
Normal 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
|
||||
}
|
||||
}
|
510
websocket-sharp/Net/HttpListenerResponse.cs
Normal file
510
websocket-sharp/Net/HttpListenerResponse.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
101
websocket-sharp/Net/HttpListenerWebSocketContext.cs
Normal file
101
websocket-sharp/Net/HttpListenerWebSocketContext.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
84
websocket-sharp/Net/HttpStatusCode.cs
Normal file
84
websocket-sharp/Net/HttpStatusCode.cs
Normal 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,
|
||||
}
|
||||
}
|
98
websocket-sharp/Net/HttpStreamAsyncResult.cs
Normal file
98
websocket-sharp/Net/HttpStreamAsyncResult.cs
Normal 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 ();
|
||||
}
|
||||
}
|
||||
}
|
1116
websocket-sharp/Net/HttpUtility.cs
Normal file
1116
websocket-sharp/Net/HttpUtility.cs
Normal file
File diff suppressed because it is too large
Load Diff
42
websocket-sharp/Net/HttpVersion.cs
Normal file
42
websocket-sharp/Net/HttpVersion.cs
Normal 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 () {}
|
||||
}
|
||||
}
|
186
websocket-sharp/Net/ListenerAsyncResult.cs
Normal file
186
websocket-sharp/Net/ListenerAsyncResult.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
166
websocket-sharp/Net/ListenerPrefix.cs
Normal file
166
websocket-sharp/Net/ListenerPrefix.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
227
websocket-sharp/Net/RequestStream.cs
Normal file
227
websocket-sharp/Net/RequestStream.cs
Normal 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 ();
|
||||
}
|
||||
}
|
||||
}
|
243
websocket-sharp/Net/ResponseStream.cs
Normal file
243
websocket-sharp/Net/ResponseStream.cs
Normal 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 ();
|
||||
}
|
||||
}
|
||||
}
|
730
websocket-sharp/Net/WebHeaderCollection.cs
Normal file
730
websocket-sharp/Net/WebHeaderCollection.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
55
websocket-sharp/Net/WebSocketContext.cs
Normal file
55
websocket-sharp/Net/WebSocketContext.cs
Normal 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; }
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
|
187
websocket-sharp/Server/HttpServer.cs
Normal file
187
websocket-sharp/Server/HttpServer.cs
Normal 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
|
||||
}
|
||||
}
|
43
websocket-sharp/Server/ResponseEventArgs.cs
Normal file
43
websocket-sharp/Server/ResponseEventArgs.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
|
||||
|
@@ -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());
|
||||
}
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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.
Reference in New Issue
Block a user