// // System.Web.HttpRequestTest.cs - Unit tests for System.Web.HttpRequest // // Author: // Sebastien Pouliot // Miguel de Icaza // Gonzalo Paniagua Javier // // Copyright (C) 2005 Novell, Inc (http://www.novell.com) // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Text; using System.Web; using System.Collections.Specialized; using NUnit.Framework; using System.Diagnostics; namespace MonoTests.System.Web { [TestFixture] public class HttpRequestTest { #if NET_1_1 [Test] [ExpectedException (typeof (HttpRequestValidationException))] public void ValidateInput_XSS () { string problem = "http://server.com/attack2.aspx?test="; string decoded = HttpUtility.UrlDecode (problem); int n = decoded.IndexOf ('?'); HttpRequest request = new HttpRequest (null, decoded.Substring (0,n), decoded.Substring (n+1)); request.ValidateInput (); // the next statement throws Assert.AreEqual ("", request.QueryString ["test"], "QueryString"); } // Notes: // * this is to avoid a regression that would cause Mono to // fail again on item #2 of the XSS vulnerabilities listed at: // http://it-project.ru/andir/docs/aspxvuln/aspxvuln.en.xml // * The author notes that Microsoft has decided not to fix // this issue (hence the NotDotNet category). [Test] [Category ("NotDotNet")] [ExpectedException (typeof (HttpRequestValidationException))] public void ValidateInput_XSS_Unicode () { string problem = "http://server.com/attack2.aspx?test=%uff1cscript%uff1ealert('vulnerability')%uff1c/script%uff1e"; string decoded = HttpUtility.UrlDecode (problem); int n = decoded.IndexOf ('?'); HttpRequest request = new HttpRequest (null, decoded.Substring (0,n), decoded.Substring (n+1)); request.ValidateInput (); // the next statement throws Assert.AreEqual ("\xff1cscript\xff1ealert('vulnerability')\xff1c/script\xff1e", request.QueryString ["test"], "QueryString"); } // This has affected ASP.NET 1.1 but it seems fixed now // http://secunia.com/advisories/9716/ // http://weblogs.asp.net/kaevans/archive/2003/11/12/37169.aspx [Test] [ExpectedException (typeof (HttpRequestValidationException))] public void ValidateInput_XSS_Null () { string problem = "http://secunia.com/?test=<%00SCRIPT>alert(document.cookie)"; string decoded = HttpUtility.UrlDecode (problem); int n = decoded.IndexOf ('?'); HttpRequest request = new HttpRequest (null, decoded.Substring (0,n), decoded.Substring (n+1)); request.ValidateInput (); // the next statement throws Assert.AreEqual ("", request.QueryString ["test"], "QueryString"); } #endif // // Tests the properties from the simple constructor. [Test] public void Test_PropertiesSimpleConstructor () { string url = "http://www.gnome.org/"; string qs = "key=value&key2=value%32second"; HttpRequest r = new HttpRequest ("file", url, qs); Assert.AreEqual ("/?" + qs, r.RawUrl, "U1"); Assert.AreEqual (url, r.Url.ToString (), "U2"); r = new HttpRequest ("file", "http://www.gnome.org", qs); Assert.AreEqual (url, r.Url.ToString (), "U3"); qs = "a&b=1&c=d&e&b=2&d="; r = new HttpRequest ("file", url, qs); NameValueCollection nvc = r.QueryString; Assert.AreEqual ("a,e", nvc [null], "U4"); Assert.AreEqual ("1,2", nvc ["b"], "U5"); Assert.AreEqual ("d", nvc ["c"], "U5"); Assert.AreEqual ("", nvc ["d"], "U6"); Assert.AreEqual (4, nvc.Count, "U6"); Assert.AreEqual (null, r.ApplicationPath, "U7"); } [Test] [ExpectedException(typeof(ArgumentNullException))] public void Test_AccessToVars () { string url = "http://www.gnome.org/"; string qs = "key=value&key2=value%32second"; HttpRequest r = new HttpRequest ("file", url, qs); string s = r.PhysicalApplicationPath; } } [TestFixture] public class Test_HttpFakeRequest { class FakeHttpWorkerRequest : HttpWorkerRequest { public int return_kind; [Conditional ("REQUEST_TEST_VERY_VERBOSE")] void WhereAmI () { Console.WriteLine (Environment.StackTrace); } public FakeHttpWorkerRequest (int re) { return_kind = re; } public override string GetUriPath() { WhereAmI (); return "/uri.aspx"; } public override string GetQueryString() { WhereAmI (); switch (return_kind) { case 20: return null; case 16: return "key1=value1&key2=value2"; case 25: // HEAD case 30: // POST return "mapa.x=10&mapa.y=20"; case 26: // HEAD case 31: // POST return "mapa.x=10&mapa=20"; case 27: // GET return "mapa.x=10&mapa.y=20"; case 28: // GET return "mapa.x=10"; case 29: // GET return "mapa=10"; case 32: // GET return "mapa.x=pi&mapa.y=20"; default: return "GetQueryString"; } } public override string GetRawUrl() { WhereAmI (); return "/bb.aspx"; } public override string GetHttpVerbName() { WhereAmI (); if (return_kind == 25 || return_kind == 26) return "HEAD"; if (return_kind == 30 || return_kind == 31) return "POST"; return "GET"; } public override string GetHttpVersion() { WhereAmI (); return "HTTP/1.1"; } public override byte [] GetPreloadedEntityBody () { if (return_kind != 30 && return_kind != 31) return base.GetPreloadedEntityBody (); return Encoding.UTF8.GetBytes (GetQueryString ()); } public override bool IsEntireEntityBodyIsPreloaded () { if (return_kind != 30 && return_kind != 31) return base.IsEntireEntityBodyIsPreloaded (); return true; } public override int GetRemotePort() { return 1010; } public override string GetLocalAddress() { return "localhost"; } public override string GetAppPath () { return "AppPath"; } public override string GetRemoteName () { return "RemoteName"; } public override string GetRemoteAddress () { return "RemoteAddress"; } public override string GetServerName () { return "localhost"; } public override int GetLocalPort() { return 2020; } public override void SendStatus(int s, string x) { } public override void SendKnownResponseHeader(int x, string j) { } public override void SendUnknownResponseHeader(string a, string b) { } public override void SendResponseFromMemory(byte[] arr, int x) { } public override void SendResponseFromFile(string a, long b , long c) { WhereAmI (); } public override void SendResponseFromFile (IntPtr a, long b, long c) { } public override void FlushResponse(bool x) { } public override void EndOfRequest() { } public override string GetKnownRequestHeader (int index) { switch (index){ case HttpWorkerRequest.HeaderContentType: switch (return_kind){ case 1: return "text/plain"; case 2: return "text/plain; charset=latin1"; case 3: return "text/plain; charset=iso-8859-1"; case 4: return "text/plain; charset=\"iso-8859-1\""; case 5: return "text/plain; charset=\"iso-8859-1\" ; other"; case 30: case 31: return "application/x-www-form-urlencoded"; } break; case HttpWorkerRequest.HeaderContentLength: switch (return_kind){ case 0: return "1024"; case 1: return "-1024"; case 30: case 31: return GetQueryString ().Length.ToString (); case -1: return "Blah"; case -2: return ""; } break; case HttpWorkerRequest.HeaderCookie: switch (return_kind){ case 10: return "Key=Value"; case 11: return "Key="; case 12: return "Key=>"; case 13: return "Key=\xff1c"; case 14: return "Key=\xff1e"; } break; case HttpWorkerRequest.HeaderReferer: switch (return_kind){ case 1: return null; case 2: return "http://www.mono-project.com/test.aspx"; case 15: return "http://www.mono-project.com"; } break; case HttpWorkerRequest.HeaderUserAgent: switch (return_kind){ case 15: return "Mozilla/5.0 (X11; U; Linux i686; rv:1.7.3) Gecko/20040913 Firefox/0.10"; } break; case HttpWorkerRequest.HeaderAccept: switch (return_kind){ 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"; } break; case HttpWorkerRequest.HeaderAcceptLanguage: switch (return_kind){ case 21: return "en-us, en;q=0.5"; } break; } return ""; } public override string [][] GetUnknownRequestHeaders () { if (return_kind == 0) return new string [0][]; if (return_kind == 3){ string [][] x = new string [4][]; x [0] = new string [] { "k1", "v1" }; x [1] = new string [] { "k2", "v2" }; x [2] = new string [] { "k3", "v3" }; x [3] = new string [] { "k4", "v4" }; return x; } if (return_kind == 4){ // // This tests the bad values and the extra row with an error // string [][] x = new string [3][]; x [0] = new string [] { "k1", "" }; x [1] = new string [] { "k2", null }; x [2] = new string [] { "k3", " " }; return x; } if (return_kind == 2){ string [][] x = new string [2][]; x [0] = new string [] { "k1", "" }; // Returns an empty row. return x; } return null; } } HttpContext Cook (int re) { FakeHttpWorkerRequest f = new FakeHttpWorkerRequest (re); HttpContext c = new HttpContext (f); return c; } [Test] public void Test_BrokenContentLength () { HttpContext c = Cook (-1); Assert.AreEqual (0, c.Request.ContentLength, "C1"); c = Cook (-2); Assert.AreEqual (0, c.Request.ContentLength, "C2"); } [Test][ExpectedException(typeof(NullReferenceException))] public void Test_EmptyUnknownRow () { HttpContext c = Cook (2); NameValueCollection x = c.Request.Headers; } [Test] public void Test_RequestFields () { HttpContext c = Cook (1); #if NET_2_0 Assert.IsNull (c.Request.ApplicationPath, "A1"); #else Assert.AreEqual ("AppPath", c.Request.ApplicationPath, "A1"); #endif Assert.AreEqual ("text/plain", c.Request.ContentType, "A2"); c = Cook (0); Assert.AreEqual (1024, c.Request.ContentLength, "A3"); c = Cook (3); Assert.AreEqual ("iso-8859-1", c.Request.ContentEncoding.WebName, "A4"); NameValueCollection x = c.Request.Headers; Assert.AreEqual ("v1", x ["k1"], "K1"); Assert.AreEqual ("v2", x ["k2"], "K2"); Assert.AreEqual ("v3", x ["k3"], "K3"); Assert.AreEqual ("v4", x ["k4"], "K4"); Assert.AreEqual ("text/plain; charset=iso-8859-1", x ["Content-Type"], "K4"); Assert.AreEqual (5, x.Count, "K5"); c = Cook (2); Assert.AreEqual ("iso-8859-1", c.Request.ContentEncoding.WebName, "A5"); Assert.AreEqual ("text/plain; charset=latin1", c.Request.ContentType, "A5-1"); c = Cook (4); Assert.AreEqual ("iso-8859-1", c.Request.ContentEncoding.WebName, "A6"); x = c.Request.Headers; Assert.AreEqual ("", x ["k1"], "K6"); Assert.AreEqual (null, x ["k2"], "K7"); Assert.AreEqual (" ", x ["k3"], "K8"); Assert.AreEqual (4, x.Count, "K9"); c = Cook (5); Assert.AreEqual ("iso-8859-1", c.Request.ContentEncoding.WebName, "A7"); Assert.AreEqual ("RemoteName", c.Request.UserHostName, "A8"); Assert.AreEqual ("RemoteAddress", c.Request.UserHostAddress, "A9"); // Difference between Url property and RawUrl one: one is resolved, the other is not Assert.AreEqual ("/bb.aspx", c.Request.RawUrl, "A10"); Assert.AreEqual ("http://localhost:2020/uri.aspx?GetQueryString", c.Request.Url.ToString (), "A11"); } [Test] public void Test_Cookies () { HttpContext c; c = Cook (10); c.Request.ValidateInput (); Assert.AreEqual ("Value", c.Request.Cookies ["Key"].Value, "cookie1"); } [Test] [ExpectedException(typeof (HttpRequestValidationException))] public void Test_DangerousCookie () { HttpContext c; c = Cook (11); c.Request.ValidateInput (); object a = c.Request.Cookies; } [Test] [ExpectedException(typeof (HttpRequestValidationException))] [Category ("NotDotNet")] // doesn't work on 1.1 SP1 and 2.0 RC public void Test_DangerousCookie2 () { HttpContext c; c = Cook (12); c.Request.ValidateInput (); object a = c.Request.Cookies; } [Test] [ExpectedException(typeof (HttpRequestValidationException))] [Category ("NotDotNet")] // doesn't work on 1.1 SP1 and 2.0 RC public void Test_DangerousCookie3 () { HttpContext c; c = Cook (13); c.Request.ValidateInput (); object a = c.Request.Cookies; } [Test] [ExpectedException(typeof (HttpRequestValidationException))] [Category ("NotDotNet")] // doesn't work on 1.1 SP1 and 2.0 RC public void Test_DangerousCookie4 () { HttpContext c; c = Cook (14); c.Request.ValidateInput (); object a = c.Request.Cookies; } [Test] public void Test_MiscHeaders () { HttpContext c = Cook (15); // The uri ToString contains the trailing slash. Assert.AreEqual ("http://www.mono-project.com/", c.Request.UrlReferrer.ToString (), "ref1"); Assert.AreEqual ("Mozilla/5.0 (X11; U; Linux i686; rv:1.7.3) Gecko/20040913 Firefox/0.10", c.Request.UserAgent, "ref2"); // All the AcceptTypes and UserLanguages tests below here pass under MS c = Cook (20); string [] at = c.Request.AcceptTypes; string [] ul = c.Request.UserLanguages; Assert.IsNull (at, "AT1"); Assert.IsNull (ul, "UL1"); c = Cook (21); at = c.Request.AcceptTypes; Assert.IsNotNull (at, "AT2"); string [] expected = { "text/xml", "application/xml", "application/xhtml+xml", "text/html;q=0.9", "text/plain;q=0.8", "image/png", "*/*;q=0.5" }; Assert.AreEqual (expected.Length, at.Length, "AT3"); for (int i = expected.Length - 1; i >= 0; i--) Assert.AreEqual (expected [i], at [i], "AT" + (3 + i)); ul = c.Request.UserLanguages; Assert.IsNotNull (ul, "UL2"); expected = new string [] { "en-us", "en;q=0.5" }; Assert.AreEqual (expected.Length, ul.Length, "UL3"); for (int i = expected.Length - 1; i >= 0; i--) Assert.AreEqual (expected [i], ul [i], "UL" + (3 + i)); } [Test] public void Empty_WorkerRequest_QueryString () { HttpContext c = Cook (20); // // Checks that the following line does not throw an exception if // the querystring returned by the HttpWorkerRequest is null. // NameValueCollection nvc = c.Request.QueryString; } [Test] public void Leading_qm_in_QueryString () { HttpContext c = Cook (16); NameValueCollection nvc = c.Request.QueryString; foreach (string id in nvc.AllKeys) { if (id.StartsWith ("?")) Assert.Fail (id); } } [Test] public void TestPath () { HttpContext c = Cook (16); // This used to crash, ifolder exposed this string x = c.Request.Path; } [Test] public void MapImageCoordinatesHEAD () { HttpContext c = Cook (25); int [] coords = c.Request.MapImageCoordinates ("mapa"); Assert.IsNotNull (coords, "A1"); Assert.AreEqual (10, coords [0], "X"); Assert.AreEqual (20, coords [1], "Y"); c = Cook (26); coords = c.Request.MapImageCoordinates ("mapa"); Assert.AreEqual (null, coords, "coords"); } [Test] public void MapImageCoordinatesGET () { HttpContext c = Cook (27); int [] coords = c.Request.MapImageCoordinates ("mapa"); Assert.AreEqual (10, coords [0], "X"); Assert.AreEqual (20, coords [1], "Y"); coords = c.Request.MapImageCoordinates ("m"); Assert.AreEqual (null, coords, "coords1"); c = Cook (28); coords = c.Request.MapImageCoordinates ("mapa"); Assert.AreEqual (null, coords, "coords2"); c = Cook (29); coords = c.Request.MapImageCoordinates ("mapa"); Assert.AreEqual (null, coords, "coords3"); c = Cook (32); coords = c.Request.MapImageCoordinates ("mapa"); Assert.AreEqual (null, coords, "coords4"); } [Test] public void MapImageCoordinatesPOST () { HttpContext c = Cook (30); int [] coords = c.Request.MapImageCoordinates ("mapa"); Assert.IsNotNull (coords, "A1"); Assert.AreEqual (10, coords [0], "X"); Assert.AreEqual (20, coords [1], "Y"); c = Cook (31); coords = c.Request.MapImageCoordinates ("mapa"); Assert.AreEqual (null, coords, "coords2"); } [Test] public void TestReferer () { HttpContext c = Cook (1); Assert.AreEqual (null, c.Request.UrlReferrer, "REF1"); c = Cook (2); Assert.AreEqual ("http://www.mono-project.com/test.aspx", c.Request.UrlReferrer.ToString (), "REF1"); } [Test] public void NegativeContentLength () { HttpContext c = Cook (1); HttpRequest req = c.Request; Assert.AreEqual (0, req.ContentLength, "#01"); } } }