2 // System.Web.HttpRequestTest.cs - Unit tests for System.Web.HttpRequest
5 // Sebastien Pouliot <sebastien@ximian.com>
6 // Miguel de Icaza <miguel@novell.com>
7 // Gonzalo Paniagua Javier <gonzalo@novell.com>
9 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.Collections.Specialized;
34 using NUnit.Framework;
35 using System.Diagnostics;
37 namespace MonoTests.System.Web {
40 public class HttpRequestTest {
44 [ExpectedException (typeof (HttpRequestValidationException))]
45 public void ValidateInput_XSS ()
47 string problem = "http://server.com/attack2.aspx?test=<script>alert('vulnerability')</script>";
48 string decoded = HttpUtility.UrlDecode (problem);
49 int n = decoded.IndexOf ('?');
50 HttpRequest request = new HttpRequest (null, decoded.Substring (0,n), decoded.Substring (n+1));
51 request.ValidateInput ();
52 // the next statement throws
53 Assert.AreEqual ("<script>alert('vulnerability')</script>", request.QueryString ["test"], "QueryString");
57 // * this is to avoid a regression that would cause Mono to
58 // fail again on item #2 of the XSS vulnerabilities listed at:
59 // http://it-project.ru/andir/docs/aspxvuln/aspxvuln.en.xml
60 // * The author notes that Microsoft has decided not to fix
61 // this issue (hence the NotDotNet category).
64 [Category ("NotDotNet")]
65 [ExpectedException (typeof (HttpRequestValidationException))]
66 public void ValidateInput_XSS_Unicode ()
68 string problem = "http://server.com/attack2.aspx?test=%uff1cscript%uff1ealert('vulnerability')%uff1c/script%uff1e";
69 string decoded = HttpUtility.UrlDecode (problem);
70 int n = decoded.IndexOf ('?');
71 HttpRequest request = new HttpRequest (null, decoded.Substring (0,n), decoded.Substring (n+1));
72 request.ValidateInput ();
73 // the next statement throws
74 Assert.AreEqual ("\xff1cscript\xff1ealert('vulnerability')\xff1c/script\xff1e", request.QueryString ["test"], "QueryString");
77 // This has affected ASP.NET 1.1 but it seems fixed now
78 // http://secunia.com/advisories/9716/
79 // http://weblogs.asp.net/kaevans/archive/2003/11/12/37169.aspx
81 [ExpectedException (typeof (HttpRequestValidationException))]
82 public void ValidateInput_XSS_Null ()
84 string problem = "http://secunia.com/?test=<%00SCRIPT>alert(document.cookie)</SCRIPT>";
85 string decoded = HttpUtility.UrlDecode (problem);
86 int n = decoded.IndexOf ('?');
87 HttpRequest request = new HttpRequest (null, decoded.Substring (0,n), decoded.Substring (n+1));
88 request.ValidateInput ();
89 // the next statement throws
90 Assert.AreEqual ("<SCRIPT>alert(document.cookie)</SCRIPT>", request.QueryString ["test"], "QueryString");
94 // Tests the properties from the simple constructor.
96 public void Test_PropertiesSimpleConstructor ()
98 string url = "http://www.gnome.org/";
99 string qs = "key=value&key2=value%32second";
101 HttpRequest r = new HttpRequest ("file", url, qs);
103 Assert.AreEqual ("/?" + qs, r.RawUrl, "U1");
104 Assert.AreEqual (url, r.Url.ToString (), "U2");
106 r = new HttpRequest ("file", "http://www.gnome.org", qs);
107 Assert.AreEqual (url, r.Url.ToString (), "U3");
109 qs = "a&b=1&c=d&e&b=2&d=";
110 r = new HttpRequest ("file", url, qs);
111 NameValueCollection nvc = r.QueryString;
113 Assert.AreEqual ("a,e", nvc [null], "U4");
114 Assert.AreEqual ("1,2", nvc ["b"], "U5");
115 Assert.AreEqual ("d", nvc ["c"], "U5");
116 Assert.AreEqual ("", nvc ["d"], "U6");
117 Assert.AreEqual (4, nvc.Count, "U6");
119 Assert.AreEqual (null, r.ApplicationPath, "U7");
123 [ExpectedException(typeof(ArgumentNullException))]
124 public void Test_AccessToVars ()
126 string url = "http://www.gnome.org/";
127 string qs = "key=value&key2=value%32second";
129 HttpRequest r = new HttpRequest ("file", url, qs);
130 string s = r.PhysicalApplicationPath;
135 public class Test_HttpFakeRequest {
136 class FakeHttpWorkerRequest : HttpWorkerRequest {
137 public int return_kind;
139 [Conditional ("REQUEST_TEST_VERY_VERBOSE")]
142 Console.WriteLine (Environment.StackTrace);
146 public FakeHttpWorkerRequest (int re)
151 public override string GetUriPath()
157 public override string GetQueryString()
161 switch (return_kind) {
165 return "key1=value1&key2=value2";
168 return "mapa.x=10&mapa.y=20";
171 return "mapa.x=10&mapa=20";
173 return "mapa.x=10&mapa.y=20";
179 return "mapa.x=pi&mapa.y=20";
181 return "PlainString";
183 return "Plain&Arg=1";
185 return "GetQueryString";
189 public override string GetRawUrl()
195 public override string GetHttpVerbName()
198 if (return_kind == 25 || return_kind == 26)
200 if (return_kind == 30 || return_kind == 31)
205 public override string GetHttpVersion()
211 public override byte [] GetPreloadedEntityBody ()
213 if (return_kind != 30 && return_kind != 31)
214 return base.GetPreloadedEntityBody ();
216 return Encoding.UTF8.GetBytes (GetQueryString ());
219 public override bool IsEntireEntityBodyIsPreloaded ()
221 if (return_kind != 30 && return_kind != 31)
222 return base.IsEntireEntityBodyIsPreloaded ();
227 public override int GetRemotePort()
232 public override string GetLocalAddress()
237 public override string GetAppPath ()
242 public override string GetRemoteName ()
247 public override string GetRemoteAddress ()
249 return "RemoteAddress";
252 public override string GetServerName ()
257 public override int GetLocalPort()
262 public override void SendStatus(int s, string x)
266 public override void SendKnownResponseHeader(int x, string j)
270 public override void SendUnknownResponseHeader(string a, string b)
274 public override void SendResponseFromMemory(byte[] arr, int x)
278 public override void SendResponseFromFile(string a, long b , long c)
283 public override void SendResponseFromFile (IntPtr a, long b, long c)
287 public override void FlushResponse(bool x)
291 public override void EndOfRequest() {
294 public override string GetKnownRequestHeader (int index)
297 case HttpWorkerRequest.HeaderContentType:
298 switch (return_kind){
299 case 1: return "text/plain";
300 case 2: return "text/plain; charset=latin1";
301 case 3: return "text/plain; charset=iso-8859-1";
302 case 4: return "text/plain; charset=\"iso-8859-1\"";
303 case 5: return "text/plain; charset=\"iso-8859-1\" ; other";
306 return "application/x-www-form-urlencoded";
310 case HttpWorkerRequest.HeaderContentLength:
311 switch (return_kind){
312 case 0: return "1024";
313 case 1: return "-1024";
316 return GetQueryString ().Length.ToString ();
317 case -1: return "Blah";
322 case HttpWorkerRequest.HeaderCookie:
323 switch (return_kind){
324 case 10: return "Key=Value";
325 case 11: return "Key=<value>";
326 case 12: return "Key=>";
327 case 13: return "Key=\xff1c";
328 case 14: return "Key=\xff1e";
331 case HttpWorkerRequest.HeaderReferer:
332 switch (return_kind){
334 case 2: return "http://www.mono-project.com/test.aspx";
335 case 15: return "http://www.mono-project.com";
338 case HttpWorkerRequest.HeaderUserAgent:
339 switch (return_kind){
340 case 15: return "Mozilla/5.0 (X11; U; Linux i686; rv:1.7.3) Gecko/20040913 Firefox/0.10";
343 case HttpWorkerRequest.HeaderAccept:
344 switch (return_kind){
345 case 21: return "text/xml,application/xml, application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5";
348 case HttpWorkerRequest.HeaderAcceptLanguage:
349 switch (return_kind){
350 case 21: return "en-us, en;q=0.5";
357 public override string [][] GetUnknownRequestHeaders ()
359 if (return_kind == 0)
360 return new string [0][];
362 if (return_kind == 3){
363 string [][] x = new string [4][];
364 x [0] = new string [] { "k1", "v1" };
365 x [1] = new string [] { "k2", "v2" };
366 x [2] = new string [] { "k3", "v3" };
367 x [3] = new string [] { "k4", "v4" };
372 if (return_kind == 4){
374 // This tests the bad values and the extra row with an error
376 string [][] x = new string [3][];
377 x [0] = new string [] { "k1", "" };
378 x [1] = new string [] { "k2", null };
379 x [2] = new string [] { "k3", " " };
384 if (return_kind == 2){
385 string [][] x = new string [2][];
386 x [0] = new string [] { "k1", "" };
388 // Returns an empty row.
396 HttpContext Cook (int re)
398 FakeHttpWorkerRequest f = new FakeHttpWorkerRequest (re);
399 HttpContext c = new HttpContext (f);
404 [Test] public void Test_BrokenContentLength ()
406 HttpContext c = Cook (-1);
407 Assert.AreEqual (0, c.Request.ContentLength, "C1");
410 Assert.AreEqual (0, c.Request.ContentLength, "C2");
414 [Test][ExpectedException(typeof(NullReferenceException))]
415 public void Test_EmptyUnknownRow ()
417 HttpContext c = Cook (2);
418 NameValueCollection x = c.Request.Headers;
421 [Test] public void Test_RequestFields ()
423 HttpContext c = Cook (1);
426 Assert.IsNull (c.Request.ApplicationPath, "A1");
428 Assert.AreEqual ("AppPath", c.Request.ApplicationPath, "A1");
430 Assert.AreEqual ("text/plain", c.Request.ContentType, "A2");
433 Assert.AreEqual (1024, c.Request.ContentLength, "A3");
436 Assert.AreEqual ("iso-8859-1", c.Request.ContentEncoding.WebName, "A4");
437 NameValueCollection x = c.Request.Headers;
439 Assert.AreEqual ("v1", x ["k1"], "K1");
440 Assert.AreEqual ("v2", x ["k2"], "K2");
441 Assert.AreEqual ("v3", x ["k3"], "K3");
442 Assert.AreEqual ("v4", x ["k4"], "K4");
443 Assert.AreEqual ("text/plain; charset=iso-8859-1", x ["Content-Type"], "K4");
444 Assert.AreEqual (5, x.Count, "K5");
447 Assert.AreEqual ("iso-8859-1", c.Request.ContentEncoding.WebName, "A5");
448 Assert.AreEqual ("text/plain; charset=latin1", c.Request.ContentType, "A5-1");
451 Assert.AreEqual ("iso-8859-1", c.Request.ContentEncoding.WebName, "A6");
452 x = c.Request.Headers;
453 Assert.AreEqual ("", x ["k1"], "K6");
454 Assert.AreEqual (null, x ["k2"], "K7");
455 Assert.AreEqual (" ", x ["k3"], "K8");
456 Assert.AreEqual (4, x.Count, "K9");
459 Assert.AreEqual ("iso-8859-1", c.Request.ContentEncoding.WebName, "A7");
461 Assert.AreEqual ("RemoteName", c.Request.UserHostName, "A8");
462 Assert.AreEqual ("RemoteAddress", c.Request.UserHostAddress, "A9");
464 // Difference between Url property and RawUrl one: one is resolved, the other is not
465 Assert.AreEqual ("/bb.aspx", c.Request.RawUrl, "A10");
466 Assert.AreEqual ("http://localhost:2020/uri.aspx?GetQueryString", c.Request.Url.ToString (), "A11");
469 [Test] public void Test_Cookies ()
474 c.Request.ValidateInput ();
475 Assert.AreEqual ("Value", c.Request.Cookies ["Key"].Value, "cookie1");
479 [ExpectedException(typeof (HttpRequestValidationException))]
480 public void Test_DangerousCookie ()
485 c.Request.ValidateInput ();
486 object a = c.Request.Cookies;
490 [ExpectedException(typeof (HttpRequestValidationException))]
491 [Category ("NotDotNet")] // doesn't work on 1.1 SP1 and 2.0 RC
492 public void Test_DangerousCookie2 ()
497 c.Request.ValidateInput ();
498 object a = c.Request.Cookies;
502 [ExpectedException(typeof (HttpRequestValidationException))]
503 [Category ("NotDotNet")] // doesn't work on 1.1 SP1 and 2.0 RC
504 public void Test_DangerousCookie3 ()
509 c.Request.ValidateInput ();
510 object a = c.Request.Cookies;
514 [ExpectedException(typeof (HttpRequestValidationException))]
515 [Category ("NotDotNet")] // doesn't work on 1.1 SP1 and 2.0 RC
516 public void Test_DangerousCookie4 ()
521 c.Request.ValidateInput ();
522 object a = c.Request.Cookies;
526 public void Test_MiscHeaders ()
528 HttpContext c = Cook (15);
530 // The uri ToString contains the trailing slash.
531 Assert.AreEqual ("http://www.mono-project.com/", c.Request.UrlReferrer.ToString (), "ref1");
533 Assert.AreEqual ("Mozilla/5.0 (X11; U; Linux i686; rv:1.7.3) Gecko/20040913 Firefox/0.10", c.Request.UserAgent, "ref2");
535 // All the AcceptTypes and UserLanguages tests below here pass under MS
537 string [] at = c.Request.AcceptTypes;
538 string [] ul = c.Request.UserLanguages;
539 Assert.IsNull (at, "AT1");
540 Assert.IsNull (ul, "UL1");
542 at = c.Request.AcceptTypes;
543 Assert.IsNotNull (at, "AT2");
544 string [] expected = { "text/xml", "application/xml", "application/xhtml+xml", "text/html;q=0.9",
545 "text/plain;q=0.8", "image/png", "*/*;q=0.5" };
547 Assert.AreEqual (expected.Length, at.Length, "AT3");
548 for (int i = expected.Length - 1; i >= 0; i--)
549 Assert.AreEqual (expected [i], at [i], "AT" + (3 + i));
551 ul = c.Request.UserLanguages;
552 Assert.IsNotNull (ul, "UL2");
553 expected = new string [] { "en-us", "en;q=0.5" };
555 Assert.AreEqual (expected.Length, ul.Length, "UL3");
556 for (int i = expected.Length - 1; i >= 0; i--)
557 Assert.AreEqual (expected [i], ul [i], "UL" + (3 + i));
562 public void Empty_WorkerRequest_QueryString ()
564 HttpContext c = Cook (20);
567 // Checks that the following line does not throw an exception if
568 // the querystring returned by the HttpWorkerRequest is null.
570 NameValueCollection nvc = c.Request.QueryString;
574 public void Test_QueryString_ToString ()
576 HttpContext c = Cook (50);
578 Assert.AreEqual (c.Request.QueryString.ToString (), "PlainString", "QTS#1");
581 Assert.AreEqual (c.Request.QueryString.ToString (), "Plain&Arg=1", "QTS#2");
585 public void Leading_qm_in_QueryString ()
587 HttpContext c = Cook (16);
588 NameValueCollection nvc = c.Request.QueryString;
589 foreach (string id in nvc.AllKeys) {
590 if (id.StartsWith ("?"))
596 public void TestPath ()
598 HttpContext c = Cook (16);
600 // This used to crash, ifolder exposed this
601 string x = c.Request.Path;
605 public void MapImageCoordinatesHEAD ()
607 HttpContext c = Cook (25);
608 int [] coords = c.Request.MapImageCoordinates ("mapa");
609 Assert.IsNotNull (coords, "A1");
610 Assert.AreEqual (10, coords [0], "X");
611 Assert.AreEqual (20, coords [1], "Y");
614 coords = c.Request.MapImageCoordinates ("mapa");
615 Assert.AreEqual (null, coords, "coords");
619 public void MapImageCoordinatesGET ()
621 HttpContext c = Cook (27);
622 int [] coords = c.Request.MapImageCoordinates ("mapa");
623 Assert.AreEqual (10, coords [0], "X");
624 Assert.AreEqual (20, coords [1], "Y");
626 coords = c.Request.MapImageCoordinates ("m");
627 Assert.AreEqual (null, coords, "coords1");
630 coords = c.Request.MapImageCoordinates ("mapa");
631 Assert.AreEqual (null, coords, "coords2");
633 coords = c.Request.MapImageCoordinates ("mapa");
634 Assert.AreEqual (null, coords, "coords3");
636 coords = c.Request.MapImageCoordinates ("mapa");
637 Assert.AreEqual (null, coords, "coords4");
641 public void MapImageCoordinatesPOST ()
643 HttpContext c = Cook (30);
644 int [] coords = c.Request.MapImageCoordinates ("mapa");
645 Assert.IsNotNull (coords, "A1");
646 Assert.AreEqual (10, coords [0], "X");
647 Assert.AreEqual (20, coords [1], "Y");
650 coords = c.Request.MapImageCoordinates ("mapa");
651 Assert.AreEqual (null, coords, "coords2");
655 public void TestReferer ()
657 HttpContext c = Cook (1);
659 Assert.AreEqual (null, c.Request.UrlReferrer, "REF1");
662 Assert.AreEqual ("http://www.mono-project.com/test.aspx", c.Request.UrlReferrer.ToString (), "REF1");
667 public void NegativeContentLength ()
669 HttpContext c = Cook (1);
670 HttpRequest req = c.Request;
671 Assert.AreEqual (0, req.ContentLength, "#01");
675 // This class is defined here to make it easy to create fake
676 // HttpWorkerRequest-derived classes by only overriding the methods
677 // necessary for testing.
678 class BaseFakeHttpWorkerRequest : HttpWorkerRequest
680 public override void EndOfRequest()
684 public override void FlushResponse(bool finalFlush)
688 public override string GetHttpVerbName()
693 public override string GetHttpVersion()
698 public override string GetLocalAddress()
703 public override int GetLocalPort()
708 public override string GetQueryString()
713 public override string GetRawUrl()
715 string rawUrl = GetUriPath();
716 string queryString = GetQueryString();
717 if (queryString != null && queryString.Length > 0)
719 rawUrl += "?" + queryString;
724 public override string GetRemoteAddress()
729 public override int GetRemotePort()
734 public override string GetUriPath()
736 return "default.aspx";
739 public override void SendKnownResponseHeader(int index, string value)
743 public override void SendResponseFromFile(IntPtr handle, long offset, long length)
747 public override void SendResponseFromFile(string filename, long offset, long length)
751 public override void SendResponseFromMemory(byte[] data, int length)
755 public override void SendStatus(int statusCode, string statusDescription)
759 public override void SendUnknownResponseHeader(string name, string value)
764 // This test ensures accessing the Form property does not throw an
765 // exception when the length of data in the request exceeds the length
766 // as reported by the Content-Length header. This bug was discovered
767 // with an AJAX application using XMLHttpRequest to POST back to the
768 // server. The Content-Length header was two bytes less than the length
769 // of the buffer returned from GetPreloadedEntityBody. This was causing
770 // an exception to be thrown by Mono because it was trying to allocate
771 // a buffer that was -2 bytes in length.
773 public class Test_UrlEncodedBodyWithExtraCRLF
775 class FakeHttpWorkerRequest : BaseFakeHttpWorkerRequest
777 // This string is 9 bytes in length. That's 2 more than
778 // the Content-Length header says it should be.
779 string data = "foo=bar\r\n";
781 public override string GetKnownRequestHeader(int index)
785 case HttpWorkerRequest.HeaderContentLength:
786 return (data.Length - 2).ToString();
787 case HttpWorkerRequest.HeaderContentType:
788 return "application/x-www-form-urlencoded";
793 public override byte[] GetPreloadedEntityBody()
795 return Encoding.ASCII.GetBytes(data);
799 HttpContext context = null;
804 HttpWorkerRequest workerRequest = new FakeHttpWorkerRequest();
805 context = new HttpContext(workerRequest);
809 public void ContentLength()
811 Assert.AreEqual(7, context.Request.ContentLength);
815 public void Form_Count()
817 Assert.AreEqual(1, context.Request.Form.Count);
821 [Category ("NotDotNet")]
822 public void Form_Item()
824 // I would have expected the extra two characters to be stripped
825 // but Microsoft's CLR keeps them so Mono should, too.
826 //Assert.AreEqual("bar\r\n", context.Request.Form["foo"]);
827 Assert.AreEqual("bar", context.Request.Form["foo"]);
831 // This test ensures the HttpRequet object's Form property gets
832 // properly constructed and populated when the Content-Type header
833 // includes a charset parameter and that the charset parameter is
836 public class Test_UrlEncodedBodyWithUtf8CharsetParameter
838 class FakeHttpWorkerRequest : BaseFakeHttpWorkerRequest
840 // The two funny-looking characters are really a single
841 // accented "a" character encoded in UTF-8.
842 string data = "foo=b%C3%A1r";
844 public override string GetKnownRequestHeader(int index)
848 case HttpWorkerRequest.HeaderContentLength:
849 return data.Length.ToString();
850 case HttpWorkerRequest.HeaderContentType:
851 return "application/x-www-form-urlencoded; charset=utf-8";
856 public override byte[] GetPreloadedEntityBody()
858 return Encoding.ASCII.GetBytes(data);
862 HttpContext context = null;
867 HttpWorkerRequest workerRequest = new FakeHttpWorkerRequest();
868 context = new HttpContext(workerRequest);
872 public void Form_Count()
874 Assert.AreEqual(1, context.Request.Form.Count);
878 public void Form_Item()
880 Assert.AreEqual("b\xE1r", context.Request.Form["foo"]);