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