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