[websocket] Better hijacking.
[mono.git] / mcs / class / System / System.Net / HttpListenerContext.cs
1 //
2 // System.Net.HttpListenerContext
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 using System.ComponentModel;
29 using System.Net.Mime;
30
31 #if SECURITY_DEP
32
33 using System;
34 using System.Collections.Specialized;
35 using System.IO;
36 using System.Security.Principal;
37 using System.Text;
38
39 #if NET_4_5
40 using System.Net.Sockets;
41 using System.Net.WebSockets;
42 using System.Threading.Tasks;
43 #endif
44 namespace System.Net {
45         public sealed class HttpListenerContext {
46                 HttpListenerRequest request;
47                 HttpListenerResponse response;
48                 IPrincipal user;
49                 HttpConnection cnc;
50                 string error;
51                 int err_status = 400;
52                 internal HttpListener Listener;
53
54                 internal HttpListenerContext (HttpConnection cnc)
55                 {
56                         this.cnc = cnc;
57                         request = new HttpListenerRequest (this);
58                         response = new HttpListenerResponse (this);
59                 }
60
61                 internal int ErrorStatus {
62                         get { return err_status; }
63                         set { err_status = value; }
64                 }
65
66                 internal string ErrorMessage {
67                         get { return error; }
68                         set { error = value; }
69                 }
70
71                 internal bool HaveError {
72                         get { return (error != null); }
73                 }
74
75                 internal HttpConnection Connection {
76                         get { return cnc; }
77                 }
78
79                 public HttpListenerRequest Request {
80                         get { return request; }
81                 }
82
83                 public HttpListenerResponse Response {
84                         get { return response; }
85                 }
86
87                 public IPrincipal User {
88                         get { return user; }
89                 }
90
91                 internal void ParseAuthentication (AuthenticationSchemes expectedSchemes) {
92                         if (expectedSchemes == AuthenticationSchemes.Anonymous)
93                                 return;
94
95                         // TODO: Handle NTLM/Digest modes
96                         string header = request.Headers ["Authorization"];
97                         if (header == null || header.Length < 2)
98                                 return;
99
100                         string [] authenticationData = header.Split (new char [] {' '}, 2);
101                         if (string.Compare (authenticationData [0], "basic", true) == 0) {
102                                 user = ParseBasicAuthentication (authenticationData [1]);
103                         }
104                         // TODO: throw if malformed -> 400 bad request
105                 }
106
107                 internal IPrincipal ParseBasicAuthentication (string authData) {
108                         try {
109                                 // Basic AUTH Data is a formatted Base64 String
110                                 //string domain = null;
111                                 string user = null;
112                                 string password = null;
113                                 int pos = -1;
114                                 string authString = System.Text.Encoding.Default.GetString (Convert.FromBase64String (authData));
115
116                                 // The format is DOMAIN\username:password
117                                 // Domain is optional
118
119                                 pos = authString.IndexOf (':');
120         
121                                 // parse the password off the end
122                                 password = authString.Substring (pos+1);
123
124                                 // discard the password
125                                 authString = authString.Substring (0, pos);
126         
127                                 // check if there is a domain
128                                 pos = authString.IndexOf ('\\');
129         
130                                 if (pos > 0) {
131                                         //domain = authString.Substring (0, pos);
132                                         user = authString.Substring (pos);
133                                 } else {
134                                         user = authString;
135                                 }
136         
137                                 HttpListenerBasicIdentity identity = new HttpListenerBasicIdentity (user, password);
138                                 // TODO: What are the roles MS sets
139                                 return new GenericPrincipal (identity, new string [0]);
140                         } catch (Exception) {
141                                 // Invalid auth data is swallowed silently
142                                 return null;
143                         } 
144                 }
145 #if NET_4_5
146                 public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync (string subProtocol)
147                 {
148                         return AcceptWebSocketAsync (subProtocol, System.Net.WebSockets.WebSocket.DefaultKeepAliveInterval);
149                 }
150
151                 public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync (string subProtocol, TimeSpan keepAliveInterval)
152                 {
153                         // Default receiveBuffersize is documented on MSDN Library.
154                         // http://msdn.microsoft.com/ja-jp/library/hh159274(v=vs.110).aspx
155                         return AcceptWebSocketAsync (subProtocol, 16385, keepAliveInterval);
156                 }
157
158                 public async Task<HttpListenerWebSocketContext> AcceptWebSocketAsync (string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval)
159                 {
160                         if (subProtocol != null && subProtocol == "") {
161                                 throw new ArgumentException ("subProtocol must not empty string");
162                         }
163                         if (receiveBufferSize < 16 || receiveBufferSize > 64 * 1024) {
164                                 throw new ArgumentOutOfRangeException ("receiveBufferSize should be >=16 and <=64K bytes");
165                         }
166                         if (!request.IsWebSocketRequest) {
167                                 throw new WebSocketException ("Request is not WebSocket Handshake");
168                         }
169                         string secKey = request.Headers ["Sec-WebSocket-Key"];
170                         if (secKey == null) {
171                                 throw new WebSocketException ("Request doesn't contain Sec-WebSocket-Key header");
172                         }
173                         string origin = request.Headers ["Origin"];
174                         if (origin == null) {
175                                 throw new WebSocketException ("Request doesn't contain Origin header");
176                         }
177                         string acceptKey = StreamWebSocket.CreateAcceptKey (secKey);
178                         var rstream = cnc.GetRequestStream (false, -1);
179                         var wstream = new NetworkStream (cnc.Hijack ());
180                         string header = "HTTP/1.1 101 Switching Protocols\r\n";
181                         header += "Upgrade: websocket\r\nConnection: Upgrade\r\n";
182                         header += "Sec-WebSocket-Accept: " + acceptKey + "\r\n\r\n";
183                         var headerBytes = Encoding.ASCII.GetBytes (header);
184                         await wstream.WriteAsync (headerBytes, 0, headerBytes.Length);
185                         return new HttpListenerWebSocketContext (rstream, wstream, request, user, subProtocol);
186                 }
187
188                 public Task<HttpListenerWebSocketContext> AcceptWebSocketAsync (string subProtocol, int receiveBufferSize, TimeSpan keepAliveInterval, ArraySegment<byte> internalBuffer)
189                 {
190                         return AcceptWebSocketAsync (subProtocol, receiveBufferSize, keepAliveInterval);
191                 }
192 #endif
193         }
194 }
195 #endif
196