2010-02-16 Marek Habersack <mhabersack@novell.com>
[xsp.git] / src / Mono.WebServer.Apache / ModMonoRequest.cs
blob56033dc9a0c6d92e2bf4c365bff5d673d3cbf0f4
1 //
2 // ModMonoRequest.cs
3 //
4 // Authors:
5 // Daniel Lopez Ridruejo
6 // Gonzalo Paniagua Javier
7 //
8 // Copyright (c) 2002 Daniel Lopez Ridruejo.
9 // (c) 2002,2003 Ximian, Inc.
10 // All rights reserved.
11 // (C) Copyright 2004-2009 Novell, Inc. (http://www.novell.com)
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
20 //
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
23 //
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System;
34 using System.Collections;
35 using System.Diagnostics;
36 using System.IO;
37 using System.Net.Sockets;
38 using System.Runtime.InteropServices;
39 using System.Runtime.CompilerServices;
40 using System.Text;
41 using System.Threading;
42 using System.Collections.Generic;
44 namespace Mono.WebServer
46 enum Cmd
48 FIRST_COMMAND,
49 SEND_FROM_MEMORY = 0,
50 GET_SERVER_VARIABLES,
51 SET_RESPONSE_HEADERS,
52 GET_LOCAL_PORT,
53 CLOSE,
54 SHOULD_CLIENT_BLOCK,
55 SETUP_CLIENT_BLOCK,
56 GET_CLIENT_BLOCK,
57 SET_STATUS,
58 DECLINE_REQUEST,
59 NOT_FOUND,
60 IS_CONNECTED,
61 SEND_FILE,
62 SET_CONFIGURATION,
63 LAST_COMMAND
66 struct ModMonoConfig
68 bool changed;
69 bool outputBuffering;
71 public bool Changed {
72 get { return changed; }
73 set { changed = value; }
76 public bool OutputBuffering {
77 get { return outputBuffering; }
78 set {
79 changed |= (value != outputBuffering);
80 outputBuffering = value;
85 public class ModMonoRequest
87 const int MAX_STRING_SIZE = 1024 * 10;
88 const int INITIAL_MEMORY_STREAM_SIZE = 2048;
90 BinaryReader reader;
91 BinaryWriter writer;
92 MemoryStream reader_ms;
93 MemoryStream writer_ms;
94 bool got_server_vars;
95 Dictionary <string, string> serverVariables;
96 string verb;
97 string queryString;
98 string protocol;
99 string uri;
100 string vServerName;
101 string localAddress;
102 string remoteAddress;
103 string remoteName;
104 int localPort;
105 int remotePort;
106 int serverPort;
107 bool setupClientBlockCalled;
108 Dictionary <string, string> headers;
109 int clientBlock;
110 bool shutdown;
111 StringBuilder out_headers = new StringBuilder ();
112 bool headers_sent;
113 string physical_path;
114 ModMonoConfig mod_mono_config;
115 Socket client;
116 static bool use_libc;
118 static ModMonoRequest ()
120 if (Environment.GetEnvironmentVariable ("XSP_NO_LIBC") != null)
121 return;
123 try {
124 string os = "";
125 using (StreamReader sr = new StreamReader (File.OpenRead ("/proc/sys/kernel/ostype")))
126 os = sr.ReadToEnd ();
127 use_libc = os.StartsWith ("Linux");
128 } catch {
132 public ModMonoRequest (Socket client)
134 mod_mono_config.OutputBuffering = true;
135 this.client = client;
136 reader_ms = new MemoryStream (INITIAL_MEMORY_STREAM_SIZE);
137 writer_ms = new MemoryStream (INITIAL_MEMORY_STREAM_SIZE);
138 reader = new BinaryReader (reader_ms);
139 writer = new BinaryWriter (writer_ms);
140 serverVariables = new Dictionary <string, string> (StringComparer.OrdinalIgnoreCase);
141 GetInitialData ();
144 public bool HeadersSent {
145 get { return headers_sent; }
148 public bool ShuttingDown {
149 get { return shutdown; }
152 void FillBuffer (int count)
154 // This will "reset" the stream
155 int capacity = reader_ms.Capacity;
156 if (capacity > count)
157 reader_ms.Capacity = capacity;
158 else
159 reader_ms.Capacity = count;
161 reader_ms.Seek (0, SeekOrigin.Begin);
162 byte[] buffer = reader_ms.GetBuffer ();
163 int received = client.Receive (buffer, count, SocketFlags.None);
164 reader_ms.SetLength (received);
167 void Send ()
169 client.Send (writer_ms.GetBuffer (), (int) writer_ms.Length, SocketFlags.None);
170 writer_ms.Position = 0;
171 writer_ms.SetLength (0);
174 // KEEP IN SYNC WITH mod_mono.h!!
175 const byte protocol_version = 9;
176 void GetInitialData ()
178 FillBuffer (5);
180 byte cmd = reader.ReadByte ();
181 shutdown = (cmd == 0);
182 if (shutdown)
183 return;
185 if (cmd != protocol_version) {
186 string msg = String.Format ("mod_mono and xsp have different versions. Expected '{0}', got {1}", protocol_version, cmd);
187 Console.WriteLine (msg);
188 Console.Error.WriteLine (msg);
189 throw new InvalidOperationException (msg);
192 Int32 dataSize = reader.ReadInt32 ();
193 FillBuffer (dataSize);
195 verb = ReadString ();
196 vServerName = ReadString ();
197 uri = ReadString ();
198 queryString = ReadString ();
199 protocol = ReadString ();
200 localAddress = ReadString ();
201 serverPort = reader.ReadInt32 ();
202 remoteAddress = ReadString ();
203 remotePort = reader.ReadInt32 ();
204 remoteName = ReadString ();
205 reader.ReadInt32 (); // This is autoApp!!! (unused!?
206 int nheaders = reader.ReadInt32 ();
207 headers = new Dictionary <string, string> (StringComparer.OrdinalIgnoreCase);
209 for (int i = 0; i < nheaders; i++) {
210 string key = ReadString ();
211 if (headers.ContainsKey (key)) {
212 Console.WriteLine ("WARNING: duplicate header found! Overwriting old value with the new one.");
213 headers [key] = ReadString ();
214 } else
215 headers.Add (key, ReadString ());
218 if (reader.ReadByte () != 0)
219 physical_path = ReadString ();
222 string ReadString ()
224 int size = reader.ReadInt32 ();
225 if (size < 0 || size > MAX_STRING_SIZE)
226 throw new ArgumentOutOfRangeException ("size", "Abnormal string size " + size);
228 byte [] buf = new byte [size];
229 string s;
230 if (size != 0) {
231 int chunk;
232 int total = 0;
233 do {
234 chunk = reader.Read (buf, total, size - total);
235 if (chunk == 0)
236 throw new IOException ("Lost connection with mod_mono");
237 if (chunk > 0)
238 total += chunk;
239 } while ((chunk > 0 && total < size));
241 s = Encoding.UTF8.GetString (buf);
242 } else {
243 s = "";
245 return s;
248 void WriteString (string s)
250 byte [] bytes = Encoding.UTF8.GetBytes (s);
251 writer.Write (bytes.Length);
252 writer.Write (bytes);
255 public void Decline ()
257 writer.Write ((int) Cmd.DECLINE_REQUEST);
258 Send ();
261 public void NotFound ()
263 writer.Write ((int) Cmd.NOT_FOUND);
264 Send ();
267 public string GetProtocol ()
269 return protocol;
272 public string GetHttpVerbName ()
274 return verb;
277 public void SendResponseFromMemory (IntPtr ptr, int length)
279 BufferConfig ();
280 BufferHeaders ();
281 writer.Write ((int) Cmd.SEND_FROM_MEMORY);
282 writer.Write (length);
283 Send ();
284 if (use_libc) {
285 Send (ptr, length);
286 return;
289 while (length > 0) {
290 if (writer_ms.Position != 0)
291 throw new Exception ("Should not happen... We called Send()!");
293 int size = System.Math.Min (16384, length);
294 if (writer_ms.Capacity < size)
295 writer_ms.Capacity = size;
297 byte [] buf = writer_ms.GetBuffer ();
298 Marshal.Copy (ptr, buf, 0, size);
299 length -= size;
300 client.Send (buf, size, SocketFlags.None);
304 unsafe int Send (IntPtr ptr, int len)
306 int total = 0;
307 byte *bptr = (byte *) ptr.ToPointer ();
308 while (total < len) {
309 // 0x4000 no sigpipe
310 int n = send_libc (client.Handle.ToInt32 (), bptr + total, (IntPtr) (len - total), (int) 0x4000);
311 if (n >= 0) {
312 total += n;
313 } else if (Marshal.GetLastWin32Error () != 4 /* EINTR */) {
314 throw new IOException ();
318 return total;
321 [DllImport ("libc", SetLastError=true, EntryPoint="send")]
322 unsafe extern static int send_libc (int s, byte *buffer, IntPtr len, int flags);
324 public void SendResponseFromMemory (byte [] data, int position, int length)
326 BufferConfig ();
327 BufferHeaders ();
328 writer.Write ((int) Cmd.SEND_FROM_MEMORY);
329 writer.Write (length);
330 Send ();
331 client.Send (data, position, length, SocketFlags.None);
334 public void SendFile (string filename)
336 BufferConfig ();
337 BufferHeaders ();
338 writer.Write ((int) Cmd.SEND_FILE);
339 WriteString (filename);
340 Send ();
343 void BufferConfig ()
345 if (!mod_mono_config.Changed)
346 return;
348 mod_mono_config.Changed = false;
349 writer.Write ((int) Cmd.SET_CONFIGURATION);
350 writer.Write (Convert.ToByte (mod_mono_config.OutputBuffering));
353 void BufferHeaders ()
355 if (headers_sent)
356 return;
358 writer.Write ((int) Cmd.SET_RESPONSE_HEADERS);
359 WriteString (out_headers.ToString ());
360 out_headers = null;
361 headers_sent = true;
364 public void SetResponseHeader (string name, string value)
366 if (!headers_sent)
367 out_headers.AppendFormat ("{0}\0{1}\0", name, value);
370 public void SetOutputBuffering (bool doBuffer)
372 mod_mono_config.OutputBuffering = doBuffer;
375 public string [] GetAllHeaders ()
377 ICollection k = headers.Keys;
378 string [] keys = new string [k.Count];
379 k.CopyTo (keys, 0);
380 return keys;
383 public string [] GetAllHeaderValues ()
385 ICollection v = headers.Values;
386 string [] values = new string [v.Count];
387 v.CopyTo (values, 0);
388 return values;
391 public string GetRequestHeader (string name)
393 if (headers.ContainsKey (name))
394 return headers [name] as string;
395 return null;
398 void GetServerVariables ()
400 writer.Write ((int) Cmd.GET_SERVER_VARIABLES);
401 Send ();
403 FillBuffer (4);
404 int blockSize = reader.ReadInt32 ();
406 FillBuffer (blockSize);
407 int nvars = reader.ReadInt32 ();
408 while (nvars > 0) {
409 string key = ReadString ();
410 if (serverVariables.ContainsKey (key)) {
411 Console.WriteLine ("WARNING! Duplicate server variable found. Overwriting old value with the new one.");
412 serverVariables [key] = ReadString ();
413 } else
414 serverVariables.Add (key, ReadString ());
416 nvars--;
419 got_server_vars = true;
422 public string GetServerVariable (string name)
424 if (!got_server_vars)
425 GetServerVariables ();
427 if (serverVariables.ContainsKey (name))
428 return (string) serverVariables [name];
430 return null;
433 public string GetPhysicalPath ()
435 return physical_path;
438 public string GetUri ()
440 return uri;
443 public string GetVirtualServerName ()
445 return vServerName;
448 public string GetQueryString ()
450 return queryString;
453 // May be different from Connection.GetLocalPort depending on Apache configuration,
454 // for things like self referential URLs, etc.
455 public int GetServerPort ()
457 return serverPort;
460 public string GetRemoteAddress ()
462 return remoteAddress;
465 public string GetRemoteName ()
467 return remoteName;
470 public string GetLocalAddress ()
472 return localAddress;
475 public int GetLocalPort ()
477 if (localPort != 0)
478 return localPort;
480 writer.Write ((int) Cmd.GET_LOCAL_PORT);
481 Send ();
482 FillBuffer (4);
483 localPort = reader.ReadInt32 ();
484 return localPort;
487 public int GetRemotePort ()
489 return remotePort;
492 public void Close ()
494 BufferHeaders ();
495 writer.Write ((int) Cmd.CLOSE);
496 Send ();
499 public int SetupClientBlock ()
501 if (setupClientBlockCalled)
502 return clientBlock;
504 BufferConfig ();
505 setupClientBlockCalled = true;
506 writer.Write ((int) Cmd.SETUP_CLIENT_BLOCK);
507 Send ();
508 FillBuffer (4);
509 int i = reader.ReadInt32 ();
510 clientBlock = i;
511 return i;
514 public bool IsConnected ()
516 writer.Write ((int) Cmd.IS_CONNECTED);
517 Send ();
518 FillBuffer (4);
519 int i = reader.ReadInt32 ();
520 return (i != 0);
523 public bool ShouldClientBlock ()
525 writer.Write ((int) Cmd.SHOULD_CLIENT_BLOCK);
526 Send ();
527 FillBuffer (4);
528 int i = reader.ReadInt32 ();
529 return (i == 0);
532 public int GetClientBlock ([Out] byte [] bytes, int position, int size)
534 if (SetupClientBlock () != 0) return 0;
537 * turns out that that GET_CLIENT_BLOCK (ap_get_client_block) can
538 * return -1 if a socket is closed
540 writer.Write ((int) Cmd.GET_CLIENT_BLOCK);
541 writer.Write (size);
542 Send ();
544 FillBuffer (4);
545 int i = reader.ReadInt32 ();
546 if (i == -1)
547 return -1;
549 if (i > size)
550 throw new Exception ("Houston...");
552 FillBuffer (i);
553 return reader.Read (bytes, position, i);
556 public void SetStatusCodeLine (int code, string status)
558 writer.Write ((int) Cmd.SET_STATUS);
559 writer.Write (code);
560 WriteString (status);
561 Send ();