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