From 83491db9166f1ce4be7daae151da5370facb06bc Mon Sep 17 00:00:00 2001 From: UbitUmarov Date: Sat, 6 May 2023 18:06:40 +0100 Subject: [PATCH] several changes to llhttprequest like use HttpClient --- .../HttpRequest/ScriptsHttpRequests.cs | 553 +++++++++--------- .../Framework/Interfaces/IHttpRequests.cs | 9 +- .../Shared/Api/Implementation/LSL_Api.cs | 79 ++- 3 files changed, 307 insertions(+), 334 deletions(-) diff --git a/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs b/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs index 5d45372d69..e323452400 100644 --- a/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs +++ b/OpenSim/Region/CoreModules/Scripting/HttpRequest/ScriptsHttpRequests.cs @@ -40,6 +40,8 @@ using OpenSim.Framework.Monitoring; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.Framework.Scenes; using Mono.Addins; +using System.Net.Http; +using System.Security.Authentication; /***************************************************** * @@ -64,14 +66,16 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest // private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - private static readonly object m_mainLock = new object(); + private static HttpClient VeriFyCertClient = null; + private static HttpClient VeriFyNoCertClient = null; + + private static readonly object m_mainLock = new(); private static int m_numberScenes; - private static int m_httpTimeout = 30000; + private static readonly string m_name = "HttpScriptRequests"; private static OutboundUrlFilter m_outboundUrlFilter; - private static string m_proxyurl = ""; - private static string m_proxyexcepts = ""; + private static int m_HttpBodyMaxLenMAX = 16384; private static float m_primPerSec = 1.0f; private static float m_primBurst = 3.0f; @@ -82,10 +86,9 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest private static Dictionary m_pendingRequests; //this are per region/module - private readonly ConcurrentQueue m_CompletedRequests = new ConcurrentQueue(); - private readonly ConcurrentDictionary m_RequestsThrottle = new ConcurrentDictionary(); - private readonly ConcurrentDictionary m_OwnerRequestsThrottle = new ConcurrentDictionary(); - + private readonly ConcurrentQueue m_CompletedRequests = new(); + private readonly ConcurrentDictionary m_RequestsThrottle = new(); + private readonly ConcurrentDictionary m_OwnerRequestsThrottle = new(); public HttpRequestModule() { @@ -98,29 +101,115 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest lock (m_mainLock) { // shared items - if (m_jobEngine == null) + if (m_jobEngine is null) { - m_proxyurl = config.Configs["Startup"].GetString("HttpProxy"); - m_proxyexcepts = config.Configs["Startup"].GetString("HttpProxyExceptions"); + WebProxy proxy = null; + string proxyurl = config.Configs["Startup"].GetString("HttpProxy"); + if (!string.IsNullOrEmpty(proxyurl)) + { + string[] proxyexceptsArray = null; + string proxyexcepts = config.Configs["Startup"].GetString("HttpProxyExceptions"); + if (!string.IsNullOrEmpty(proxyexcepts)) + { + proxyexceptsArray = proxyexcepts.Split(';'); + if(proxyexceptsArray.Length == 0) + proxyexceptsArray = null; + } + proxy = proxyexceptsArray is null ? + new WebProxy(proxyurl, true) : + new WebProxy(proxyurl, true, proxyexceptsArray); + } - HttpRequestClass.HttpBodyMaxLenMAX = config.Configs["Network"].GetInt("HttpBodyMaxLenMAX", 16384); + m_HttpBodyMaxLenMAX = config.Configs["Network"].GetInt("HttpBodyMaxLenMAX", m_HttpBodyMaxLenMAX); m_outboundUrlFilter = new OutboundUrlFilter("Script HTTP request module", config); int maxThreads = 8; IConfig httpConfig = config.Configs["ScriptsHttpRequestModule"]; - if (httpConfig != null) + int httpTimeout = 30000; + if (httpConfig is not null) { maxThreads = httpConfig.GetInt("MaxPoolThreads", maxThreads); m_primBurst = httpConfig.GetFloat("PrimRequestsBurst", m_primBurst); m_primPerSec = httpConfig.GetFloat("PrimRequestsPerSec", m_primPerSec); m_primOwnerBurst = httpConfig.GetFloat("PrimOwnerRequestsBurst", m_primOwnerBurst); m_primOwnerPerSec = httpConfig.GetFloat("PrimOwnerRequestsPerSec", m_primOwnerPerSec); - m_httpTimeout = httpConfig.GetInt("RequestsTimeOut", m_httpTimeout); - if (m_httpTimeout > 60000) - m_httpTimeout = 60000; - else if (m_httpTimeout < 200) - m_httpTimeout = 200; + httpTimeout = httpConfig.GetInt("RequestsTimeOut", httpTimeout); + if (httpTimeout > 60000) + httpTimeout = 60000; + else if (httpTimeout < 200) + httpTimeout = 200; + } + + if (VeriFyNoCertClient is null) + { + SocketsHttpHandler shhnc = new() + { + AllowAutoRedirect = false, + AutomaticDecompression = DecompressionMethods.None, + ConnectTimeout = TimeSpan.FromMilliseconds(httpTimeout), + PreAuthenticate = false, + UseCookies = false, + MaxConnectionsPerServer = maxThreads < 10 ? maxThreads : 10, + PooledConnectionLifetime = TimeSpan.FromMinutes(3) + }; + //shhnc.SslOptions.ClientCertificates = null, + shhnc.SslOptions.EnabledSslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13; + shhnc.SslOptions.CertificateRevocationCheckMode = X509RevocationMode.NoCheck; + shhnc.SslOptions.RemoteCertificateValidationCallback = (message, cert, chain, errors) => + { + errors &= ~(SslPolicyErrors.RemoteCertificateChainErrors | SslPolicyErrors.RemoteCertificateNameMismatch); + return errors == SslPolicyErrors.None; + }; + if (proxy is null) + shhnc.UseProxy = false; + else + { + shhnc.Proxy = proxy; + shhnc.UseProxy = true; + } + + VeriFyNoCertClient = new HttpClient(shhnc) + { + Timeout = TimeSpan.FromMilliseconds(httpTimeout), + }; + VeriFyNoCertClient.DefaultRequestHeaders.ExpectContinue = false; + VeriFyNoCertClient.DefaultRequestHeaders.ConnectionClose = true; + } + + if (VeriFyCertClient is null) + { + SocketsHttpHandler shh = new() + { + AllowAutoRedirect = false, + AutomaticDecompression = DecompressionMethods.None, + ConnectTimeout = TimeSpan.FromMilliseconds((double)httpTimeout), + PreAuthenticate = false, + UseCookies = false, + MaxConnectionsPerServer = maxThreads < 10 ? maxThreads : 10, + PooledConnectionLifetime = TimeSpan.FromMinutes(3) + }; + //shhnc.SslOptions.ClientCertificates = null, + shh.SslOptions.EnabledSslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13; + shh.SslOptions.CertificateRevocationCheckMode = X509RevocationMode.NoCheck; + shh.SslOptions.RemoteCertificateValidationCallback = (message, cert, chain, errors) => + { + errors &= ~SslPolicyErrors.RemoteCertificateChainErrors; + return errors == SslPolicyErrors.None; + }; + if (proxy is null) + shh.UseProxy = false; + else + { + shh.Proxy = proxy; + shh.UseProxy = true; + } + VeriFyCertClient = new HttpClient(shh) + { + Timeout = TimeSpan.FromMilliseconds(httpTimeout) + }; + VeriFyCertClient.DefaultRequestHeaders.ExpectContinue = false; + VeriFyCertClient.DefaultRequestHeaders.ConnectionClose = true; } m_pendingRequests = new Dictionary(); @@ -157,11 +246,15 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest { lock(m_mainLock) { - if (m_jobEngine != null) + if (m_jobEngine is not null) { m_jobEngine.Stop(); m_jobEngine = null; } + VeriFyCertClient?.Dispose(); + VeriFyCertClient = null; + VeriFyNoCertClient?.Dispose(); + VeriFyNoCertClient = null; } } } @@ -185,6 +278,11 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest return UUID.Zero; } + public HttpClient GetHttpClient(bool verify) + { + return verify ? VeriFyCertClient : VeriFyNoCertClient; + } + public bool CheckThrottle(uint localID, UUID ownerID) { double now = Util.GetTimeStamp(); @@ -252,47 +350,35 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest return ret; } - public UUID StartHttpRequest( - uint localID, UUID itemID, string url, List parameters, Dictionary headers, string body, - out HttpInitialRequestStatus status) + public UUID StartHttpRequest(uint localID, UUID itemID, string url, + List parameters, Dictionary headers, string body) { - if (!CheckAllowed(new Uri(url))) - { - status = HttpInitialRequestStatus.DISALLOWED_BY_FILTER; - return UUID.Zero; - } - UUID reqID = UUID.Random(); - HttpRequestClass htc = new HttpRequestClass(); + HttpRequestClass htc = new(); // Partial implementation: support for parameter flags needed // see http://wiki.secondlife.com/wiki/LlHTTPRequest // // Parameters are expected in {key, value, ... , key, value} - if (parameters != null) + if (parameters is not null) { - string[] parms = parameters.ToArray(); - for (int i = 0; i < parms.Length; i += 2) + for (int i = 0; i < parameters.Count; i += 2) { - switch (Int32.Parse(parms[i])) + switch (Int32.Parse(parameters[i])) { case (int)HttpRequestConstants.HTTP_METHOD: - - htc.HttpMethod = parms[i + 1]; + htc.HttpMethod = parameters[i + 1]; break; case (int)HttpRequestConstants.HTTP_MIMETYPE: - - htc.HttpMIMEType = parms[i + 1]; + htc.HttpMIMEType = parameters[i + 1]; break; case (int)HttpRequestConstants.HTTP_BODY_MAXLENGTH: - - int len; - if(int.TryParse(parms[i + 1], out len)) + if(int.TryParse(parameters[i + 1], out int len)) { - if(len > HttpRequestClass.HttpBodyMaxLenMAX) - len = HttpRequestClass.HttpBodyMaxLenMAX; + if(len > m_HttpBodyMaxLenMAX) + len = m_HttpBodyMaxLenMAX; else if(len < 64) //??? len = 64; htc.HttpBodyMaxLen = len; @@ -300,15 +386,14 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest break; case (int)HttpRequestConstants.HTTP_VERIFY_CERT: - htc.HttpVerifyCert = (int.Parse(parms[i + 1]) != 0); + htc.HttpVerifyCert = (int.Parse(parameters[i + 1]) != 0); break; case (int)HttpRequestConstants.HTTP_VERBOSE_THROTTLE: - - // TODO implement me break; case (int)HttpRequestConstants.HTTP_CUSTOM_HEADER: + // should not happen //Parameters are in pairs and custom header takes //arguments in pairs so adjust for header marker. ++i; @@ -318,16 +403,11 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest for (int count = 1; count <= 8; ++count) { //Not enough parameters remaining for a header? - if (parms.Length - i < 2) - break; + if (parameters.Count - i < 2) + break; - if (htc.HttpCustomHeaders == null) - htc.HttpCustomHeaders = new List(); - - htc.HttpCustomHeaders.Add(parms[i]); - htc.HttpCustomHeaders.Add(parms[i+1]); int nexti = i + 2; - if (nexti >= parms.Length || Char.IsDigit(parms[nexti][0])) + if (nexti >= parameters.Count || Char.IsDigit(parameters[nexti][0])) break; i = nexti; @@ -335,7 +415,7 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest break; case (int)HttpRequestConstants.HTTP_PRAGMA_NO_CACHE: - htc.HttpPragmaNoCache = (int.Parse(parms[i + 1]) != 0); + htc.HttpPragmaNoCache = (int.Parse(parameters[i + 1]) != 0); break; } } @@ -346,20 +426,13 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest htc.ItemID = itemID; htc.Url = url; htc.ReqID = reqID; - htc.HttpTimeout = m_httpTimeout; htc.OutboundBody = body; htc.ResponseHeaders = headers; - htc.proxyurl = m_proxyurl; - htc.proxyexcepts = m_proxyexcepts; - - // Same number as default HttpWebRequest.MaximumAutomaticRedirections - htc.MaxRedirects = 50; lock (m_mainLock) m_pendingRequests.Add(reqID, htc); htc.Process(); - status = HttpInitialRequestStatus.OK; return reqID; } @@ -438,44 +511,60 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest public class HttpRequestClass : IServiceRequest { - // Constants for parameters - // public const int HTTP_BODY_MAXLENGTH = 2; - // public const int HTTP_METHOD = 0; - // public const int HTTP_MIMETYPE = 1; - // public const int HTTP_VERIFY_CERT = 3; - // public const int HTTP_VERBOSE_THROTTLE = 4; - // public const int HTTP_CUSTOM_HEADER = 5; - // public const int HTTP_PRAGMA_NO_CACHE = 6; + private static readonly string[] s_wellKnownContentHeaders = { + "Content-Disposition", + "Content-Encoding", + "Content-Language", + "Content-Length", + "Content-Location", + "Content-MD5", + "Content-Range", + "Content-Type", + "Expires", + "Last-Modified" + }; + + private bool IsWellKnownContentHeader(string header) + { + foreach (string contentHeaderName in s_wellKnownContentHeaders) + { + if (string.Equals(header, contentHeaderName, StringComparison.OrdinalIgnoreCase)) + return true; + } + return false; + } + private void AddHeader(string headerName, string value, HttpRequestMessage request) + { + if (IsWellKnownContentHeader(headerName)) + { + request.Content ??= new ByteArrayContent(Array.Empty()); + request.Content.Headers.TryAddWithoutValidation(headerName, value); + } + else + request.Headers.TryAddWithoutValidation(headerName, value); + } /// /// Module that made this request. /// public HttpRequestModule RequestModule { get; set; } - public bool Finished { get; private set;} + public bool HttpVerifyCert = true; public bool Removed; - public static int HttpBodyMaxLenMAX = 16384; - // Parameter members and default values public int HttpBodyMaxLen = 2048; public string HttpMethod = "GET"; public string HttpMIMEType = "text/plain;charset=utf-8"; - public int HttpTimeout; - public bool HttpVerifyCert = true; - //public bool HttpVerboseThrottle = true; // not implemented - public List HttpCustomHeaders = null; - public bool HttpPragmaNoCache = true; + public bool HttpPragmaNoCache = false; // Request info + public bool Finished { get; } public UUID ReqID { get; set; } public UUID ItemID { get; set;} public uint LocalID { get; set;} - public string proxyurl; - public string proxyexcepts; - /// /// Number of HTTP redirects that this request has been through. /// @@ -484,12 +573,11 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest /// /// Maximum number of HTTP redirects allowed for this request. /// - public int MaxRedirects { get; set; } + public int MaxRedirects { get; set; } = 10; public string OutboundBody; public string ResponseBody; - public List ResponseMetadata; public Dictionary ResponseHeaders; public int Status; public string Url; @@ -499,198 +587,97 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest HttpRequestModule.m_jobEngine?.QueueJob("", SendRequest); } - public static bool ValidateServerCertificate( - object sender, - X509Certificate certificate, - X509Chain chain, - SslPolicyErrors sslPolicyErrors) - { - // If this is a web request we need to check the headers first - // We may want to ignore SSL - if (sender is HttpWebRequest) - { - HttpWebRequest Request = sender as HttpWebRequest; - // We don't case about encryption, get out of here - if (Request.Headers.Get("NoVerifyCert") != null) - return true; - } - - if ((((int)sslPolicyErrors) & ~4) != 0) - return false; - - return true; - } - - /* - * TODO: More work on the response codes. Right now - * returning 200 for success or 499 for exception - */ - public void SendRequest() { - if(Removed) + if (Removed) return; - HttpWebRequest Request; - HttpWebResponse response = null; - Stream resStream = null; + HttpResponseMessage responseMessage = null; + HttpRequestMessage request = null; try { - Request = (HttpWebRequest)WebRequest.Create(Url); - Request.ServerCertificateValidationCallback = ValidateServerCertificate; + HttpClient client = RequestModule.GetHttpClient(HttpVerifyCert); + request = new (new HttpMethod(HttpMethod), Url); - Request.AllowAutoRedirect = false; - Request.KeepAlive = false; - Request.Timeout = HttpTimeout; - - //This works around some buggy HTTP Servers like Lighttpd - Request.ServicePoint.Expect100Continue = false; - - Request.Method = HttpMethod; - Request.ContentType = HttpMIMEType; - - if (!HttpVerifyCert) - { - // We could hijack Connection Group Name to identify - // a desired security exception. But at the moment we'll use a dummy header instead. - Request.Headers.Add("NoVerifyCert", "true"); - } -// else -// { -// Request.ConnectionGroupName="Verify"; -// } - - if (!HttpPragmaNoCache) - { - Request.Headers.Add("Pragma", "no-cache"); - } - - if (HttpCustomHeaders != null) - { - for (int i = 0; i < HttpCustomHeaders.Count; i += 2) - Request.Headers.Add(HttpCustomHeaders[i], - HttpCustomHeaders[i+1]); - } - - if (!string.IsNullOrEmpty(proxyurl)) - { - if (!string.IsNullOrEmpty(proxyexcepts)) - { - string[] elist = proxyexcepts.Split(';'); - Request.Proxy = new WebProxy(proxyurl, true, elist); - } - else - { - Request.Proxy = new WebProxy(proxyurl, true); - } - } - - foreach (KeyValuePair entry in ResponseHeaders) - if (entry.Key.ToLower().Equals("user-agent")) - Request.UserAgent = entry.Value; - else - Request.Headers[entry.Key] = entry.Value; - - if (Removed) - return; - - // Encode outbound data + int datalen; if (!string.IsNullOrEmpty(OutboundBody)) { byte[] data = Util.UTF8.GetBytes(OutboundBody); - - Request.ContentLength = data.Length; - using (Stream bstream = Request.GetRequestStream()) - bstream.Write(data, 0, data.Length); - data = null; - } - - if (Removed) - return; - - try - { - // execute the request - response = (HttpWebResponse) Request.GetResponse(); - } - catch (WebException e) - { - if (e.Status != WebExceptionStatus.ProtocolError) - { - throw; - } - response = (HttpWebResponse)e.Response; - } - - if (Removed) - return; - - Status = (int)response.StatusCode; - - byte[] buf = new byte[HttpBodyMaxLenMAX + 16]; - int count; - - resStream = response.GetResponseStream(); - int totalBodyBytes = 0; - int maxBytes = HttpBodyMaxLen; - if(maxBytes > buf.Length) - maxBytes = buf.Length; - - // we need to read all allowed or UFT8 conversion may fail - do - { - // fill the buffer with data - count = resStream.Read(buf, totalBodyBytes, maxBytes - totalBodyBytes); - totalBodyBytes += count; - if (totalBodyBytes >= maxBytes) - break; - - } while (count > 0); // any more data to read? - - if(totalBodyBytes > 0) - { - string tempString = Util.UTF8.GetString(buf, 0, totalBodyBytes); - ResponseBody = tempString.Replace("\r", ""); + datalen = data.Length; + request.Content = new ByteArrayContent(data); } else - ResponseBody = ""; - } - catch (WebException e) - { - if (e.Status == WebExceptionStatus.ProtocolError) + datalen = -1; + + foreach (KeyValuePair entry in ResponseHeaders) + AddHeader(entry.Key, entry.Value, request); + + if (HttpPragmaNoCache) + request.Headers.TryAddWithoutValidation("Pragma", "no-cache"); + + request.Headers.TransferEncodingChunked = false; + request.Headers.ConnectionClose = true; + + if (datalen > 0) { - HttpWebResponse webRsp = (HttpWebResponse)((WebException)e).Response; - Status = (int)webRsp.StatusCode; - try + request.Content.Headers.TryAddWithoutValidation("Content-Type", HttpMIMEType); + request.Content.Headers.TryAddWithoutValidation("Content-Length", datalen.ToString()); + } + + if (Removed) + return; + + responseMessage = client.Send(request, HttpCompletionOption.ResponseHeadersRead); + + if (Removed) + return; + + Status = (int)responseMessage.StatusCode; + if (responseMessage.Content is not null) + { + int len; + if(responseMessage.Content.Headers is not null && responseMessage.Content.Headers.ContentLength is long l) + len = (int)l; + else + len = -1; + + Stream resStream = responseMessage.Content.ReadAsStream(); + + if(resStream is not null) { - using (Stream responseStream = webRsp.GetResponseStream()) + int maxBytes = (len < 0 || len > HttpBodyMaxLen) ? HttpBodyMaxLen : len; + byte[] buf = new byte[maxBytes]; + + int totalBodyBytes = 0; + int count; + do { - using (StreamReader reader = new StreamReader(responseStream)) - ResponseBody = reader.ReadToEnd(); + count = resStream.Read(buf, totalBodyBytes, maxBytes - totalBodyBytes); + totalBodyBytes += count; + } while (count > 0 && totalBodyBytes < maxBytes); // any more data to read? + resStream.Dispose(); + + if (totalBodyBytes > 0) + { + string tempString = Util.UTF8.GetString(buf, 0, totalBodyBytes); + ResponseBody = tempString.Replace("\r", ""); } } - catch - { - ResponseBody = webRsp.StatusDescription; - } - } - else - { - Status = 499; //ClientErrorJoker; - ResponseBody = e.Message; } } -// catch (Exception e) + catch (HttpRequestException e) + { + Status = e.StatusCode is null ? 499 : (int)e.StatusCode; + ResponseBody = e.Message; + } + //catch (Exception e) catch { // Don't crash on anything else } finally { - if (resStream != null) - resStream.Close(); - - if(!Removed) + if (!Removed) { // We need to resubmit ? if (Status == (int)HttpStatusCode.MovedPermanently || @@ -704,10 +691,10 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest ResponseBody = "Number of redirects exceeded max redirects"; RequestModule.GotCompletedRequest(this); } - else + else if (responseMessage is not null && responseMessage.Headers is not null) { - string location = response.Headers["Location"]; - if (location == null) + Uri locationUri = responseMessage.Headers.Location; + if (locationUri == null) { Status = 499;//ClientErrorJoker; ResponseBody = "HTTP redirect code but no location header"; @@ -715,41 +702,38 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest } else { - if(Uri.TryCreate(location, UriKind.RelativeOrAbsolute, out Uri locationUri)) + bool validredir = true; + if(!locationUri.IsAbsoluteUri) { - bool validredir = true; - if(!locationUri.IsAbsoluteUri) + Uri reqUri = responseMessage.RequestMessage.RequestUri; + string newloc = reqUri.Scheme +"://" + reqUri.DnsSafeHost + ":" + + reqUri.Port +"/" + locationUri.OriginalString; + if (!Uri.TryCreate(newloc, UriKind.RelativeOrAbsolute, out locationUri)) { - string newloc = response.ResponseUri.Scheme +"://" + response.ResponseUri.DnsSafeHost + ":" + - response.ResponseUri.Port +"/" + location; - if (!Uri.TryCreate(newloc, UriKind.RelativeOrAbsolute, out locationUri)) - { - Status = 499;//ClientErrorJoker; - ResponseBody = "HTTP redirect code but invalid location header"; - RequestModule.GotCompletedRequest(this); - validredir = false; - } - location = newloc; + Status = 499;//ClientErrorJoker; + ResponseBody = "HTTP redirect code but invalid location header"; + RequestModule.GotCompletedRequest(this); + validredir = false; } - if(validredir) + } + if(validredir) + { + if (!RequestModule.CheckAllowed(locationUri)) { - if (!RequestModule.CheckAllowed(locationUri)) - { - Status = 499;//ClientErrorJoker; - ResponseBody = "URL from HTTP redirect blocked: " + location; - RequestModule.GotCompletedRequest(this); - } - else - { - Status = 0; - Url = location; - Redirects++; - ResponseBody = null; + Status = 499;//ClientErrorJoker; + ResponseBody = "URL from HTTP redirect blocked: " + locationUri.AbsoluteUri; + RequestModule.GotCompletedRequest(this); + } + else + { + Status = 0; + Url = locationUri.AbsoluteUri; + Redirects++; + ResponseBody = null; - //m_log.DebugFormat("Redirecting to [{0}]", Url); + //m_log.DebugFormat("Redirecting to [{0}]", Url); - Process(); - } + Process(); } } else @@ -763,13 +747,12 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest } else { - if (ResponseBody == null) - ResponseBody = string.Empty; + ResponseBody ??= string.Empty; RequestModule.GotCompletedRequest(this); } } - if (response != null) - response.Close(); + responseMessage?.Dispose(); + request.Dispose(); } } diff --git a/OpenSim/Region/Framework/Interfaces/IHttpRequests.cs b/OpenSim/Region/Framework/Interfaces/IHttpRequests.cs index 2c75844fea..68827d82d9 100644 --- a/OpenSim/Region/Framework/Interfaces/IHttpRequests.cs +++ b/OpenSim/Region/Framework/Interfaces/IHttpRequests.cs @@ -72,13 +72,9 @@ namespace OpenSim.Region.Framework.Interfaces /// LSL parameters for the request. /// Extra headers for the request. /// Body of the request. - /// - /// Initial status of the request. If OK then the request is actually made to the URL. Subsequent status is - /// then returned via IServiceRequest when the response is asynchronously fetched. /// - UUID StartHttpRequest( - uint localID, UUID itemID, string url, List parameters, Dictionary headers, string body, - out HttpInitialRequestStatus status); + + UUID StartHttpRequest(uint localID, UUID itemID, string url, List parameters, Dictionary headers, string body); /// /// Stop and remove all http requests for the given script. @@ -88,5 +84,6 @@ namespace OpenSim.Region.Framework.Interfaces IServiceRequest GetNextCompletedRequest(); void RemoveCompletedRequest(UUID id); bool CheckThrottle(uint localID, UUID onerID); + bool CheckAllowed(Uri url); } } diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs index ce8e258870..c4fa732ce9 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs @@ -13814,6 +13814,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api return Convert.ToBase64String(data1); } + static Regex llHTTPRequestRegex = new(@"^(https?:\/\/)(\w+):(\w+)@(.*)$", RegexOptions.Compiled); + public LSL_Key llHTTPRequest(string url, LSL_List parameters, string body) { IHttpRequestModule httpScriptMod = m_ScriptEngine.World.RequestModuleInterface(); @@ -13823,40 +13825,48 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api if(!httpScriptMod.CheckThrottle(m_host.LocalId, m_host.OwnerID)) return ScriptBaseClass.NULL_KEY; - try - { - Uri m_checkuri = new(url); - if (m_checkuri.Scheme != Uri.UriSchemeHttp && m_checkuri.Scheme != Uri.UriSchemeHttps) - { - Error("llHTTPRequest", "Invalid url schema"); - return string.Empty; - } - } - catch + if(!Uri.TryCreate(url, UriKind.Absolute, out Uri m_checkuri)) { Error("llHTTPRequest", "Invalid url"); return string.Empty; } + if (m_checkuri.Scheme != Uri.UriSchemeHttp && m_checkuri.Scheme != Uri.UriSchemeHttps) + { + Error("llHTTPRequest", "Invalid url schema"); + return string.Empty; + } + + if (!httpScriptMod.CheckAllowed(m_checkuri)) + { + Error("llHttpRequest", string.Format("Request to {0} disallowed by filter", url)); + return string.Empty; + } + + Dictionary httpHeaders = new(); List param = new(); - bool ok; int nCustomHeaders = 0; + int flag; for (int i = 0; i < parameters.Data.Length; i += 2) { - ok = Int32.TryParse(parameters.Data[i].ToString(), out int flag); - if (!ok || flag < 0 || - flag > (int)HttpRequestConstants.HTTP_PRAGMA_NO_CACHE) + object di = parameters.Data[i]; + if(di is LSL_Integer li ) + flag = li.value; + else if (di is int ldi) + flag = ldi; + else flag = -1; + + if(flag < 0 || flag > (int)HttpRequestConstants.HTTP_PRAGMA_NO_CACHE) { Error("llHTTPRequest", "Parameter " + i.ToString() + " is an invalid flag"); ScriptSleep(200); return string.Empty; } - param.Add(parameters.Data[i].ToString()); //Add parameter flag - if (flag != (int)HttpRequestConstants.HTTP_CUSTOM_HEADER) { + param.Add(flag.ToString()); //Add parameter flag param.Add(parameters.Data[i+1].ToString()); //Add parameter value } else @@ -13909,17 +13919,15 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api noskip = false; } - string paramValue = parameters.Data[i + 1].ToString(); - if(paramName.Length + paramValue.Length > 253) - { - Error("llHTTPRequest", "name and value length exceds 253 characters for custom header at parameter " + i.ToString()); - return string.Empty; - } - if (noskip) { - param.Add(paramName); - param.Add(paramValue); + string paramValue = parameters.Data[i + 1].ToString(); + if (paramName.Length + paramValue.Length > 253) + { + Error("llHTTPRequest", "name and value length exceds 253 characters for custom header at parameter " + i.ToString()); + return string.Empty; + } + httpHeaders[paramName] = paramValue; nCustomHeaders++; } @@ -13949,8 +13957,6 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api RegionInfo regionInfo = World.RegionInfo; - Dictionary httpHeaders =new(); - if (!string.IsNullOrWhiteSpace(m_lsl_shard)) httpHeaders["X-SecondLife-Shard"] = m_lsl_shard; httpHeaders["X-SecondLife-Object-Name"] = m_host.Name; @@ -13996,17 +14002,9 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api url = url[..idx]; } - string authregex = @"^(https?:\/\/)(\w+):(\w+)@(.*)$"; - Regex r = new(authregex); - //int[] gnums = r.GetGroupNumbers(); - Match m = r.Match(url); + Match m = llHTTPRequestRegex.Match(url); if (m.Success) { - //for (int i = 1; i < gnums.Length; i++) - //{ - //System.Text.RegularExpressions.Group g = m.Groups[gnums[i]]; - //CaptureCollection cc = g.Captures; - //} if (m.Groups.Count == 5) { httpHeaders["Authorization"] = String.Format("Basic {0}", Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(m.Groups[2].ToString() + ":" + m.Groups[3].ToString()))); @@ -14014,13 +14012,8 @@ namespace OpenSim.Region.ScriptEngine.Shared.Api } } - UUID reqID = httpScriptMod.StartHttpRequest(m_host.LocalId, m_item.ItemID, url, param, httpHeaders, body, - out HttpInitialRequestStatus status); - - if (status == HttpInitialRequestStatus.DISALLOWED_BY_FILTER) - Error("llHttpRequest", string.Format("Request to {0} disallowed by filter", url)); - - return reqID.IsZero() ? "" : reqID.ToString(); + UUID reqID = httpScriptMod.StartHttpRequest(m_host.LocalId, m_item.ItemID, url, param, httpHeaders, body); + return reqID.IsZero() ? string.Empty : reqID.ToString(); }