* Utilities.cs: New.
[mono.git] / mcs / class / System.Runtime.Remoting / MonoHttp / HttpConnection.cs
1 #define EMBEDDED_IN_1_0
2
3 //
4 // System.Net.HttpConnection
5 //
6 // Author:
7 //      Gonzalo Paniagua Javier (gonzalo@novell.com)
8 //
9 // Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
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 #if EMBEDDED_IN_1_0
32
33 using System.IO;
34 using System.Net.Sockets;
35 using System.Reflection;
36 using System.Text;
37 using System.Security.Cryptography;
38 using System.Security.Cryptography.X509Certificates;
39
40 #if !EMBEDDED_IN_1_0
41 using Mono.Security.Protocol.Tls;
42 #endif
43
44 using System; using System.Net; namespace MonoHttp {
45         
46         interface IHttpListenerContextBinder {
47                 bool BindContext (HttpListenerContext context);
48                 void UnbindContext (HttpListenerContext context);
49         }
50         
51         sealed class HttpConnection
52         {
53                 const int BufferSize = 8192;
54                 Socket sock;
55                 Stream stream;
56                 IHttpListenerContextBinder epl;
57                 MemoryStream ms;
58                 byte [] buffer;
59                 HttpListenerContext context;
60                 StringBuilder current_line;
61                 ListenerPrefix prefix;
62                 RequestStream i_stream;
63                 ResponseStream o_stream;
64                 bool chunked;
65                 int chunked_uses;
66                 bool context_bound;
67
68 #if EMBEDDED_IN_1_0
69                 public HttpConnection (Socket sock, IHttpListenerContextBinder epl)
70                 {
71                         this.sock = sock;
72                         this.epl = epl;
73                         stream = new NetworkStream (sock, false);
74                         Init ();
75                 }
76 #else
77                 public HttpConnection (Socket sock, IHttpListenerContextBinder epl, bool secure, X509Certificate2 cert, AsymmetricAlgorithm key)
78                 {
79                         this.sock = sock;
80                         this.epl = epl;
81                         this.secure = secure;
82                         this.key = key;
83                         if (secure == false) {
84                                 stream = new NetworkStream (sock, false);
85                         } else {
86                                 SslServerStream ssl_stream = new SslServerStream (new NetworkStream (sock, false), cert, false, false);
87                                 ssl_stream.PrivateKeyCertSelectionDelegate += OnPVKSelection;
88                                 stream = ssl_stream;
89
90                         }
91                         Init ();
92                 }
93 #endif
94
95                 void Init ()
96                 {
97                         context_bound = false;
98                         i_stream = null;
99                         o_stream = null;
100                         prefix = null;
101                         chunked = false;
102                         ms = new MemoryStream ();
103                         position = 0;
104                         input_state = InputState.RequestLine;
105                         line_state = LineState.None;
106                         context = new HttpListenerContext (this);
107                 }
108
109                 public int ChunkedUses {
110                         get { return chunked_uses; }
111                 }
112
113                 public IPEndPoint LocalEndPoint {
114                         get { return (IPEndPoint) sock.LocalEndPoint; }
115                 }
116
117                 public IPEndPoint RemoteEndPoint {
118                         get { return (IPEndPoint) sock.RemoteEndPoint; }
119                 }
120
121                 public bool IsSecure {
122                         get { return false; }
123                 }
124
125                 public ListenerPrefix Prefix {
126                         get { return prefix; }
127                         set { prefix = value; }
128                 }
129
130                 public void BeginReadRequest ()
131                 {
132                         if (buffer == null)
133                                 buffer = new byte [BufferSize];
134                         try {
135                                 stream.BeginRead (buffer, 0, BufferSize, OnRead, this);
136                         } catch {
137                                 sock.Close (); // stream disposed
138                         }
139                 }
140
141                 public RequestStream GetRequestStream (bool chunked, long contentlength)
142                 {
143                         if (i_stream == null) {
144                                 byte [] buffer = ms.GetBuffer ();
145                                 int length = (int) ms.Length;
146                                 ms = null;
147                                 if (chunked) {
148                                         this.chunked = true;
149                                         context.Response.SendChunked = true;
150                                         i_stream = new ChunkedInputStream (context, stream, buffer, position, length - position);
151                                 } else {
152                                         i_stream = new RequestStream (stream, buffer, position, length - position, contentlength);
153                                 }
154                         }
155                         return i_stream;
156                 }
157
158                 public ResponseStream GetResponseStream ()
159                 {
160                         // TODO: can we get this stream before reading the input?
161                         if (o_stream == null) {
162                                 bool ign = false;// ? true : listener.IgnoreWriteExceptions;
163                                 o_stream = new ResponseStream (stream, context.Response, ign);
164                         }
165                         return o_stream;
166                 }
167
168                 void OnRead (IAsyncResult ares)
169                 {
170                         // TODO: set a limit on ms length.
171                         HttpConnection cnc = (HttpConnection) ares.AsyncState;
172                         int nread = -1;
173                         try {
174                                 nread = stream.EndRead (ares);
175                                 ms.Write (buffer, 0, nread);
176                         } catch (Exception) {
177                                 //Console.WriteLine (e);
178                                 if (ms.Length > 0)
179                                         SendError ();
180                                 sock.Close ();
181                                 return;
182                         }
183
184                         if (nread == 0) {
185                                 //if (ms.Length > 0)
186                                 //      SendError (); // Why bother?
187                                 sock.Close ();
188                                 return;
189                         }
190
191                         if (ProcessInput (ms)) {
192                                 if (!context.HaveError)
193                                         context.Request.FinishInitialization ();
194
195                                 if (context.HaveError) {
196                                         SendError ();
197                                         Close ();
198                                         return;
199                                 }
200
201                                 if (!epl.BindContext (context)) {
202                                         SendError ("Invalid host", 400);
203                                         Close ();
204                                 }
205                                 context_bound = true;
206                                 return;
207                         }
208                         stream.BeginRead (buffer, 0, BufferSize, OnRead, cnc);
209                 }
210
211                 enum InputState {
212                         RequestLine,
213                         Headers
214                 }
215
216                 enum LineState {
217                         None,
218                         CR,
219                         LF
220                 }
221
222                 InputState input_state = InputState.RequestLine;
223                 LineState line_state = LineState.None;
224                 int position;
225
226                 // true -> done processing
227                 // false -> need more input
228                 bool ProcessInput (MemoryStream ms)
229                 {
230                         byte [] buffer = ms.GetBuffer ();
231                         int len = (int) ms.Length;
232                         int used = 0;
233                         string line;
234                         while ((line = ReadLine (buffer, position, len - position, ref used)) != null) {
235                                 position += used;
236                                 if (line == "") {
237                                         if (input_state == InputState.RequestLine)
238                                                 continue;
239                                         current_line = null;
240                                         ms = null;
241                                         return true;
242                                 }
243
244                                 if (input_state == InputState.RequestLine) {
245                                         context.Request.SetRequestLine (line);
246                                         input_state = InputState.Headers;
247                                 } else {
248                                         context.Request.AddHeader (line);
249                                 }
250
251                                 if (context.HaveError)
252                                         return true;
253
254                                 if (position >= len)
255                                         break;
256                         }
257
258                         if (used == len) {
259                                 ms.SetLength (0);
260                                 position = 0;
261                         }
262                         return false;
263                 }
264
265                 string ReadLine (byte [] buffer, int offset, int len, ref int used)
266                 {
267                         if (current_line == null)
268                                 current_line = new StringBuilder ();
269                         int last = offset + len;
270                         used = 0;
271                         for (int i = offset; i < last && line_state != LineState.LF; i++) {
272                                 used++;
273                                 byte b = buffer [i];
274                                 if (b == 13) {
275                                         line_state = LineState.CR;
276                                 } else if (b == 10) {
277                                         line_state = LineState.LF;
278                                 } else {
279                                         current_line.Append ((char) b);
280                                 }
281                         }
282
283                         string result = null;
284                         if (line_state == LineState.LF) {
285                                 line_state = LineState.None;
286                                 result = current_line.ToString ();
287                                 current_line.Length = 0;
288                         }
289
290                         return result;
291                 }
292
293                 public void SendError (string msg, int status)
294                 {
295                         HttpListenerResponse response = context.Response;
296                         response.StatusCode = status;
297                         response.ContentType = "text/html";
298                         string description = HttpListenerResponse.GetStatusDescription (status);
299                         string str;
300                         if (msg != null)
301                                 str = String.Format ("<h1>{0} ({1})</h1>", description, msg);
302                         else
303                                 str = String.Format ("<h1>{0}</h1>", description);
304
305                         byte [] error = context.Response.ContentEncoding.GetBytes (str);
306                         response.Close (error, false);
307                 }
308
309                 public void SendError ()
310                 {
311                         SendError (context.ErrorMessage, context.ErrorStatus);
312                 }
313
314                 public void Close ()
315                 {
316                         if (sock != null) {
317                                 Stream st = GetResponseStream ();
318                                 st.Close ();
319                                 o_stream = null;
320                         }
321
322                         if (sock != null) {
323                                 if (chunked && context.Response.ForceCloseChunked == false) {
324                                         // Don't close. Keep working.
325                                         chunked_uses++;
326                                         Init ();
327                                         BeginReadRequest ();
328                                         return;
329                                 }
330
331                                 if (context.Response.Headers ["connection"] == "close") {
332                                         Socket s = sock;
333                                         sock = null;
334                                         try {
335                                                 s.Shutdown (SocketShutdown.Both);
336                                         } catch {
337                                         } finally {
338                                                 s.Close ();
339                                         }
340                                 } else {
341                                         Init ();
342                                         BeginReadRequest ();
343                                         return;
344                                 }
345
346                                 if (context_bound)
347                                         epl.UnbindContext (context);
348                         }
349                 }
350         }
351 }
352 #endif
353