merge -r 53370:58178
[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 #if NET_2_0
29 using System.IO;
30 using System.Net.Sockets;
31 using System.Text;
32 namespace System.Net {
33         sealed class HttpConnection
34         {
35                 const int BufferSize = 8192;
36                 Socket sock;
37                 NetworkStream stream;
38                 EndPointListener epl;
39                 MemoryStream ms;
40                 byte [] buffer;
41                 HttpListenerContext context;
42                 bool secure;
43                 StringBuilder current_line;
44                 ListenerPrefix prefix;
45                 RequestStream i_stream;
46                 ResponseStream o_stream;
47                 bool chunked;
48                 int chunked_uses;
49                 bool context_bound;
50
51                 public HttpConnection (Socket sock, EndPointListener epl, bool secure)
52                 {
53                         this.sock = sock;
54                         stream = new NetworkStream (sock, false);
55                         this.epl = epl;
56                         this.secure = secure;
57                         Init ();
58                 }
59
60                 void Init ()
61                 {
62                         context_bound = false;
63                         i_stream = null;
64                         o_stream = null;
65                         prefix = null;
66                         chunked = false;
67                         ms = new MemoryStream ();
68                         position = 0;
69                         input_state = InputState.RequestLine;
70                         line_state = LineState.None;
71                         context = new HttpListenerContext (this);
72                 }
73
74                 public int ChunkedUses {
75                         get { return chunked_uses; }
76                 }
77
78                 public IPEndPoint LocalEndPoint {
79                         get { return (IPEndPoint) sock.LocalEndPoint; }
80                 }
81
82                 public IPEndPoint RemoteEndPoint {
83                         get { return (IPEndPoint) sock.RemoteEndPoint; }
84                 }
85
86                 public bool IsSecure {
87                         get { return secure; }
88                 }
89
90                 public ListenerPrefix Prefix {
91                         get { return prefix; }
92                         set { prefix = value; }
93                 }
94
95                 public void BeginReadRequest ()
96                 {
97                         if (buffer == null)
98                                 buffer = new byte [BufferSize];
99                         stream.BeginRead (buffer, 0, BufferSize, OnRead, this);
100                 }
101
102                 public RequestStream GetRequestStream (bool chunked)
103                 {
104                         if (i_stream == null) {
105                                 byte [] buffer = ms.GetBuffer ();
106                                 int length = (int) ms.Length;
107                                 ms = null;
108                                 if (chunked) {
109                                         this.chunked = true;
110                                         context.Response.SendChunked = true;
111                                         i_stream = new ChunkedInputStream (context, sock, buffer, position, length);
112                                 } else {
113                                         i_stream = new RequestStream (sock, buffer, position, length);
114                                 }
115                         }
116                         return i_stream;
117                 }
118
119                 public ResponseStream GetResponseStream ()
120                 {
121                         // TODO: can we get this stream before reading the input?
122                         if (o_stream == null) {
123                                 HttpListener listener = context.Listener;
124                                 bool ign = (listener == null) ? true : listener.IgnoreWriteExceptions;
125                                 o_stream = new ResponseStream (sock, context.Response, ign);
126                         }
127                         return o_stream;
128                 }
129
130                 void OnRead (IAsyncResult ares)
131                 {
132                         // TODO: set a limit on ms length.
133                         HttpConnection cnc = (HttpConnection) ares.AsyncState;
134                         int nread = -1;
135                         try {
136                                 nread = stream.EndRead (ares);
137                                 ms.Write (buffer, 0, nread);
138                         } catch {
139                                 if (ms.Length > 0)
140                                         SendError ();
141                                 sock.Close ();
142                                 return;
143                         }
144
145                         if (nread == 0) {
146                                 //if (ms.Length > 0)
147                                 //      SendError (); // Why bother?
148                                 sock.Close ();
149                                 return;
150                         }
151
152                         if (ProcessInput (ms)) {
153                                 if (!context.HaveError)
154                                         context.Request.FinishInitialization ();
155
156                                 if (context.HaveError) {
157                                         SendError ();
158                                         Close ();
159                                         return;
160                                 }
161
162                                 if (!epl.BindContext (context)) {
163                                         SendError ("Invalid host", 400);
164                                         Close ();
165                                 }
166                                 context_bound = true;
167                                 return;
168                         }
169                         stream.BeginRead (buffer, 0, BufferSize, OnRead, cnc);
170                 }
171
172                 enum InputState {
173                         RequestLine,
174                         Headers
175                 }
176
177                 enum LineState {
178                         None,
179                         CR,
180                         LF
181                 }
182
183                 InputState input_state = InputState.RequestLine;
184                 LineState line_state = LineState.None;
185                 int position;
186
187                 // true -> done processing
188                 // false -> need more input
189                 bool ProcessInput (MemoryStream ms)
190                 {
191                         byte [] buffer = ms.GetBuffer ();
192                         int len = (int) ms.Length;
193                         int used = 0;
194                         string line;
195                         while ((line = ReadLine (buffer, position, len - position, ref used)) != null) {
196                                 position += used;
197                                 if (line == "") {
198                                         if (input_state == InputState.RequestLine)
199                                                 continue;
200                                         current_line = null;
201                                         ms = null;
202                                         return true;
203                                 }
204
205                                 if (input_state == InputState.RequestLine) {
206                                         context.Request.SetRequestLine (line);
207                                         input_state = InputState.Headers;
208                                 } else {
209                                         context.Request.AddHeader (line);
210                                 }
211
212                                 if (context.HaveError)
213                                         return true;
214
215                                 if (position >= len)
216                                         break;
217                         }
218
219                         if (used == len) {
220                                 ms.SetLength (0);
221                                 position = 0;
222                         }
223                         return false;
224                 }
225
226                 string ReadLine (byte [] buffer, int offset, int len, ref int used)
227                 {
228                         if (current_line == null)
229                                 current_line = new StringBuilder ();
230                         int last = offset + len;
231                         used = 0;
232                         for (int i = offset; i < last && line_state != LineState.LF; i++) {
233                                 used++;
234                                 byte b = buffer [i];
235                                 if (b == 13) {
236                                         line_state = LineState.CR;
237                                 } else if (b == 10) {
238                                         line_state = LineState.LF;
239                                 } else {
240                                         current_line.Append ((char) b);
241                                 }
242                         }
243
244                         string result = null;
245                         if (line_state == LineState.LF) {
246                                 line_state = LineState.None;
247                                 result = current_line.ToString ();
248                                 current_line.Length = 0;
249                         }
250
251                         return result;
252                 }
253
254                 public void SendError (string msg, int status)
255                 {
256                         HttpListenerResponse response = context.Response;
257                         response.StatusCode = status;
258                         response.ContentType = "text/html";
259                         string description = HttpListenerResponse.GetStatusDescription (status);
260                         string str;
261                         if (msg != null)
262                                 str = String.Format ("<h1>{0} ({1})</h1>", description, msg);
263                         else
264                                 str = String.Format ("<h1>{0}</h1>", description);
265
266                         byte [] error = context.Response.ContentEncoding.GetBytes (str);
267                         response.Close (error, false);
268                 }
269
270                 public void SendError ()
271                 {
272                         SendError (context.ErrorMessage, context.ErrorStatus);
273                 }
274
275                 public void Close ()
276                 {
277                         if (o_stream != null) {
278                                 Stream st = o_stream;
279                                 st.Close ();
280                                 o_stream = null;
281                         }
282
283                         if (sock != null) {
284                                 if (chunked && context.Response.ForceCloseChunked == false) {
285                                         // Don't close. Keep working.
286                                         chunked_uses++;
287                                         Init ();
288                                         BeginReadRequest ();
289                                         return;
290                                 }
291
292                                 Socket s = sock;
293                                 sock = null;
294                                 s.Shutdown (SocketShutdown.Both);
295                                 s.Close ();
296                                 if (context_bound)
297                                         epl.UnbindContext (context);
298                         }
299                 }
300         }
301 }
302 #endif
303