[runtime] Fix test_op_il_seq_point in amd64.
[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.Globalization;
33 using System.IO;
34 using System.Text;
35
36 namespace System.Net
37 {
38         class ChunkStream
39         {
40                 enum State {
41                         None,
42                         PartialSize,
43                         Body,
44                         BodyFinished,
45                         Trailer
46                 }
47
48                 class Chunk {
49                         public byte [] Bytes;
50                         public int Offset;
51
52                         public Chunk (byte [] chunk)
53                         {
54                                 this.Bytes = chunk;
55                         }
56
57                         public int Read (byte [] buffer, int offset, int size)
58                         {
59                                 int nread = (size > Bytes.Length - Offset) ? Bytes.Length - Offset : size;
60                                 Buffer.BlockCopy (Bytes, Offset, buffer, offset, nread);
61                                 Offset += nread;
62                                 return nread;
63                         }
64                 }
65
66                 internal WebHeaderCollection headers;
67                 int chunkSize;
68                 int chunkRead;
69                 int totalWritten;
70                 State state;
71                 //byte [] waitBuffer;
72                 StringBuilder saved;
73                 bool sawCR;
74                 bool gotit;
75                 int trailerState;
76                 ArrayList chunks;
77                 
78                 public ChunkStream (byte [] buffer, int offset, int size, WebHeaderCollection headers)
79                                         : this (headers)
80                 {
81                         Write (buffer, offset, size);
82                 }
83
84                 public ChunkStream (WebHeaderCollection headers)
85                 {
86                         this.headers = headers;
87                         saved = new StringBuilder ();
88                         chunks = new ArrayList ();
89                         chunkSize = -1;
90                         totalWritten = 0;
91                 }
92
93                 public void ResetBuffer ()
94                 {
95                         chunkSize = -1;
96                         chunkRead = 0;
97                         totalWritten = 0;
98                         chunks.Clear ();
99                 }
100                 
101                 public void WriteAndReadBack (byte [] buffer, int offset, int size, ref int read)
102                 {
103                         if (offset + read > 0)
104                                 Write (buffer, offset, offset+read);
105                         read = Read (buffer, offset, size);
106                 }
107
108                 public int Read (byte [] buffer, int offset, int size)
109                 {
110                         return ReadFromChunks (buffer, offset, size);
111                 }
112
113                 int ReadFromChunks (byte [] buffer, int offset, int size)
114                 {
115                         int count = chunks.Count;
116                         int nread = 0;
117                         for (int i = 0; i < count; i++) {
118                                 Chunk chunk = (Chunk) chunks [i];
119                                 if (chunk == null)
120                                         continue;
121
122                                 if (chunk.Offset == chunk.Bytes.Length) {
123                                         chunks [i] = null;
124                                         continue;
125                                 }
126                                 
127                                 nread += chunk.Read (buffer, offset + nread, size - nread);
128                                 if (nread == size)
129                                         break;
130                         }
131
132                         return nread;
133                 }
134                 
135                 public void Write (byte [] buffer, int offset, int size)
136                 {
137                         if (offset < size)
138                                 InternalWrite (buffer, ref offset, size);
139                 }
140                 
141                 void InternalWrite (byte [] buffer, ref int offset, int size)
142                 {
143                         if (state == State.None || state == State.PartialSize) {
144                                 state = GetChunkSize (buffer, ref offset, size);
145                                 if (state == State.PartialSize)
146                                         return;
147                                 
148                                 saved.Length = 0;
149                                 sawCR = false;
150                                 gotit = false;
151                         }
152                         
153                         if (state == State.Body && offset < size) {
154                                 state = ReadBody (buffer, ref offset, size);
155                                 if (state == State.Body)
156                                         return;
157                         }
158                         
159                         if (state == State.BodyFinished && offset < size) {
160                                 state = ReadCRLF (buffer, ref offset, size);
161                                 if (state == State.BodyFinished)
162                                         return;
163
164                                 sawCR = false;
165                         }
166                         
167                         if (state == State.Trailer && offset < size) {
168                                 state = ReadTrailer (buffer, ref offset, size);
169                                 if (state == State.Trailer)
170                                         return;
171
172                                 saved.Length = 0;
173                                 sawCR = false;
174                                 gotit = false;
175                         }
176
177                         if (offset < size)
178                                 InternalWrite (buffer, ref offset, size);
179                 }
180
181                 public bool WantMore {
182                         get { return (chunkRead != chunkSize || chunkSize != 0 || state != State.None); }
183                 }
184
185                 public bool DataAvailable {
186                         get {
187                                 int count = chunks.Count;
188                                 for (int i = 0; i < count; i++) {
189                                         Chunk ch = (Chunk) chunks [i];
190                                         if (ch == null || ch.Bytes == null)
191                                                 continue;
192                                         if (ch.Bytes.Length > 0 && ch.Offset < ch.Bytes.Length)
193                                                 return (state != State.Body);
194                                 }
195                                 return false;
196                         }
197                 }
198
199                 public int TotalDataSize {
200                         get { return totalWritten; }
201                 }
202
203                 public int ChunkLeft {
204                         get { return chunkSize - chunkRead; }
205                 }
206                 
207                 State ReadBody (byte [] buffer, ref int offset, int size)
208                 {
209                         if (chunkSize == 0)
210                                 return State.BodyFinished;
211
212                         int diff = size - offset;
213                         if (diff + chunkRead > chunkSize)
214                                 diff = chunkSize - chunkRead;
215
216                         byte [] chunk = new byte [diff];
217                         Buffer.BlockCopy (buffer, offset, chunk, 0, diff);
218                         chunks.Add (new Chunk (chunk));
219                         offset += diff;
220                         chunkRead += diff;
221                         totalWritten += diff;
222                         return (chunkRead == chunkSize) ? State.BodyFinished : State.Body;
223                                 
224                 }
225                 
226                 State GetChunkSize (byte [] buffer, ref int offset, int size)
227                 {
228                         chunkRead = 0;
229                         chunkSize = 0;
230                         char c = '\0';
231                         while (offset < size) {
232                                 c = (char) buffer [offset++];
233                                 if (c == '\r') {
234                                         if (sawCR)
235                                                 ThrowProtocolViolation ("2 CR found");
236
237                                         sawCR = true;
238                                         continue;
239                                 }
240                                 
241                                 if (sawCR && c == '\n')
242                                         break;
243
244                                 if (c == ' ')
245                                         gotit = true;
246
247                                 if (!gotit)
248                                         saved.Append (c);
249
250                                 if (saved.Length > 20)
251                                         ThrowProtocolViolation ("chunk size too long.");
252                         }
253
254                         if (!sawCR || c != '\n') {
255                                 if (offset < size)
256                                         ThrowProtocolViolation ("Missing \\n");
257
258                                 try {
259                                         if (saved.Length > 0) {
260                                                 chunkSize = Int32.Parse (RemoveChunkExtension (saved.ToString ()), NumberStyles.HexNumber);
261                                         }
262                                 } catch (Exception) {
263                                         ThrowProtocolViolation ("Cannot parse chunk size.");
264                                 }
265
266                                 return State.PartialSize;
267                         }
268
269                         chunkRead = 0;
270                         try {
271                                 chunkSize = Int32.Parse (RemoveChunkExtension (saved.ToString ()), NumberStyles.HexNumber);
272                         } catch (Exception) {
273                                 ThrowProtocolViolation ("Cannot parse chunk size.");
274                         }
275
276                         if (chunkSize == 0) {
277                                 trailerState = 2;
278                                 return State.Trailer;
279                         }
280
281                         return State.Body;
282                 }
283
284                 static string RemoveChunkExtension (string input)
285                 {
286                         int idx = input.IndexOf (';');
287                         if (idx == -1)
288                                 return input;
289                         return input.Substring (0, idx);
290                 }
291
292                 State ReadCRLF (byte [] buffer, ref int offset, int size)
293                 {
294                         if (!sawCR) {
295                                 if ((char) buffer [offset++] != '\r')
296                                         ThrowProtocolViolation ("Expecting \\r");
297
298                                 sawCR = true;
299                                 if (offset == size)
300                                         return State.BodyFinished;
301                         }
302                         
303                         if (sawCR && (char) buffer [offset++] != '\n')
304                                 ThrowProtocolViolation ("Expecting \\n");
305
306                         return State.None;
307                 }
308
309                 State ReadTrailer (byte [] buffer, ref int offset, int size)
310                 {
311                         char c = '\0';
312
313                         // short path
314                         if (trailerState == 2 && (char) buffer [offset] == '\r' && saved.Length == 0) {
315                                 offset++;
316                                 if (offset < size && (char) buffer [offset] == '\n') {
317                                         offset++;
318                                         return State.None;
319                                 }
320                                 offset--;
321                         }
322                         
323                         int st = trailerState;
324                         string stString = "\r\n\r";
325                         while (offset < size && st < 4) {
326                                 c = (char) buffer [offset++];
327                                 if ((st == 0 || st == 2) && c == '\r') {
328                                         st++;
329                                         continue;
330                                 }
331
332                                 if ((st == 1 || st == 3) && c == '\n') {
333                                         st++;
334                                         continue;
335                                 }
336
337                                 if (st > 0) {
338                                         saved.Append (stString.Substring (0, saved.Length == 0? st-2: st));
339                                         st = 0;
340                                         if (saved.Length > 4196)
341                                                 ThrowProtocolViolation ("Error reading trailer (too long).");
342                                 }
343                         }
344
345                         if (st < 4) {
346                                 trailerState = st;
347                                 if (offset < size)
348                                         ThrowProtocolViolation ("Error reading trailer.");
349
350                                 return State.Trailer;
351                         }
352
353                         StringReader reader = new StringReader (saved.ToString ());
354                         string line;
355                         while ((line = reader.ReadLine ()) != null && line != "")
356                                 headers.Add (line);
357
358                         return State.None;
359                 }
360
361                 static void ThrowProtocolViolation (string message)
362                 {
363                         WebException we = new WebException (message, null, WebExceptionStatus.ServerProtocolViolation, null);
364                         throw we;
365                 }
366         }
367 }
368