1 //
2 // Mono.WebServer.ModMonoWorker
3 //
4 // Authors:
5 //	Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 // 	Lluis Sanchez Gual (lluis@ximian.com)
7 //
8 // (C) Copyright 2004-2007 Novell, Inc. (http://www.novell.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 using System;
31 using System.IO;
32 using System.Net.Sockets;
33 using Mono.WebServer.Log;
34 
35 namespace Mono.WebServer.Apache
36 {
37 	//
38 	// ModMonoWorker: The worker that does the initial processing of mod_mono
39 	// requests.
40 	//
41 	class ModMonoWorker: Worker, IDisposable
42 	{
43 		public NetworkStream Stream;
44 
45 		readonly ApplicationServer server;
46 		ModMonoRequest modRequest;
47 		bool closed;
48 		int requestId = -1;
49 		ModMonoRequestBroker broker;
50 		readonly Socket client;
51 
ModMonoWorker(Socket client, ApplicationServer server)52 		public ModMonoWorker (Socket client, ApplicationServer server)
53 		{
54 			Stream = new NetworkStream (client, true);
55 			this.client = client;
56 			this.server = server;
57 		}
58 
Dispose(Action disposer, string name)59 		static void Dispose (Action disposer, string name)
60 		{
61 			if (disposer == null)
62 				return;
63 
64 			try {
65 				disposer ();
66 			} catch (Exception ex) {
67 				Logger.Write (LogLevel.Error, "While disposing ModMonoWorker. {0} disposing failed with exception:", name);
68 				Logger.Write (ex);
69 			}
70 		}
71 
Dispose()72 		public void Dispose ()
73 		{
74 			Dispose (() => {
75 				if (Stream != null)
76 					Stream.Dispose ();
77 			}, "Stream");
78 
79 			Dispose (() => {
80 				if (modRequest != null)
81 					modRequest.Dispose ();
82 			}, "modRequest");
83 
84 			GC.SuppressFinalize (this);
85 		}
86 
Run(object state)87 		public override void Run (object state)
88 		{
89 			try {
90 				InnerRun ();
91 			} catch (Exception e) {
92 				// FileNotFoundException might be a sign of a bad deployment
93 				// once we require the .exe and Mono.WebServer in bin or the GAC.
94 
95 				// IOException, like EndOfStreamException, might be ok.
96 
97 				if (!(e is EndOfStreamException))
98 					Logger.Write (e);
99 
100 				try {
101 					// Closing is enough for mod_mono. the module will return a 50x
102 					if (Stream != null){
103 						Stream.Close ();
104 						Stream = null;
105 					}
106 				} catch {}
107 				if (!server.SingleApplication && broker != null && requestId != -1) {
108 					broker.UnregisterRequest (requestId);
109 					requestId = -1;
110 				}
111 			}
112 		}
113 
GetOrCreateApplication(string vhost, int port, string filepath, string virt)114 		VPathToHost GetOrCreateApplication (string vhost, int port, string filepath, string virt)
115 		{
116 			string final_vdir;
117 			string final_pdir;
118 			GetPhysicalDirectory (virt, filepath, out final_vdir, out final_pdir);
119 
120 			//Logger.Write (LogLevel.Error, "final_pdir: {0} final_vdir: {1}", final_pdir, final_vdir);
121 			VPathToHost vapp = server.GetApplicationForPath (vhost, port, virt, false);
122 			if (vapp == null) {
123 				// Don't know why this breaks mod-mono-server2.exe, but not mod-mono-server.exe
124 				//final_pdir = "file://" + final_pdir;
125 				if (final_vdir [0] != '/')
126 					final_vdir = "/" + final_vdir;
127 				server.AddApplication (vhost, port, final_vdir, final_pdir);
128 				vapp = server.GetApplicationForPath (vhost, port, virt, false);
129 			}
130 
131 			return vapp;
132 		}
133 
GetPhysicalDirectory(string vfile, string pfile, out string final_vdir, out string final_pdir)134 		internal static void GetPhysicalDirectory (string vfile, string pfile, out string final_vdir, out string final_pdir)
135 		{
136 			string vdir = Path.GetDirectoryName (vfile);
137 			string pdir = Path.GetDirectoryName (pfile);
138 			var vinfo = new DirectoryInfo (vdir);
139 			var pinfo = new DirectoryInfo (pdir);
140 			final_pdir = null;
141 			final_vdir = null;
142 			while (vinfo != null && pinfo != null) {
143 				if (IsRootDirectory (pinfo)) {
144 					final_pdir = pinfo.ToString ();
145 					final_vdir = vinfo.ToString ();
146 					break;
147 				}
148 				if (pinfo.Name != vinfo.Name) {
149 					final_vdir = vinfo.ToString ();
150 					break;
151 				}
152 				pinfo = pinfo.Parent;
153 				vinfo = vinfo.Parent;
154 			}
155 			if (final_pdir == null) {
156 				final_pdir = pinfo.ToString ();
157 			}
158 			if (final_vdir == null) {
159 				final_vdir = vinfo.ToString ();
160 			}
161 		}
162 
IsRootDirectory(DirectoryInfo info)163 		static bool IsRootDirectory (DirectoryInfo info)
164 		{
165 			if (!info.Exists)
166 				return false;
167 
168 			return File.Exists (Path.Combine (info.FullName, "Global.asax"))
169 				|| File.Exists (Path.Combine (info.FullName, "global.asax"))
170 				|| Directory.Exists (Path.Combine (info.FullName, "bin"));
171 		}
172 
InnerRun()173 		void InnerRun ()
174 		{
175 			requestId = -1;
176 			broker = null;
177 
178 			var rr = new RequestReader (client);
179 			if (rr.ShuttingDown) {
180 				Close ();
181 				server.Stop ();
182 				return;
183 			}
184 
185 			string vhost = rr.Request.GetRequestHeader ("Host");
186 			int port = -1;
187 			if (vhost != null) {
188 				int lead = vhost.IndexOf('[');
189 				int follow = lead >= 0 ? vhost.IndexOf(']') : -1;
190 				if (follow > lead) {
191 					//ipv6 address
192 					int colon = vhost.IndexOf("]:", StringComparison.Ordinal);
193 					if (colon != -1) {
194 						Int32.TryParse (vhost.Substring (colon + 2), out port);
195 						vhost = vhost.Substring(0, colon + 1);
196 					}
197 				} else {
198 					//ipv4 or hostname
199 					int colon = vhost.IndexOf (':');
200 					if (colon != -1) {
201 						Int32.TryParse (vhost.Substring (colon + 1), out port);
202 						vhost = vhost.Substring (0, colon);
203 					}
204 				}
205 				if (port <= 0 || port > 65535) {
206 					//No port specified, Int32.TryParse failed or invalid port number
207 					port = 80;
208 				}
209 			}
210 
211 			string vServerName = rr.Request.GetVirtualServerName () ?? vhost;
212 
213 			VPathToHost vapp;
214 			string vpath = rr.GetUriPath ();
215 			string path = rr.GetPhysicalPath ();
216 			if (path == null) {
217 				vapp = server.GetApplicationForPath (vServerName, port, vpath, false);
218 			} else {
219 				vapp = GetOrCreateApplication (vServerName, port, path, vpath);
220 			}
221 
222 			if (vapp == null) {
223 				rr.NotFound ();
224 				Stream.Close ();
225 				Stream = null;
226 				return;
227 			}
228 
229 			var host = (ModMonoApplicationHost) vapp.AppHost;
230 			if (host == null) {
231 				rr.NotFound ();
232 				Stream.Close ();
233 				Stream = null;
234 				return;
235 			}
236 			modRequest = rr.Request;
237 
238 			if (!server.SingleApplication) {
239 				broker = (ModMonoRequestBroker) vapp.RequestBroker;
240 				broker.UnregisterRequestEvent += OnUnregisterRequest;
241 				requestId = broker.RegisterRequest (this);
242 			}
243 
244 			host.ProcessRequest (requestId,
245 					     modRequest.GetHttpVerbName(),
246 					     modRequest.GetQueryString(),
247 					     modRequest.GetUri(),
248 					     modRequest.GetProtocol(),
249 					     modRequest.GetLocalAddress(),
250 					     modRequest.GetServerPort(),
251 					     modRequest.GetRemoteAddress(),
252 					     modRequest.GetRemotePort(),
253 					     modRequest.GetRemoteName(),
254 					     modRequest.GetAllHeaders(),
255 					     modRequest.GetAllHeaderValues(),
256 					     (requestId == -1) ? this : null);
257 		}
258 
OnUnregisterRequest(object sender, UnregisterRequestEventArgs args)259 		void OnUnregisterRequest (object sender, UnregisterRequestEventArgs args)
260 		{
261 			var broker = sender as BaseRequestBroker;
262 			if (broker != null)
263 				broker.UnregisterRequestEvent -= OnUnregisterRequest;
264 
265 			if (requestId == -1 || requestId != args.RequestId)
266 				return;
267 
268 			requestId = -1;
269 		}
270 
Read(byte[] buffer, int position, int size)271 		public override int Read (byte[] buffer, int position, int size)
272 		{
273 			return modRequest.GetClientBlock (buffer, position, size);
274 		}
275 
Write(byte[] buffer, int position, int size)276 		public override void Write (byte[] buffer, int position, int size)
277 		{
278 			modRequest.SendResponseFromMemory (buffer, position, size);
279 		}
280 
Write(IntPtr ptr, int size)281 		public void Write (IntPtr ptr, int size)
282 		{
283 			modRequest.SendResponseFromMemory (ptr, size);
284 		}
285 
Close()286 		public override void Close ()
287 		{
288 			if (closed)
289 				return;
290 
291 			closed = true;
292 			try {
293 				modRequest.Close ();
294 			} catch {} // ignore any error
295 			try {
296 				if (Stream != null) {
297 					Stream.Close ();
298 					Stream = null;
299 				}
300 			} catch {}
301 		}
302 
Flush()303 		public override void Flush ()
304 		{
305 			//modRequest.Flush (); No-op
306 		}
307 
IsConnected()308 		public override bool IsConnected ()
309 		{
310 			return modRequest.IsConnected ();
311 		}
312 
SendFile(string filename)313 		public void SendFile (string filename)
314 		{
315 			modRequest.SendFile (filename);
316 		}
317 
GetServerVariable(string name)318 		public string GetServerVariable (string name)
319 		{
320 			return modRequest.GetServerVariable (name);
321 		}
322 
SetStatusCodeLine(int code, string status)323 		public void SetStatusCodeLine (int code, string status)
324 		{
325 			modRequest.SetStatusCodeLine (code, status);
326 		}
327 
SetResponseHeader(string name, string value)328 		public void SetResponseHeader (string name, string value)
329 		{
330 			modRequest.SetResponseHeader (name, value);
331 		}
332 
SetOutputBuffering(bool doBuffer)333 		public void SetOutputBuffering (bool doBuffer)
334 		{
335 			modRequest.SetOutputBuffering (doBuffer);
336 		}
337 
338 		public bool HeadersSent {
339 			get { return modRequest.HeadersSent; }
340 		}
341 	}
342 }
343