Merge pull request #347 from JamesB7/master
[mono.git] / mcs / class / System / System.Net / EndPointListener.cs
1 //
2 // System.Net.EndPointListener
3 //
4 // Author:
5 //      Gonzalo Paniagua Javier (gonzalo.mono@gmail.com)
6 //
7 // Copyright (c) 2005 Novell, Inc. (http://www.novell.com)
8 // Copyright (c) 2012 Xamarin, Inc. (http://xamarin.com)
9 //
10 // Permission is hereby granted, free of charge, to any person obtaining
11 // a copy of this software and associated documentation files (the
12 // "Software"), to deal in the Software without restriction, including
13 // without limitation the rights to use, copy, modify, merge, publish,
14 // distribute, sublicense, and/or sell copies of the Software, and to
15 // permit persons to whom the Software is furnished to do so, subject to
16 // the following conditions:
17 // 
18 // The above copyright notice and this permission notice shall be
19 // included in all copies or substantial portions of the Software.
20 // 
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
24 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
25 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
26 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
27 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 //
29
30 #if SECURITY_DEP
31
32 using System.IO;
33 using System.Net.Sockets;
34 using System.Collections;
35 using System.Collections.Generic;
36 using System.Security.Cryptography;
37 using System.Security.Cryptography.X509Certificates;
38 using System.Threading;
39 using Mono.Security.Authenticode;
40
41 namespace System.Net {
42         sealed class EndPointListener
43         {
44                 IPEndPoint endpoint;
45                 Socket sock;
46                 Hashtable prefixes;  // Dictionary <ListenerPrefix, HttpListener>
47                 ArrayList unhandled; // List<ListenerPrefix> unhandled; host = '*'
48                 ArrayList all;       // List<ListenerPrefix> all;  host = '+'
49                 X509Certificate2 cert;
50                 AsymmetricAlgorithm key;
51                 bool secure;
52                 Dictionary<HttpConnection, HttpConnection> unregistered;
53
54                 public EndPointListener (IPAddress addr, int port, bool secure)
55                 {
56                         if (secure) {
57                                 this.secure = secure;
58                                 LoadCertificateAndKey (addr, port);
59                         }
60
61                         endpoint = new IPEndPoint (addr, port);
62                         sock = new Socket (addr.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
63                         sock.Bind (endpoint);
64                         sock.Listen (500);
65                         SocketAsyncEventArgs args = new SocketAsyncEventArgs ();
66                         args.UserToken = this;
67                         args.Completed += OnAccept;
68                         sock.AcceptAsync (args);
69                         prefixes = new Hashtable ();
70                         unregistered = new Dictionary<HttpConnection, HttpConnection> ();
71                 }
72
73                 void LoadCertificateAndKey (IPAddress addr, int port)
74                 {
75                         // Actually load the certificate
76                         try {
77                                 string dirname = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData);
78                                 string path = Path.Combine (dirname, ".mono");
79                                 path = Path.Combine (path, "httplistener");
80                                 string cert_file = Path.Combine (path, String.Format ("{0}.cer", port));
81                                 if (!File.Exists (cert_file))
82                                         return;
83                                 string pvk_file = Path.Combine (path, String.Format ("{0}.pvk", port));
84                                 if (!File.Exists (pvk_file))
85                                         return;
86                                 cert = new X509Certificate2 (cert_file);
87                                 key = PrivateKey.CreateFromFile (pvk_file).RSA;
88                         } catch {
89                                 // ignore errors
90                         }
91                 }
92
93                 static void OnAccept (object sender, EventArgs e)
94                 {
95                         SocketAsyncEventArgs args = (SocketAsyncEventArgs) e;
96                         EndPointListener epl = (EndPointListener) args.UserToken;
97                         Socket accepted = null;
98                         if (args.SocketError == SocketError.Success) {
99                                 accepted = args.AcceptSocket;
100                                 args.AcceptSocket = null;
101                         }
102
103                         try {
104                                 if (epl.sock != null)
105                                         epl.sock.AcceptAsync (args);
106                         } catch {
107                                 if (accepted != null) {
108                                         try {
109                                                 accepted.Close ();
110                                         } catch {}
111                                         accepted = null;
112                                 }
113                         } 
114
115                         if (accepted == null)
116                                 return;
117
118                         if (epl.secure && (epl.cert == null || epl.key == null)) {
119                                 accepted.Close ();
120                                 return;
121                         }
122                         HttpConnection conn = new HttpConnection (accepted, epl, epl.secure, epl.cert, epl.key);
123                         lock (epl.unregistered) {
124                                 epl.unregistered [conn] = conn;
125                         }
126                         conn.BeginReadRequest ();
127                 }
128
129                 internal void RemoveConnection (HttpConnection conn)
130                 {
131                         lock (unregistered) {
132                                 unregistered.Remove (conn);
133                         }
134                 }
135
136                 public bool BindContext (HttpListenerContext context)
137                 {
138                         HttpListenerRequest req = context.Request;
139                         ListenerPrefix prefix;
140                         HttpListener listener = SearchListener (req.Url, out prefix);
141                         if (listener == null)
142                                 return false;
143
144                         context.Listener = listener;
145                         context.Connection.Prefix = prefix;
146                         return true;
147                 }
148
149                 public void UnbindContext (HttpListenerContext context)
150                 {
151                         if (context == null || context.Request == null)
152                                 return;
153
154                         context.Listener.UnregisterContext (context);
155                 }
156
157                 HttpListener SearchListener (Uri uri, out ListenerPrefix prefix)
158                 {
159                         prefix = null;
160                         if (uri == null)
161                                 return null;
162
163                         string host = uri.Host;
164                         int port = uri.Port;
165                         string path = HttpUtility.UrlDecode (uri.AbsolutePath);
166                         string path_slash = path [path.Length - 1] == '/' ? path : path + "/";
167                         
168                         HttpListener best_match = null;
169                         int best_length = -1;
170
171                         if (host != null && host != "") {
172                                 Hashtable p_ro = prefixes;
173                                 foreach (ListenerPrefix p in p_ro.Keys) {
174                                         string ppath = p.Path;
175                                         if (ppath.Length < best_length)
176                                                 continue;
177
178                                         if (p.Host != host || p.Port != port)
179                                                 continue;
180
181                                         if (path.StartsWith (ppath) || path_slash.StartsWith (ppath)) {
182                                                 best_length = ppath.Length;
183                                                 best_match = (HttpListener) p_ro [p];
184                                                 prefix = p;
185                                         }
186                                 }
187                                 if (best_length != -1)
188                                         return best_match;
189                         }
190
191                         ArrayList list = unhandled;
192                         best_match = MatchFromList (host, path, list, out prefix);
193                         if (path != path_slash && best_match == null)
194                                 best_match = MatchFromList (host, path_slash, list, out prefix);
195                         if (best_match != null)
196                                 return best_match;
197
198                         list = all;
199                         best_match = MatchFromList (host, path, list, out prefix);
200                         if (path != path_slash && best_match == null)
201                                 best_match = MatchFromList (host, path_slash, list, out prefix);
202                         if (best_match != null)
203                                 return best_match;
204
205                         return null;
206                 }
207
208                 HttpListener MatchFromList (string host, string path, ArrayList list, out ListenerPrefix prefix)
209                 {
210                         prefix = null;
211                         if (list == null)
212                                 return null;
213
214                         HttpListener best_match = null;
215                         int best_length = -1;
216                         
217                         foreach (ListenerPrefix p in list) {
218                                 string ppath = p.Path;
219                                 if (ppath.Length < best_length)
220                                         continue;
221
222                                 if (path.StartsWith (ppath)) {
223                                         best_length = ppath.Length;
224                                         best_match = p.Listener;
225                                         prefix = p;
226                                 }
227                         }
228
229                         return best_match;
230                 }
231
232                 void AddSpecial (ArrayList coll, ListenerPrefix prefix)
233                 {
234                         if (coll == null)
235                                 return;
236
237                         foreach (ListenerPrefix p in coll) {
238                                 if (p.Path == prefix.Path) //TODO: code
239                                         throw new HttpListenerException (400, "Prefix already in use.");
240                         }
241                         coll.Add (prefix);
242                 }
243
244                 bool RemoveSpecial (ArrayList coll, ListenerPrefix prefix)
245                 {
246                         if (coll == null)
247                                 return false;
248
249                         int c = coll.Count;
250                         for (int i = 0; i < c; i++) {
251                                 ListenerPrefix p = (ListenerPrefix) coll [i];
252                                 if (p.Path == prefix.Path) {
253                                         coll.RemoveAt (i);
254                                         return true;
255                                 }
256                         }
257                         return false;
258                 }
259
260                 void CheckIfRemove ()
261                 {
262                         if (prefixes.Count > 0)
263                                 return;
264
265                         ArrayList list = unhandled;
266                         if (list != null && list.Count > 0)
267                                 return;
268
269                         list = all;
270                         if (list != null && list.Count > 0)
271                                 return;
272
273                         EndPointManager.RemoveEndPoint (this, endpoint);
274                 }
275
276                 public void Close ()
277                 {
278                         sock.Close ();
279                         lock (unregistered) {
280                                 //
281                                 // Clone the list because RemoveConnection can be called from Close
282                                 //
283                                 var connections = new List<HttpConnection> (unregistered.Keys);
284
285                                 foreach (HttpConnection c in connections)
286                                         c.Close (true);
287                                 unregistered.Clear ();
288                         }
289                 }
290
291                 public void AddPrefix (ListenerPrefix prefix, HttpListener listener)
292                 {
293                         ArrayList current;
294                         ArrayList future;
295                         if (prefix.Host == "*") {
296                                 do {
297                                         current = unhandled;
298                                         future = (current != null) ? (ArrayList) current.Clone () : new ArrayList ();
299                                         prefix.Listener = listener;
300                                         AddSpecial (future, prefix);
301                                 } while (Interlocked.CompareExchange (ref unhandled, future, current) != current);
302                                 return;
303                         }
304
305                         if (prefix.Host == "+") {
306                                 do {
307                                         current = all;
308                                         future = (current != null) ? (ArrayList) current.Clone () : new ArrayList ();
309                                         prefix.Listener = listener;
310                                         AddSpecial (future, prefix);
311                                 } while (Interlocked.CompareExchange (ref all, future, current) != current);
312                                 return;
313                         }
314
315                         Hashtable prefs, p2;
316                         do {
317                                 prefs = prefixes;
318                                 if (prefs.ContainsKey (prefix)) {
319                                         HttpListener other = (HttpListener) prefs [prefix];
320                                         if (other != listener) // TODO: code.
321                                                 throw new HttpListenerException (400, "There's another listener for " + prefix);
322                                         return;
323                                 }
324                                 p2 = (Hashtable) prefs.Clone ();
325                                 p2 [prefix] = listener;
326                         } while (Interlocked.CompareExchange (ref prefixes, p2, prefs) != prefs);
327                 }
328
329                 public void RemovePrefix (ListenerPrefix prefix, HttpListener listener)
330                 {
331                         ArrayList current;
332                         ArrayList future;
333                         if (prefix.Host == "*") {
334                                 do {
335                                         current = unhandled;
336                                         future = (current != null) ? (ArrayList) current.Clone () : new ArrayList ();
337                                         if (!RemoveSpecial (future, prefix))
338                                                 break; // Prefix not found
339                                 } while (Interlocked.CompareExchange (ref unhandled, future, current) != current);
340                                 CheckIfRemove ();
341                                 return;
342                         }
343
344                         if (prefix.Host == "+") {
345                                 do {
346                                         current = all;
347                                         future = (current != null) ? (ArrayList) current.Clone () : new ArrayList ();
348                                         if (!RemoveSpecial (future, prefix))
349                                                 break; // Prefix not found
350                                 } while (Interlocked.CompareExchange (ref all, future, current) != current);
351                                 CheckIfRemove ();
352                                 return;
353                         }
354
355                         Hashtable prefs, p2;
356                         do {
357                                 prefs = prefixes;
358                                 if (!prefs.ContainsKey (prefix))
359                                         break;
360
361                                 p2 = (Hashtable) prefs.Clone ();
362                                 p2.Remove (prefix);
363                         } while (Interlocked.CompareExchange (ref prefixes, p2, prefs) != prefs);
364                         CheckIfRemove ();
365                 }
366         }
367 }
368 #endif
369