diff options
Diffstat (limited to 'SocketHttpListener.Portable/Net/ChunkStream.cs')
| -rw-r--r-- | SocketHttpListener.Portable/Net/ChunkStream.cs | 371 |
1 files changed, 371 insertions, 0 deletions
diff --git a/SocketHttpListener.Portable/Net/ChunkStream.cs b/SocketHttpListener.Portable/Net/ChunkStream.cs new file mode 100644 index 000000000..3f3b4a667 --- /dev/null +++ b/SocketHttpListener.Portable/Net/ChunkStream.cs @@ -0,0 +1,371 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Net; +using System.Text; + +namespace SocketHttpListener.Net +{ + class ChunkStream + { + enum State + { + None, + PartialSize, + 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; + int totalWritten; + State state; + //byte [] waitBuffer; + StringBuilder saved; + bool sawCR; + bool gotit; + int trailerState; + List<Chunk> chunks; + + public ChunkStream(WebHeaderCollection headers) + { + this.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); + } + + 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 = (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); + } + + 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 = (Chunk)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; } + } + + 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; + + } + + 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; + } + + 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.UnknownError, null); + //WebException we = new WebException(message, null, WebExceptionStatus.ServerProtocolViolation, null); + throw we; + } + } +} |
