2008-09-17 Michael Hutchinson <mhutchinson@novell.com>
[mono-project.git] / mcs / class / System / System.Net / HttpConnection.cs
blob2eefa1153abae05bd9041110297a293bda1522d0
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.
29 #if NET_2_0 && SECURITY_DEP
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;
38 #if !EMBEDDED_IN_1_0
39 using Mono.Security.Protocol.Tls;
40 #endif
42 namespace System.Net {
44 interface IHttpListenerContextBinder {
45 bool BindContext (HttpListenerContext context);
46 void UnbindContext (HttpListenerContext context);
49 sealed class HttpConnection
51 const int BufferSize = 8192;
52 Socket sock;
53 Stream stream;
54 IHttpListenerContextBinder epl;
55 MemoryStream ms;
56 byte [] buffer;
57 HttpListenerContext context;
58 StringBuilder current_line;
59 ListenerPrefix prefix;
60 RequestStream i_stream;
61 ResponseStream o_stream;
62 bool chunked;
63 int chunked_uses;
64 bool context_bound;
65 bool secure;
66 AsymmetricAlgorithm key;
68 #if EMBEDDED_IN_1_0
69 public HttpConnection (Socket sock, IHttpListenerContextBinder epl)
71 this.sock = sock;
72 this.epl = epl;
73 stream = new NetworkStream (sock, false);
74 Init ();
76 #else
77 public HttpConnection (Socket sock, IHttpListenerContextBinder epl, bool secure, X509Certificate2 cert, AsymmetricAlgorithm key)
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;
91 Init ();
93 #endif
95 AsymmetricAlgorithm OnPVKSelection (X509Certificate certificate, string targetHost)
97 return key;
101 void Init ()
103 context_bound = false;
104 i_stream = null;
105 o_stream = null;
106 prefix = null;
107 chunked = false;
108 ms = new MemoryStream ();
109 position = 0;
110 input_state = InputState.RequestLine;
111 line_state = LineState.None;
112 context = new HttpListenerContext (this);
115 public int ChunkedUses {
116 get { return chunked_uses; }
119 public IPEndPoint LocalEndPoint {
120 get { return (IPEndPoint) sock.LocalEndPoint; }
123 public IPEndPoint RemoteEndPoint {
124 get { return (IPEndPoint) sock.RemoteEndPoint; }
127 public bool IsSecure {
128 get { return secure; }
131 public ListenerPrefix Prefix {
132 get { return prefix; }
133 set { prefix = value; }
136 public void BeginReadRequest ()
138 if (buffer == null)
139 buffer = new byte [BufferSize];
140 stream.BeginRead (buffer, 0, BufferSize, OnRead, this);
143 public RequestStream GetRequestStream (bool chunked, long contentlength)
145 if (i_stream == null) {
146 byte [] buffer = ms.GetBuffer ();
147 int length = (int) ms.Length;
148 ms = null;
149 if (chunked) {
150 this.chunked = true;
151 context.Response.SendChunked = true;
152 i_stream = new ChunkedInputStream (context, stream, buffer, position, length - position);
153 } else {
154 i_stream = new RequestStream (stream, buffer, position, length - position, contentlength);
157 return i_stream;
160 public ResponseStream GetResponseStream ()
162 // TODO: can we get this stream before reading the input?
163 if (o_stream == null) {
164 HttpListener listener = context.Listener;
165 bool ign = (listener == null) ? true : listener.IgnoreWriteExceptions;
166 o_stream = new ResponseStream (stream, context.Response, ign);
168 return o_stream;
171 void OnRead (IAsyncResult ares)
173 // TODO: set a limit on ms length.
174 HttpConnection cnc = (HttpConnection) ares.AsyncState;
175 int nread = -1;
176 try {
177 nread = stream.EndRead (ares);
178 ms.Write (buffer, 0, nread);
179 } catch (Exception e) {
180 Console.WriteLine (e);
181 if (ms.Length > 0)
182 SendError ();
183 sock.Close ();
184 return;
187 if (nread == 0) {
188 //if (ms.Length > 0)
189 // SendError (); // Why bother?
190 sock.Close ();
191 return;
194 if (ProcessInput (ms)) {
195 if (!context.HaveError)
196 context.Request.FinishInitialization ();
198 if (context.HaveError) {
199 SendError ();
200 Close ();
201 return;
204 if (!epl.BindContext (context)) {
205 SendError ("Invalid host", 400);
206 Close ();
208 context_bound = true;
209 return;
211 stream.BeginRead (buffer, 0, BufferSize, OnRead, cnc);
214 enum InputState {
215 RequestLine,
216 Headers
219 enum LineState {
220 None,
225 InputState input_state = InputState.RequestLine;
226 LineState line_state = LineState.None;
227 int position;
229 // true -> done processing
230 // false -> need more input
231 bool ProcessInput (MemoryStream ms)
233 byte [] buffer = ms.GetBuffer ();
234 int len = (int) ms.Length;
235 int used = 0;
236 string line;
237 while ((line = ReadLine (buffer, position, len - position, ref used)) != null) {
238 position += used;
239 if (line == "") {
240 if (input_state == InputState.RequestLine)
241 continue;
242 current_line = null;
243 ms = null;
244 return true;
247 if (input_state == InputState.RequestLine) {
248 context.Request.SetRequestLine (line);
249 input_state = InputState.Headers;
250 } else {
251 context.Request.AddHeader (line);
254 if (context.HaveError)
255 return true;
257 if (position >= len)
258 break;
261 if (used == len) {
262 ms.SetLength (0);
263 position = 0;
265 return false;
268 string ReadLine (byte [] buffer, int offset, int len, ref int used)
270 if (current_line == null)
271 current_line = new StringBuilder ();
272 int last = offset + len;
273 used = 0;
274 for (int i = offset; i < last && line_state != LineState.LF; i++) {
275 used++;
276 byte b = buffer [i];
277 if (b == 13) {
278 line_state = LineState.CR;
279 } else if (b == 10) {
280 line_state = LineState.LF;
281 } else {
282 current_line.Append ((char) b);
286 string result = null;
287 if (line_state == LineState.LF) {
288 line_state = LineState.None;
289 result = current_line.ToString ();
290 current_line.Length = 0;
293 return result;
296 public void SendError (string msg, int status)
298 HttpListenerResponse response = context.Response;
299 response.StatusCode = status;
300 response.ContentType = "text/html";
301 string description = HttpListenerResponse.GetStatusDescription (status);
302 string str;
303 if (msg != null)
304 str = String.Format ("<h1>{0} ({1})</h1>", description, msg);
305 else
306 str = String.Format ("<h1>{0}</h1>", description);
308 byte [] error = context.Response.ContentEncoding.GetBytes (str);
309 response.Close (error, false);
312 public void SendError ()
314 SendError (context.ErrorMessage, context.ErrorStatus);
317 public void Close ()
319 if (o_stream != null) {
320 Stream st = o_stream;
321 st.Close ();
322 o_stream = null;
325 if (sock != null) {
326 if (chunked && context.Response.ForceCloseChunked == false) {
327 // Don't close. Keep working.
328 chunked_uses++;
329 Init ();
330 BeginReadRequest ();
331 return;
334 Socket s = sock;
335 sock = null;
336 try {
337 s.Shutdown (SocketShutdown.Both);
338 } finally {
339 s.Close ();
341 if (context_bound)
342 epl.UnbindContext (context);
347 #endif