2004-06-06 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 using System.Collections;
11 using System.Globalization;
12 using System.IO;
13 using System.Text;
14
15 namespace System.Net
16 {
17         class ChunkStream
18         {
19                 enum State {
20                         None,
21                         Body,
22                         BodyFinished,
23                         Trailer
24                 }
25
26                 class Chunk {
27                         public byte [] Bytes;
28                         public int Offset;
29
30                         public Chunk (byte [] chunk)
31                         {
32                                 this.Bytes = chunk;
33                         }
34
35                         public int Read (byte [] buffer, int offset, int size)
36                         {
37                                 int nread = (size > Bytes.Length - Offset) ? Bytes.Length - Offset : size;
38                                 Buffer.BlockCopy (Bytes, Offset, buffer, offset, nread);
39                                 Offset += nread;
40                                 return nread;
41                         }
42                 }
43
44                 WebHeaderCollection headers;
45                 int chunkSize;
46                 int chunkRead;
47                 State state;
48                 //byte [] waitBuffer;
49                 StringBuilder saved;
50                 bool sawCR;
51                 bool gotit;
52                 ArrayList chunks;
53                 
54                 public ChunkStream (byte [] buffer, int offset, int size, WebHeaderCollection headers)
55                 {
56                         this.headers = headers;
57                         saved = new StringBuilder ();
58                         chunks = new ArrayList ();
59                         chunkSize = -1;
60                         Write (buffer, offset, size);
61                 }
62
63                 public void ResetBuffer ()
64                 {
65                         chunkSize = -1;
66                         chunkRead = 0;
67                         chunks.Clear ();
68                 }
69                 
70                 public void WriteAndReadBack (byte [] buffer, int offset, int size, ref int read)
71                 {
72                         Write (buffer, offset, offset+read);
73                         read = Read (buffer, offset, size);
74                 }
75
76                 public int Read (byte [] buffer, int offset, int size)
77                 {
78                         return ReadFromChunks (buffer, offset, size);
79                 }
80
81                 int ReadFromChunks (byte [] buffer, int offset, int size)
82                 {
83                         int count = chunks.Count;
84                         int nread = 0;
85                         for (int i = 0; i < count; i++) {
86                                 Chunk chunk = (Chunk) chunks [i];
87                                 if (chunk == null)
88                                         continue;
89
90                                 if (chunk.Offset == chunk.Bytes.Length) {
91                                         chunks [i] = null;
92                                         continue;
93                                 }
94                                 
95                                 nread += chunk.Read (buffer, offset + nread, size - nread);
96                                 if (nread == size)
97                                         break;
98                         }
99
100                         return nread;
101                 }
102                 
103                 public void Write (byte [] buffer, int offset, int size)
104                 {
105                         InternalWrite (buffer, ref offset, size);
106                 }
107                 
108                 void InternalWrite (byte [] buffer, ref int offset, int size)
109                 {
110                         if (state == State.None) {
111                                 state = GetChunkSize (buffer, ref offset, size);
112                                 if (state == State.None)
113                                         return;
114                                 
115                                 saved.Length = 0;
116                                 sawCR = false;
117                                 gotit = false;
118                         }
119                         
120                         if (state == State.Body && offset < size) {
121                                 state = ReadBody (buffer, ref offset, size);
122                                 if (state == State.Body)
123                                         return;
124                         }
125                         
126                         if (state == State.BodyFinished && offset < size) {
127                                 state = ReadCRLF (buffer, ref offset, size);
128                                 if (state == State.BodyFinished)
129                                         return;
130
131                                 sawCR = false;
132                         }
133                         
134                         if (state == State.Trailer && offset < size) {
135                                 state = ReadTrailer (buffer, ref offset, size);
136                                 if (state == State.Trailer)
137                                         return;
138
139                                 saved.Length = 0;
140                                 sawCR = false;
141                                 gotit = false;
142                         }
143
144                         if (offset < size)
145                                 InternalWrite (buffer, ref offset, size);
146                 }
147
148                 public bool WantMore {
149                         get { return (chunkRead != chunkSize || chunkSize != 0); }
150                 }
151
152                 public int ChunkLeft {
153                         get { return chunkSize - chunkRead; }
154                 }
155                 
156                 State ReadBody (byte [] buffer, ref int offset, int size)
157                 {
158                         if (chunkSize == 0)
159                                 return State.BodyFinished;
160
161                         int diff = size - offset;
162                         if (diff + chunkRead > chunkSize)
163                                 diff = chunkSize - chunkRead;
164
165                         byte [] chunk = new byte [diff];
166                         Buffer.BlockCopy (buffer, offset, chunk, 0, diff);
167                         chunks.Add (new Chunk (chunk));
168                         offset += diff;
169                         chunkRead += diff;
170                         return (chunkRead == chunkSize) ? State.BodyFinished : State.Body;
171                                 
172                 }
173                 
174                 State GetChunkSize (byte [] buffer, ref int offset, int size)
175                 {
176                         char c = '\0';
177                         while (offset < size) {
178                                 c = (char) buffer [offset++];
179                                 if (c == '\r') {
180                                         if (sawCR)
181                                                 throw new ProtocolViolationException ("2 CR found");
182
183                                         sawCR = true;
184                                         continue;
185                                 }
186                                 
187                                 if (sawCR && c == '\n')
188                                         break;
189
190                                 if (c == ' ')
191                                         gotit = true;
192
193                                 if (!gotit)
194                                         saved.Append (c);
195                         }
196
197                         if (!sawCR || c != '\n')
198                                 return State.None;
199
200                         chunkRead = 0;
201                         chunkSize = Int32.Parse (saved.ToString (), NumberStyles.HexNumber);
202                         if (chunkSize == 0)
203                                 return State.Trailer;
204
205                         return State.Body;
206                 }
207
208                 State ReadCRLF (byte [] buffer, ref int offset, int size)
209                 {
210                         if (!sawCR) {
211                                 if ((char) buffer [offset++] != '\r')
212                                         throw new ProtocolViolationException ("Expecting \\r");
213
214                                 sawCR = true;
215                                 if (offset == size)
216                                         return State.BodyFinished;
217                         }
218                         
219                         if ((char) buffer [offset++] != '\n')
220                                 throw new ProtocolViolationException ("Expecting \\n");
221
222                         return State.None;
223                 }
224
225                 State ReadTrailer (byte [] buffer, ref int offset, int size)
226                 {
227                         char c = '\0';
228
229                         // short path
230                         if ((char) buffer [offset] == '\r') {
231                                 offset++;
232                                 if ((char) buffer [offset] == '\n') {
233                                         offset++;
234                                         return State.None;
235                                 }
236                                 offset--;
237                         }
238                         
239                         int st = 0;
240                         string stString = "\r\n\r";
241                         while (offset < size && st < 4) {
242                                 c = (char) buffer [offset++];
243                                 if ((st == 0 || st == 2) && c == '\r') {
244                                         st++;
245                                         continue;
246                                 }
247
248                                 if ((st == 1 || st == 3) && c == '\n') {
249                                         st++;
250                                         continue;
251                                 }
252
253                                 if (st > 0) {
254                                         saved.Append (stString.Substring (0, st));
255                                         st = 0;
256                                 }
257                         }
258
259                         if (st < 4)
260                                 return State.Trailer;
261
262                         StringReader reader = new StringReader (saved.ToString ());
263                         string line;
264                         while ((line = reader.ReadLine ()) != null && line != "")
265                                 headers.Add (line);
266
267                         return State.None;
268                 }
269         }
270 }
271