diff options
Diffstat (limited to 'SocketHttpListener/Net/ChunkStream.cs')
| -rw-r--r-- | SocketHttpListener/Net/ChunkStream.cs | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/SocketHttpListener/Net/ChunkStream.cs b/SocketHttpListener/Net/ChunkStream.cs new file mode 100644 index 000000000..2de6c2c18 --- /dev/null +++ b/SocketHttpListener/Net/ChunkStream.cs @@ -0,0 +1,405 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Net; +using System.Text; + +namespace SocketHttpListener.Net +{ + // Licensed to the .NET Foundation under one or more agreements. + // See the LICENSE file in the project root for more information. + // + // 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. + // + + internal sealed class ChunkStream + { + private enum State + { + None, + PartialSize, + Body, + BodyFinished, + Trailer + } + + private class Chunk + { + public byte[] Bytes; + public int Offset; + + public Chunk(byte[] chunk) + { + 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; + private int _chunkSize; + private int _chunkRead; + private int _totalWritten; + private State _state; + private StringBuilder _saved; + private bool _sawCR; + private bool _gotit; + private int _trailerState; + private List<Chunk> _chunks; + + public ChunkStream(byte[] buffer, int offset, int size, WebHeaderCollection headers) + : this(headers) + { + Write(buffer, offset, size); + } + + public ChunkStream(WebHeaderCollection headers) + { + _headers = headers; + _saved = new StringBuilder(); + _chunks = new List<Chunk>(); + _chunkSize = -1; + _totalWritten = 0; + } + + public void ResetBuffer() + { + _chunkSize = -1; + _chunkRead = 0; + _totalWritten = 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); + } + + private int ReadFromChunks(byte[] buffer, int offset, int size) + { + int count = _chunks.Count; + int nread = 0; + + var chunksForRemoving = new List<Chunk>(count); + for (int i = 0; i < count; i++) + { + Chunk chunk = _chunks[i]; + + if (chunk.Offset == chunk.Bytes.Length) + { + chunksForRemoving.Add(chunk); + continue; + } + + nread += chunk.Read(buffer, offset + nread, size - nread); + if (nread == size) + break; + } + + foreach (var chunk in chunksForRemoving) + _chunks.Remove(chunk); + + return nread; + } + + public void Write(byte[] buffer, int offset, int size) + { + if (offset < size) + InternalWrite(buffer, ref offset, size); + } + + private void InternalWrite(byte[] buffer, ref int offset, int size) + { + if (_state == State.None || _state == State.PartialSize) + { + _state = GetChunkSize(buffer, ref offset, size); + if (_state == State.PartialSize) + 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 bool DataAvailable + { + get + { + int count = _chunks.Count; + for (int i = 0; i < count; i++) + { + Chunk ch = _chunks[i]; + if (ch == null || ch.Bytes == null) + continue; + if (ch.Bytes.Length > 0 && ch.Offset < ch.Bytes.Length) + return (_state != State.Body); + } + return false; + } + } + + public int TotalDataSize + { + get { return _totalWritten; } + } + + public int ChunkLeft + { + get { return _chunkSize - _chunkRead; } + } + + private 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; + _totalWritten += diff; + + return (_chunkRead == _chunkSize) ? State.BodyFinished : State.Body; + } + + private State GetChunkSize(byte[] buffer, ref int offset, int size) + { + _chunkRead = 0; + _chunkSize = 0; + 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.PartialSize; + } + + _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; + } + + private static string RemoveChunkExtension(string input) + { + int idx = input.IndexOf(';'); + if (idx == -1) + return input; + return input.Substring(0, idx); + } + + private 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; + } + + private 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; + } + + private static void ThrowProtocolViolation(string message) + { + WebException we = new WebException(message, null, WebExceptionStatus.ServerProtocolViolation, null); + throw we; + } + } +} |
