Merge pull request #1991 from esdrubal/seq_test_fix
[mono.git] / mcs / class / System / System.Net / ChunkStream.cs
1 //
2 // System.Net.ChunkStream
3 //
4 // Authors:
5 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 //
7 // (C) 2003 Ximian, Inc (http://www.ximian.com)
8 //
9
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System.Collections;
32 using System.Collections.Generic;
33 using System.Globalization;
34 using System.IO;
35 using System.Text;
36
37 namespace System.Net
38 {
39         class ChunkStream
40         {
41                 enum State {
42                         None,
43                         PartialSize,
44                         Body,
45                         BodyFinished,
46                         Trailer
47                 }
48
49                 class Chunk {
50                         public byte [] Bytes;
51                         public int Offset;
52
53                         public Chunk (byte [] chunk)
54                         {
55                                 this.Bytes = chunk;
56                         }
57
58                         public int Read (byte [] buffer, int offset, int size)
59                         {
60                                 int nread = (size > Bytes.Length - Offset) ? Bytes.Length - Offset : size;
61                                 Buffer.BlockCopy (Bytes, Offset, buffer, offset, nread);
62                                 Offset += nread;
63                                 return nread;
64                         }
65                 }
66
67                 internal WebHeaderCollection headers;
68                 int chunkSize;
69                 int chunkRead;
70                 int totalWritten;
71                 State state;
72                 //byte [] waitBuffer;
73                 StringBuilder saved;
74                 bool sawCR;
75                 bool gotit;
76                 int trailerState;
77                 ArrayList chunks;
78                 
79                 public ChunkStream (byte [] buffer, int offset, int size, WebHeaderCollection headers)
80                                         : this (headers)
81                 {
82                         Write (buffer, offset, size);
83                 }
84
85                 public ChunkStream (WebHeaderCollection headers)
86                 {
87                         this.headers = headers;
88                         saved = new StringBuilder ();
89                         chunks = new ArrayList ();
90                         chunkSize = -1;
91                         totalWritten = 0;
92                 }
93
94                 public void ResetBuffer ()
95                 {
96                         chunkSize = -1;
97                         chunkRead = 0;
98                         totalWritten = 0;
99                         chunks.Clear ();
100                 }
101                 
102                 public void WriteAndReadBack (byte [] buffer, int offset, int size, ref int read)
103                 {
104                         if (offset + read > 0)
105                                 Write (buffer, offset, offset+read);
106                         read = Read (buffer, offset, size);
107                 }
108
109                 public int Read (byte [] buffer, int offset, int size)
110                 {
111                         return ReadFromChunks (buffer, offset, size);
112                 }
113
114                 int ReadFromChunks (byte [] buffer, int offset, int size)
115                 {
116                         int count = chunks.Count;
117                         int nread = 0;
118
119                         var chunksForRemoving = new List<Chunk>(count);
120                         for (int i = 0; i < count; i++) {
121                                 Chunk chunk = (Chunk) chunks [i];
122
123                                 if (chunk.Offset == chunk.Bytes.Length) {
124                                         chunksForRemoving.Add(chunk);
125                                         continue;
126                                 }
127                                 
128                                 nread += chunk.Read (buffer, offset + nread, size - nread);
129                                 if (nread == size)
130                                         break;
131                         }
132
133                         foreach (var chunk in chunksForRemoving)
134                                 chunks.Remove(chunk);
135
136                         return nread;
137                 }
138                 
139                 public void Write (byte [] buffer, int offset, int size)
140                 {
141                         if (offset < size)
142                                 InternalWrite (buffer, ref offset, size);
143                 }
144                 
145                 void InternalWrite (byte [] buffer, ref int offset, int size)
146                 {
147                         if (state == State.None || state == State.PartialSize) {
148                                 state = GetChunkSize (buffer, ref offset, size);
149                                 if (state == State.PartialSize)
150                                         return;
151                                 
152                                 saved.Length = 0;
153                                 sawCR = false;
154                                 gotit = false;
155                         }
156                         
157                         if (state == State.Body && offset < size) {
158                                 state = ReadBody (buffer, ref offset, size);
159                                 if (state == State.Body)
160                                         return;
161                         }
162                         
163                         if (state == State.BodyFinished && offset < size) {
164                                 state = ReadCRLF (buffer, ref offset, size);
165                                 if (state == State.BodyFinished)
166                                         return;
167
168                                 sawCR = false;
169                         }
170                         
171                         if (state == State.Trailer && offset < size) {
172                                 state = ReadTrailer (buffer, ref offset, size);
173                                 if (state == State.Trailer)
174                                         return;
175
176                                 saved.Length = 0;
177                                 sawCR = false;
178                                 gotit = false;
179                         }
180
181                         if (offset < size)
182                                 InternalWrite (buffer, ref offset, size);
183                 }
184
185                 public bool WantMore {
186                         get { return (chunkRead != chunkSize || chunkSize != 0 || state != State.None); }
187                 }
188
189                 public bool DataAvailable {
190                         get {
191                                 int count = chunks.Count;
192                                 for (int i = 0; i < count; i++) {
193                                         Chunk ch = (Chunk) chunks [i];
194                                         if (ch == null || ch.Bytes == null)
195                                                 continue;
196                                         if (ch.Bytes.Length > 0 && ch.Offset < ch.Bytes.Length)
197                                                 return (state != State.Body);
198                                 }
199                                 return false;
200                         }
201                 }
202
203                 public int TotalDataSize {
204                         get { return totalWritten; }
205                 }
206
207                 public int ChunkLeft {
208                         get { return chunkSize - chunkRead; }
209                 }
210                 
211                 State ReadBody (byte [] buffer, ref int offset, int size)
212                 {
213                         if (chunkSize == 0)
214                                 return State.BodyFinished;
215
216                         int diff = size - offset;
217                         if (diff + chunkRead > chunkSize)
218                                 diff = chunkSize - chunkRead;
219
220                         byte [] chunk = new byte [diff];
221                         Buffer.BlockCopy (buffer, offset, chunk, 0, diff);
222                         chunks.Add (new Chunk (chunk));
223                         offset += diff;
224                         chunkRead += diff;
225                         totalWritten += diff;
226                         return (chunkRead == chunkSize) ? State.BodyFinished : State.Body;
227                                 
228                 }
229                 
230                 State GetChunkSize (byte [] buffer, ref int offset, int size)
231                 {
232                         chunkRead = 0;
233                         chunkSize = 0;
234                         char c = '\0';
235                         while (offset < size) {
236                                 c = (char) buffer [offset++];
237                                 if (c == '\r') {
238                                         if (sawCR)
239                                                 ThrowProtocolViolation ("2 CR found");
240
241                                         sawCR = true;
242                                         continue;
243                                 }
244                                 
245                                 if (sawCR && c == '\n')
246                                         break;
247
248                                 if (c == ' ')
249                                         gotit = true;
250
251                                 if (!gotit)
252                                         saved.Append (c);
253
254                                 if (saved.Length > 20)
255                                         ThrowProtocolViolation ("chunk size too long.");
256                         }
257
258                         if (!sawCR || c != '\n') {
259                                 if (offset < size)
260                                         ThrowProtocolViolation ("Missing \\n");
261
262                                 try {
263                                         if (saved.Length > 0) {
264                                                 chunkSize = Int32.Parse (RemoveChunkExtension (saved.ToString ()), NumberStyles.HexNumber);
265                                         }
266                                 } catch (Exception) {
267                                         ThrowProtocolViolation ("Cannot parse chunk size.");
268                                 }
269
270                                 return State.PartialSize;
271                         }
272
273                         chunkRead = 0;
274                         try {
275                                 chunkSize = Int32.Parse (RemoveChunkExtension (saved.ToString ()), NumberStyles.HexNumber);
276                         } catch (Exception) {
277                                 ThrowProtocolViolation ("Cannot parse chunk size.");
278                         }
279
280                         if (chunkSize == 0) {
281                                 trailerState = 2;
282                                 return State.Trailer;
283                         }
284
285                         return State.Body;
286                 }
287
288                 static string RemoveChunkExtension (string input)
289                 {
290                         int idx = input.IndexOf (';');
291                         if (idx == -1)
292                                 return input;
293                         return input.Substring (0, idx);
294                 }
295
296                 State ReadCRLF (byte [] buffer, ref int offset, int size)
297                 {
298                         if (!sawCR) {
299                                 if ((char) buffer [offset++] != '\r')
300                                         ThrowProtocolViolation ("Expecting \\r");
301
302                                 sawCR = true;
303                                 if (offset == size)
304                                         return State.BodyFinished;
305                         }
306                         
307                         if (sawCR && (char) buffer [offset++] != '\n')
308                                 ThrowProtocolViolation ("Expecting \\n");
309
310                         return State.None;
311                 }
312
313                 State ReadTrailer (byte [] buffer, ref int offset, int size)
314                 {
315                         char c = '\0';
316
317                         // short path
318                         if (trailerState == 2 && (char) buffer [offset] == '\r' && saved.Length == 0) {
319                                 offset++;
320                                 if (offset < size && (char) buffer [offset] == '\n') {
321                                         offset++;
322                                         return State.None;
323                                 }
324                                 offset--;
325                         }
326                         
327                         int st = trailerState;
328                         string stString = "\r\n\r";
329                         while (offset < size && st < 4) {
330                                 c = (char) buffer [offset++];
331                                 if ((st == 0 || st == 2) && c == '\r') {
332                                         st++;
333                                         continue;
334                                 }
335
336                                 if ((st == 1 || st == 3) && c == '\n') {
337                                         st++;
338                                         continue;
339                                 }
340
341                                 if (st > 0) {
342                                         saved.Append (stString.Substring (0, saved.Length == 0? st-2: st));
343                                         st = 0;
344                                         if (saved.Length > 4196)
345                                                 ThrowProtocolViolation ("Error reading trailer (too long).");
346                                 }
347                         }
348
349                         if (st < 4) {
350                                 trailerState = st;
351                                 if (offset < size)
352                                         ThrowProtocolViolation ("Error reading trailer.");
353
354                                 return State.Trailer;
355                         }
356
357                         StringReader reader = new StringReader (saved.ToString ());
358                         string line;
359                         while ((line = reader.ReadLine ()) != null && line != "")
360                                 headers.Add (line);
361
362                         return State.None;
363                 }
364
365                 static void ThrowProtocolViolation (string message)
366                 {
367                         WebException we = new WebException (message, null, WebExceptionStatus.ServerProtocolViolation, null);
368                         throw we;
369                 }
370         }
371 }
372