From 2a1f706051c97cb8bf1988e629bca590b9b13500 Mon Sep 17 00:00:00 2001 From: sta Date: Sat, 17 May 2014 20:52:34 +0900 Subject: [PATCH] Refactored ChunkStream.cs --- websocket-sharp/Net/Chunk.cs | 97 ++++ websocket-sharp/Net/ChunkStream.cs | 720 ++++++++++++------------- websocket-sharp/Net/InputChunkState.cs | 51 ++ websocket-sharp/websocket-sharp.csproj | 2 + 4 files changed, 509 insertions(+), 361 deletions(-) create mode 100644 websocket-sharp/Net/Chunk.cs create mode 100644 websocket-sharp/Net/InputChunkState.cs diff --git a/websocket-sharp/Net/Chunk.cs b/websocket-sharp/Net/Chunk.cs new file mode 100644 index 00000000..73573dbf --- /dev/null +++ b/websocket-sharp/Net/Chunk.cs @@ -0,0 +1,97 @@ +#region License +/* + * Chunk.cs + * + * This code is derived from System.Net.ChunkStream.cs of Mono + * (http://www.mono-project.com). + * + * The MIT License + * + * Copyright (c) 2003 Ximian, Inc (http://www.ximian.com) + * Copyright (c) 2014 sta.blockhead + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#endregion + +#region Authors +/* + * Authors: + * - Gonzalo Paniagua Javier + */ +#endregion + +using System; + +namespace WebSocketSharp.Net +{ + internal class Chunk + { + #region Private Fields + + private byte [] _data; + private int _offset; + + #endregion + + #region Public Constructors + + public Chunk (byte [] data) + { + _data = data; + } + + #endregion + + #region Public Properties + + public int Length { + get { + return _data.Length; + } + } + + public int Offset { + get { + return _offset; + } + } + + #endregion + + #region Public Methods + + public int Read (byte [] buffer, int offset, int size) + { + var left = _data.Length - _offset; + if (left == 0) + return left; + + if (size > left) + size = left; + + Buffer.BlockCopy (_data, _offset, buffer, offset, size); + _offset += size; + + return size; + } + + #endregion + } +} diff --git a/websocket-sharp/Net/ChunkStream.cs b/websocket-sharp/Net/ChunkStream.cs index 25cf7ad7..68c3a830 100644 --- a/websocket-sharp/Net/ChunkStream.cs +++ b/websocket-sharp/Net/ChunkStream.cs @@ -1,31 +1,41 @@ -// -// ChunkStream.cs -// Copied from System.Net.ChunkStream.cs -// -// 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. -// +#region License +/* + * ChunkStream.cs + * + * This code is derived from System.Net.ChunkStream.cs of Mono + * (http://www.mono-project.com). + * + * The MIT License + * + * Copyright (c) 2003 Ximian, Inc (http://www.ximian.com) + * Copyright (c) 2012-2014 sta.blockhead + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#endregion + +#region Authors +/* + * Authors: + * - Gonzalo Paniagua Javier + */ +#endregion using System; using System.Collections.Generic; @@ -34,337 +44,325 @@ 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; - } - } - - #region Private Fields - - int chunkRead; - List chunks; - int chunkSize; - bool gotit; - StringBuilder saved; - bool sawCR; - State state; - int trailerState; - - #endregion - - #region Internal Fields - - internal WebHeaderCollection headers; - - #endregion - - #region Constructors - - 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 List (); - chunkSize = -1; - } - - #endregion - - #region Properties - - public int ChunkLeft { - get { return chunkSize - chunkRead; } - } - - public bool WantMore { - get { return (chunkRead != chunkSize || chunkSize != 0 || state != State.None); } - } - - #endregion - - #region Private Methods - - 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; - } - - 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); - } - - 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 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; - } - - int ReadFromChunks (byte [] buffer, int offset, int size) - { - int count = chunks.Count; - int nread = 0; - for (int i = 0; i < count; i++) { - var 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; - } - - 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; - } - - var reader = new StringReader (saved.ToString ()); - string line; - while ((line = reader.ReadLine ()) != null && line != "") - headers.Add (line); - - return State.None; - } - - static string RemoveChunkExtension (string input) - { - int idx = input.IndexOf (';'); - if (idx == -1) - return input; - - return input.Substring (0, idx); - } - - static void ThrowProtocolViolation (string message) - { - var we = new WebException (message, null, WebExceptionStatus.ServerProtocolViolation, null); - throw we; - } - - #endregion - - #region Public Methods - - public int Read (byte [] buffer, int offset, int size) - { - return ReadFromChunks (buffer, offset, size); - } - - public void ResetBuffer () - { - chunkSize = -1; - chunkRead = 0; - chunks.Clear (); - } - - public void Write (byte [] buffer, int offset, int size) - { - InternalWrite (buffer, ref offset, size); - } - - 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); - } - - #endregion - } +namespace WebSocketSharp.Net +{ + internal class ChunkStream + { + #region Private Fields + + private int _chunkRead; + private int _chunkSize; + private List _chunks; + private bool _gotit; + private WebHeaderCollection _headers; + private StringBuilder _saved; + private bool _sawCR; + private InputChunkState _state; + private int _trailerState; + + #endregion + + #region Public Constructors + + public ChunkStream (byte [] buffer, int offset, int size, WebHeaderCollection headers) + : this (headers) + { + Write (buffer, offset, size); + } + + public ChunkStream (WebHeaderCollection headers) + { + _headers = headers; + _chunkSize = -1; + _chunks = new List (); + _saved = new StringBuilder (); + } + + #endregion + + #region Internal Properties + + internal WebHeaderCollection Headers { + get { + return _headers; + } + } + + #endregion + + #region Public Properties + + public int ChunkLeft { + get { + return _chunkSize - _chunkRead; + } + } + + public bool WantMore { + get { + return _chunkRead != _chunkSize || _chunkSize != 0 || _state != InputChunkState.None; + } + } + + #endregion + + #region Private Methods + + private InputChunkState readCRLF (byte [] buffer, ref int offset, int size) + { + if (!_sawCR) { + if ((char) buffer [offset++] != '\r') + throwProtocolViolation ("Expecting \\r."); + + _sawCR = true; + if (offset == size) + return InputChunkState.BodyFinished; + } + + if ((char) buffer [offset++] != '\n') + throwProtocolViolation ("Expecting \\n."); + + return InputChunkState.None; + } + + private int readFromChunks (byte [] buffer, int offset, int size) + { + var count = _chunks.Count; + var nread = 0; + for (int i = 0; i < count; i++) { + var chunk = _chunks [i]; + if (chunk == null) + continue; + + if (chunk.Offset == chunk.Length) { + _chunks [i] = null; + continue; + } + + nread += chunk.Read (buffer, offset + nread, size - nread); + if (nread == size) + break; + } + + return nread; + } + + private InputChunkState readTrailer (byte [] buffer, ref int offset, int size) + { + var c = '\0'; + + // Short path + if (_trailerState == 2 && (char) buffer [offset] == '\r' && _saved.Length == 0) { + offset++; + if (offset < size && (char) buffer [offset] == '\n') { + offset++; + return InputChunkState.None; + } + + offset--; + } + + var st = _trailerState; + var 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 InputChunkState.Trailer; + } + + var reader = new StringReader (_saved.ToString ()); + string line; + while ((line = reader.ReadLine ()) != null && line != "") + _headers.Add (line); + + return InputChunkState.None; + } + + private static string removeChunkExtension (string input) + { + var idx = input.IndexOf (';'); + return idx > -1 + ? input.Substring (0, idx) + : input; + } + + private InputChunkState setChunkSize (byte [] buffer, ref int offset, int size) + { + var 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 { + throwProtocolViolation ("Cannot parse chunk size."); + } + + return InputChunkState.None; + } + + _chunkRead = 0; + try { + _chunkSize = Int32.Parse ( + removeChunkExtension (_saved.ToString ()), NumberStyles.HexNumber); + } + catch { + throwProtocolViolation ("Cannot parse chunk size."); + } + + if (_chunkSize == 0) { + _trailerState = 2; + return InputChunkState.Trailer; + } + + return InputChunkState.Body; + } + + private static void throwProtocolViolation (string message) + { + var ex = new WebException (message, null, WebExceptionStatus.ServerProtocolViolation, null); + throw ex; + } + + private void write (byte [] buffer, ref int offset, int size) + { + if (_state == InputChunkState.None) { + _state = setChunkSize (buffer, ref offset, size); + if (_state == InputChunkState.None) + return; + + _saved.Length = 0; + _sawCR = false; + _gotit = false; + } + + if (_state == InputChunkState.Body && offset < size) { + _state = writeBody (buffer, ref offset, size); + if (_state == InputChunkState.Body) + return; + } + + if (_state == InputChunkState.BodyFinished && offset < size) { + _state = readCRLF (buffer, ref offset, size); + if (_state == InputChunkState.BodyFinished) + return; + + _sawCR = false; + } + + if (_state == InputChunkState.Trailer && offset < size) { + _state = readTrailer (buffer, ref offset, size); + if (_state == InputChunkState.Trailer) + return; + + _saved.Length = 0; + _sawCR = false; + _gotit = false; + } + + if (offset < size) + write (buffer, ref offset, size); + } + + private InputChunkState writeBody (byte [] buffer, ref int offset, int size) + { + if (_chunkSize == 0) + return InputChunkState.BodyFinished; + + var diff = size - offset; + if (diff + _chunkRead > _chunkSize) + diff = _chunkSize - _chunkRead; + + var body = new byte [diff]; + Buffer.BlockCopy (buffer, offset, body, 0, diff); + _chunks.Add (new Chunk (body)); + + offset += diff; + _chunkRead += diff; + + return _chunkRead == _chunkSize + ? InputChunkState.BodyFinished + : InputChunkState.Body; + } + + #endregion + + #region Public Methods + + public int Read (byte [] buffer, int offset, int size) + { + return readFromChunks (buffer, offset, size); + } + + public void ResetBuffer () + { + _chunkSize = -1; + _chunkRead = 0; + _chunks.Clear (); + } + + public void Write (byte [] buffer, int offset, int size) + { + write (buffer, ref offset, size); + } + + public void WriteAndReadBack (byte [] buffer, int offset, int size, ref int read) + { + if (offset + read > 0) + Write (buffer, offset, offset + read); + + read = readFromChunks (buffer, offset, size); + } + + #endregion + } } diff --git a/websocket-sharp/Net/InputChunkState.cs b/websocket-sharp/Net/InputChunkState.cs new file mode 100644 index 00000000..04f84df6 --- /dev/null +++ b/websocket-sharp/Net/InputChunkState.cs @@ -0,0 +1,51 @@ +#region License +/* + * InputChunkState.cs + * + * This code is derived from System.Net.ChunkStream.cs of Mono + * (http://www.mono-project.com). + * + * The MIT License + * + * Copyright (c) 2003 Ximian, Inc (http://www.ximian.com) + * Copyright (c) 2014 sta.blockhead + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#endregion + +#region Authors +/* + * Authors: + * - Gonzalo Paniagua Javier + */ +#endregion + +using System; + +namespace WebSocketSharp.Net +{ + internal enum InputChunkState + { + None, + Body, + BodyFinished, + Trailer + } +} diff --git a/websocket-sharp/websocket-sharp.csproj b/websocket-sharp/websocket-sharp.csproj index 254816b1..664eb1dc 100644 --- a/websocket-sharp/websocket-sharp.csproj +++ b/websocket-sharp/websocket-sharp.csproj @@ -132,6 +132,8 @@ + +