New test.
[mono.git] / mcs / class / System / System.Net / HttpConnection.cs
1 //
2 // System.Net.HttpConnection
3 //
4 // Author:
5 //      Gonzalo Paniagua Javier (gonzalo@novell.com)
6 //
7 // Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 #if SECURITY_DEP
30
31 using System.IO;
32 using System.Net.Sockets;
33 using System.Reflection;
34 using System.Text;
35 using System.Security.Cryptography;
36 using System.Security.Cryptography.X509Certificates;
37 using Mono.Security.Protocol.Tls;
38
39 namespace System.Net {
40         sealed class HttpConnection
41         {
42                 const int BufferSize = 8192;
43                 Socket sock;
44                 Stream stream;
45                 EndPointListener epl;
46                 MemoryStream ms;
47                 byte [] buffer;
48                 HttpListenerContext context;
49                 StringBuilder current_line;
50                 ListenerPrefix prefix;
51                 RequestStream i_stream;
52                 ResponseStream o_stream;
53                 bool chunked;
54                 int chunked_uses;
55                 bool context_bound;
56                 bool secure;
57                 AsymmetricAlgorithm key;
58
59                 public HttpConnection (Socket sock, EndPointListener epl, bool secure, X509Certificate2 cert, AsymmetricAlgorithm key)
60                 {
61                         this.sock = sock;
62                         this.epl = epl;
63                         this.secure = secure;
64                         this.key = key;
65                         if (secure == false) {
66                                 stream = new NetworkStream (sock, false);
67                         } else {
68                                 SslServerStream ssl_stream = new SslServerStream (new NetworkStream (sock, false), cert, false, false);
69                                 ssl_stream.PrivateKeyCertSelectionDelegate += OnPVKSelection;
70                                 stream = ssl_stream;
71                         }
72                         Init ();
73                 }
74
75                 AsymmetricAlgorithm OnPVKSelection (X509Certificate certificate, string targetHost)
76                 {
77                         return key;
78                 }
79
80                 void Init ()
81                 {
82                         context_bound = false;
83                         i_stream = null;
84                         o_stream = null;
85                         prefix = null;
86                         chunked = false;
87                         ms = new MemoryStream ();
88                         position = 0;
89                         input_state = InputState.RequestLine;
90                         line_state = LineState.None;
91                         context = new HttpListenerContext (this);
92                 }
93
94                 public int ChunkedUses {
95                         get { return chunked_uses; }
96                 }
97
98                 public IPEndPoint LocalEndPoint {
99                         get { return (IPEndPoint) sock.LocalEndPoint; }
100                 }
101
102                 public IPEndPoint RemoteEndPoint {
103                         get { return (IPEndPoint) sock.RemoteEndPoint; }
104                 }
105
106                 public bool IsSecure {
107                         get { return secure; }
108                 }
109
110                 public ListenerPrefix Prefix {
111                         get { return prefix; }
112                         set { prefix = value; }
113                 }
114
115                 public void BeginReadRequest ()
116                 {
117                         if (buffer == null)
118                                 buffer = new byte [BufferSize];
119                         try {
120                                 stream.BeginRead (buffer, 0, BufferSize, OnRead, this);
121                         } catch {
122                                 sock.Close (); // stream disposed
123                         }
124                 }
125
126                 public RequestStream GetRequestStream (bool chunked, long contentlength)
127                 {
128                         if (i_stream == null) {
129                                 byte [] buffer = ms.GetBuffer ();
130                                 int length = (int) ms.Length;
131                                 ms = null;
132                                 if (chunked) {
133                                         this.chunked = true;
134                                         context.Response.SendChunked = true;
135                                         i_stream = new ChunkedInputStream (context, stream, buffer, position, length - position);
136                                 } else {
137                                         i_stream = new RequestStream (stream, buffer, position, length - position, contentlength);
138                                 }
139                         }
140                         return i_stream;
141                 }
142
143                 public ResponseStream GetResponseStream ()
144                 {
145                         // TODO: can we get this stream before reading the input?
146                         if (o_stream == null) {
147                                 HttpListener listener = context.Listener;
148                                 bool ign = (listener == null) ? true : listener.IgnoreWriteExceptions;
149                                 o_stream = new ResponseStream (stream, context.Response, ign);
150                         }
151                         return o_stream;
152                 }
153
154                 void OnRead (IAsyncResult ares)
155                 {
156                         HttpConnection cnc = (HttpConnection) ares.AsyncState;
157                         int nread = -1;
158                         try {
159                                 nread = stream.EndRead (ares);
160                                 ms.Write (buffer, 0, nread);
161                                 if (ms.Length > 32768) {
162                                         SendError ("Bad request", 400);
163                                         Close (true);
164                                         return;
165                                 }
166                         } catch {
167                                 if (ms != null && ms.Length > 0)
168                                         SendError ();
169                                 if (sock != null)
170                                         sock.Close ();
171                                 return;
172                         }
173
174                         if (nread == 0) {
175                                 //if (ms.Length > 0)
176                                 //      SendError (); // Why bother?
177                                 sock.Close ();
178                                 return;
179                         }
180
181                         if (ProcessInput (ms)) {
182                                 if (!context.HaveError)
183                                         context.Request.FinishInitialization ();
184
185                                 if (context.HaveError) {
186                                         SendError ();
187                                         Close (true);
188                                         return;
189                                 }
190
191                                 if (!epl.BindContext (context)) {
192                                         SendError ("Invalid host", 400);
193                                         Close (true);
194                                 }
195                                 context_bound = true;
196                                 return;
197                         }
198                         stream.BeginRead (buffer, 0, BufferSize, OnRead, cnc);
199                 }
200
201                 enum InputState {
202                         RequestLine,
203                         Headers
204                 }
205
206                 enum LineState {
207                         None,
208                         CR,
209                         LF
210                 }
211
212                 InputState input_state = InputState.RequestLine;
213                 LineState line_state = LineState.None;
214                 int position;
215
216                 // true -> done processing
217                 // false -> need more input
218                 bool ProcessInput (MemoryStream ms)
219                 {
220                         byte [] buffer = ms.GetBuffer ();
221                         int len = (int) ms.Length;
222                         int used = 0;
223                         string line;
224
225                         try {
226                                 line = ReadLine (buffer, position, len - position, ref used);
227                                 position += used;
228                         } catch {
229                                 context.ErrorMessage = "Bad request";
230                                 context.ErrorStatus = 400;
231                                 return true;
232                         }
233
234                         do {
235                                 if (line == null)
236                                         break;
237                                 if (line == "") {
238                                         if (input_state == InputState.RequestLine)
239                                                 continue;
240                                         current_line = null;
241                                         ms = null;
242                                         return true;
243                                 }
244
245                                 if (input_state == InputState.RequestLine) {
246                                         context.Request.SetRequestLine (line);
247                                         input_state = InputState.Headers;
248                                 } else {
249                                         try {
250                                                 context.Request.AddHeader (line);
251                                         } catch (Exception e) {
252                                                 context.ErrorMessage = e.Message;
253                                                 context.ErrorStatus = 400;
254                                                 return true;
255                                         }
256                                 }
257
258                                 if (context.HaveError)
259                                         return true;
260
261                                 if (position >= len)
262                                         break;
263                                 try {
264                                         line = ReadLine (buffer, position, len - position, ref used);
265                                         position += used;
266                                 } catch {
267                                         context.ErrorMessage = "Bad request";
268                                         context.ErrorStatus = 400;
269                                         return true;
270                                 }
271                         } while (line != null);
272
273                         if (used == len) {
274                                 ms.SetLength (0);
275                                 position = 0;
276                         }
277                         return false;
278                 }
279
280                 string ReadLine (byte [] buffer, int offset, int len, ref int used)
281                 {
282                         if (current_line == null)
283                                 current_line = new StringBuilder ();
284                         int last = offset + len;
285                         used = 0;
286                         for (int i = offset; i < last && line_state != LineState.LF; i++) {
287                                 used++;
288                                 byte b = buffer [i];
289                                 if (b == 13) {
290                                         line_state = LineState.CR;
291                                 } else if (b == 10) {
292                                         line_state = LineState.LF;
293                                 } else {
294                                         current_line.Append ((char) b);
295                                 }
296                         }
297
298                         string result = null;
299                         if (line_state == LineState.LF) {
300                                 line_state = LineState.None;
301                                 result = current_line.ToString ();
302                                 current_line.Length = 0;
303                         }
304
305                         return result;
306                 }
307
308                 public void SendError (string msg, int status)
309                 {
310                         try {
311                                 HttpListenerResponse response = context.Response;
312                                 response.StatusCode = status;
313                                 response.ContentType = "text/html";
314                                 string description = HttpListenerResponse.GetStatusDescription (status);
315                                 string str;
316                                 if (msg != null)
317                                         str = String.Format ("<h1>{0} ({1})</h1>", description, msg);
318                                 else
319                                         str = String.Format ("<h1>{0}</h1>", description);
320
321                                 byte [] error = context.Response.ContentEncoding.GetBytes (str);
322                                 response.Close (error, false);
323                         } catch {
324                                 // response was already closed
325                         }
326                 }
327
328                 public void SendError ()
329                 {
330                         SendError (context.ErrorMessage, context.ErrorStatus);
331                 }
332
333                 void Unbind ()
334                 {
335                         if (context_bound) {
336                                 epl.UnbindContext (context);
337                                 context_bound = false;
338                         }
339                 }
340
341                 public void Close ()
342                 {
343                         Close (false);
344                 }
345
346                 internal void Close (bool force_close)
347                 {
348                         if (sock != null) {
349                                 Stream st = GetResponseStream ();
350                                 st.Close ();
351                                 o_stream = null;
352                         }
353
354                         if (sock != null) {
355                                 force_close |= (context.Request.Headers ["connection"] == "close");
356                                 if (!force_close) {
357                                         int status_code = context.Response.StatusCode;
358                                         bool conn_close = (status_code == 400 || status_code == 408 || status_code == 411 ||
359                                                         status_code == 413 || status_code == 414 || status_code == 500 ||
360                                                         status_code == 503);
361
362                                         force_close |= (context.Request.ProtocolVersion <= HttpVersion.Version10);
363                                 }
364
365                                 if (!force_close && context.Request.FlushInput ()) {
366                                         if (chunked && context.Response.ForceCloseChunked == false) {
367                                                 // Don't close. Keep working.
368                                                 chunked_uses++;
369                                                 Unbind ();
370                                                 Init ();
371                                                 BeginReadRequest ();
372                                                 return;
373                                         }
374
375                                         Unbind ();
376                                         Init ();
377                                         BeginReadRequest ();
378                                         return;
379                                 }
380
381                                 Socket s = sock;
382                                 sock = null;
383                                 try {
384                                         s.Shutdown (SocketShutdown.Both);
385                                 } catch {
386                                 } finally {
387                                         s.Close ();
388                                 }
389                                 Unbind ();
390                                 return;
391                         }
392                 }
393         }
394 }
395 #endif
396