diff --git a/OpenSim/Region/CoreModules/Scripting/LSLHttp/UrlModule.cs b/OpenSim/Region/CoreModules/Scripting/LSLHttp/UrlModule.cs index 38633a0e6d..45c5c83b27 100644 --- a/OpenSim/Region/CoreModules/Scripting/LSLHttp/UrlModule.cs +++ b/OpenSim/Region/CoreModules/Scripting/LSLHttp/UrlModule.cs @@ -114,17 +114,22 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp /// private Dictionary m_UrlMap = new Dictionary(); - /// - /// Maximum number of external urls that can be set up by this module. - /// - private int m_TotalUrls = 100; - private uint m_HttpsPort = 0; private IHttpServer m_HttpServer = null; private IHttpServer m_HttpsServer = null; public string ExternalHostNameForLSL { get; private set; } + /// + /// The default maximum number of urls + /// + public const int DefaultTotalUrls = 100; + + /// + /// Maximum number of external urls that can be set up by this module. + /// + public int TotalUrls { get; set; } + public Type ReplaceableInterface { get { return null; } @@ -155,7 +160,9 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp IConfig llFunctionsConfig = config.Configs["LL-Functions"]; if (llFunctionsConfig != null) - m_TotalUrls = llFunctionsConfig.GetInt("max_external_urls_per_simulator", m_TotalUrls); + TotalUrls = llFunctionsConfig.GetInt("max_external_urls_per_simulator", DefaultTotalUrls); + else + TotalUrls = DefaultTotalUrls; } public void PostInitialise() @@ -206,7 +213,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp lock (m_UrlMap) { - if (m_UrlMap.Count >= m_TotalUrls) + if (m_UrlMap.Count >= TotalUrls) { engine.PostScriptEvent(itemID, "http_request", new Object[] { urlcode.ToString(), "URL_REQUEST_DENIED", "" }); return urlcode; @@ -251,7 +258,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp lock (m_UrlMap) { - if (m_UrlMap.Count >= m_TotalUrls) + if (m_UrlMap.Count >= TotalUrls) { engine.PostScriptEvent(itemID, "http_request", new Object[] { urlcode.ToString(), "URL_REQUEST_DENIED", "" }); return urlcode; @@ -349,7 +356,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp public int GetFreeUrls() { lock (m_UrlMap) - return m_TotalUrls - m_UrlMap.Count; + return TotalUrls - m_UrlMap.Count; } public void ScriptRemoved(UUID itemID) diff --git a/OpenSim/Region/ScriptEngine/Shared/Tests/LSL_ApiHttpTests.cs b/OpenSim/Region/ScriptEngine/Shared/Tests/LSL_ApiHttpTests.cs new file mode 100644 index 0000000000..b0baa1ce84 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/Shared/Tests/LSL_ApiHttpTests.cs @@ -0,0 +1,250 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Reflection; +using System.Text; +using log4net; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.CoreModules.Scripting.LSLHttp; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.Shared; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Region.ScriptEngine.Shared.ScriptBase; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; +using OpenSim.Tests.Common.Mock; + +namespace OpenSim.Region.ScriptEngine.Shared.Tests +{ + /// + /// Tests for HTTP related functions in LSL + /// + [TestFixture] + public class LSL_ApiHttpTests : OpenSimTestCase + { + private Scene m_scene; + private MockScriptEngine m_engine; + private UrlModule m_urlModule; + + private TaskInventoryItem m_scriptItem; + private LSL_Api m_lslApi; + + [TestFixtureSetUp] + public void TestFixtureSetUp() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [TestFixtureTearDown] + public void TestFixureTearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // This is an unfortunate bit of clean up we have to do because MainServer manages things through static + // variables and the VM is not restarted between tests. + uint port = 9999; + MainServer.RemoveHttpServer(port); + + BaseHttpServer server = new BaseHttpServer(port, false, 0, ""); + MainServer.AddHttpServer(server); + MainServer.Instance = server; + + server.Start(); + + m_engine = new MockScriptEngine(); + m_urlModule = new UrlModule(); + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, new IniConfigSource(), m_engine, m_urlModule); + + SceneObjectGroup so = SceneHelpers.AddSceneObject(m_scene); + m_scriptItem = TaskInventoryHelpers.AddScript(m_scene, so.RootPart); + + // This is disconnected from the actual script - the mock engine does not set up any LSL_Api atm. + // Possibly this could be done and we could obtain it directly from the MockScriptEngine. + m_lslApi = new LSL_Api(); + m_lslApi.Initialize(m_engine, so.RootPart, m_scriptItem, null); + } + + [TearDown] + public void TearDown() + { + MainServer.Instance.Stop(); + } + + [Test] + public void TestLlReleaseUrl() + { + TestHelpers.InMethod(); + + m_lslApi.llRequestURL(); + string returnedUri = m_engine.PostedEvents[m_scriptItem.ItemID][0].Params[2].ToString(); + + { + // Check that the initial number of URLs is correct + Assert.That(m_lslApi.llGetFreeURLs().value, Is.EqualTo(m_urlModule.TotalUrls - 1)); + } + + { + // Check releasing a non-url + m_lslApi.llReleaseURL("GARBAGE"); + Assert.That(m_lslApi.llGetFreeURLs().value, Is.EqualTo(m_urlModule.TotalUrls - 1)); + } + + { + // Check releasing a non-existing url + m_lslApi.llReleaseURL("http://example.com"); + Assert.That(m_lslApi.llGetFreeURLs().value, Is.EqualTo(m_urlModule.TotalUrls - 1)); + } + + { + // Check URL release + m_lslApi.llReleaseURL(returnedUri); + Assert.That(m_lslApi.llGetFreeURLs().value, Is.EqualTo(m_urlModule.TotalUrls)); + + HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(returnedUri); + + bool gotExpectedException = false; + + try + { + using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse()) + {} + } + catch (WebException e) + { + using (HttpWebResponse response = (HttpWebResponse)e.Response) + gotExpectedException = response.StatusCode == HttpStatusCode.NotFound; + } + + Assert.That(gotExpectedException, Is.True); + } + + { + // Check releasing the same URL again + m_lslApi.llReleaseURL(returnedUri); + Assert.That(m_lslApi.llGetFreeURLs().value, Is.EqualTo(m_urlModule.TotalUrls)); + } + } + + [Test] + public void TestLlRequestUrl() + { + TestHelpers.InMethod(); + + string requestId = m_lslApi.llRequestURL(); + Assert.That(requestId, Is.Not.EqualTo(UUID.Zero.ToString())); + string returnedUri; + + { + // Check that URL is correctly set up + Assert.That(m_lslApi.llGetFreeURLs().value, Is.EqualTo(m_urlModule.TotalUrls - 1)); + + Assert.That(m_engine.PostedEvents.ContainsKey(m_scriptItem.ItemID)); + + List events = m_engine.PostedEvents[m_scriptItem.ItemID]; + Assert.That(events.Count, Is.EqualTo(1)); + EventParams eventParams = events[0]; + Assert.That(eventParams.EventName, Is.EqualTo("http_request")); + + UUID returnKey; + string rawReturnKey = eventParams.Params[0].ToString(); + string method = eventParams.Params[1].ToString(); + returnedUri = eventParams.Params[2].ToString(); + + Assert.That(UUID.TryParse(rawReturnKey, out returnKey), Is.True); + Assert.That(method, Is.EqualTo(ScriptBaseClass.URL_REQUEST_GRANTED)); + Assert.That(Uri.IsWellFormedUriString(returnedUri, UriKind.Absolute), Is.True); + } + + { + // Check that request to URL works. + string testResponse = "Hello World"; + + m_engine.ClearPostedEvents(); + m_engine.PostEventHook + += (itemId, evp) => m_lslApi.llHTTPResponse(evp.Params[0].ToString(), 200, testResponse); + +// Console.WriteLine("Trying {0}", returnedUri); + HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(returnedUri); + + AssertHttpResponse(returnedUri, testResponse); + + Assert.That(m_engine.PostedEvents.ContainsKey(m_scriptItem.ItemID)); + + List events = m_engine.PostedEvents[m_scriptItem.ItemID]; + Assert.That(events.Count, Is.EqualTo(1)); + EventParams eventParams = events[0]; + Assert.That(eventParams.EventName, Is.EqualTo("http_request")); + + UUID returnKey; + string rawReturnKey = eventParams.Params[0].ToString(); + string method = eventParams.Params[1].ToString(); + string body = eventParams.Params[2].ToString(); + + Assert.That(UUID.TryParse(rawReturnKey, out returnKey), Is.True); + Assert.That(method, Is.EqualTo("GET")); + Assert.That(body, Is.EqualTo("")); + } + } + + private void AssertHttpResponse(string uri, string expectedResponse) + { + HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uri); + + using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse()) + { + using (Stream stream = webResponse.GetResponseStream()) + { + using (StreamReader reader = new StreamReader(stream)) + { + Assert.That(reader.ReadToEnd(), Is.EqualTo(expectedResponse)); + } + } + } + } + } +} \ No newline at end of file