| // | 
| // 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; | 
|         } | 
|     } | 
| } |