mirror of
https://github.com/opensim/opensim.git
synced 2026-05-15 11:25:39 +08:00
several changes to llhttprequest like use HttpClient
This commit is contained in:
@@ -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<UUID, HttpRequestClass> m_pendingRequests;
|
||||
|
||||
//this are per region/module
|
||||
private readonly ConcurrentQueue<HttpRequestClass> m_CompletedRequests = new ConcurrentQueue<HttpRequestClass>();
|
||||
private readonly ConcurrentDictionary<uint, ThrottleData> m_RequestsThrottle = new ConcurrentDictionary<uint, ThrottleData>();
|
||||
private readonly ConcurrentDictionary<UUID, ThrottleData> m_OwnerRequestsThrottle = new ConcurrentDictionary<UUID, ThrottleData>();
|
||||
|
||||
private readonly ConcurrentQueue<HttpRequestClass> m_CompletedRequests = new();
|
||||
private readonly ConcurrentDictionary<uint, ThrottleData> m_RequestsThrottle = new();
|
||||
private readonly ConcurrentDictionary<UUID, ThrottleData> 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<UUID, HttpRequestClass>();
|
||||
@@ -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<string> parameters, Dictionary<string, string> headers, string body,
|
||||
out HttpInitialRequestStatus status)
|
||||
public UUID StartHttpRequest(uint localID, UUID itemID, string url,
|
||||
List<string> parameters, Dictionary<string, string> 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<string>();
|
||||
|
||||
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<byte>());
|
||||
request.Content.Headers.TryAddWithoutValidation(headerName, value);
|
||||
}
|
||||
else
|
||||
request.Headers.TryAddWithoutValidation(headerName, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Module that made this request.
|
||||
/// </summary>
|
||||
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<string> 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;
|
||||
|
||||
/// <summary>
|
||||
/// Number of HTTP redirects that this request has been through.
|
||||
/// </summary>
|
||||
@@ -484,12 +573,11 @@ namespace OpenSim.Region.CoreModules.Scripting.HttpRequest
|
||||
/// <summary>
|
||||
/// Maximum number of HTTP redirects allowed for this request.
|
||||
/// </summary>
|
||||
public int MaxRedirects { get; set; }
|
||||
public int MaxRedirects { get; set; } = 10;
|
||||
|
||||
public string OutboundBody;
|
||||
|
||||
public string ResponseBody;
|
||||
public List<string> ResponseMetadata;
|
||||
public Dictionary<string, string> 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<string, string> 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<string, string> 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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,13 +72,9 @@ namespace OpenSim.Region.Framework.Interfaces
|
||||
/// <param name="parameters">LSL parameters for the request.</param>
|
||||
/// <param name="headers">Extra headers for the request.</param>
|
||||
/// <param name="body">Body of the request.</param>
|
||||
/// <param name="status">
|
||||
/// 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.
|
||||
/// </param>
|
||||
UUID StartHttpRequest(
|
||||
uint localID, UUID itemID, string url, List<string> parameters, Dictionary<string, string> headers, string body,
|
||||
out HttpInitialRequestStatus status);
|
||||
|
||||
UUID StartHttpRequest(uint localID, UUID itemID, string url, List<string> parameters, Dictionary<string, string> headers, string body);
|
||||
|
||||
/// <summary>
|
||||
/// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<IHttpRequestModule>();
|
||||
@@ -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<string, string> httpHeaders = new();
|
||||
List<string> 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<string, string> 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();
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user