bestMatch.Length)
{
// You have to specifically register for '/' and to get it, you must specificaly request it
//
if (pattern == "/" && searchquery == "/" || pattern != "/")
bestMatch = pattern;
}
}
}
if (String.IsNullOrEmpty(bestMatch))
{
llsdHandler = null;
return false;
}
else
{
llsdHandler = m_llsdHandlers[bestMatch];
return true;
}
}
}
// legacy should go
public byte[] HandleHTTPRequest(OSHttpRequest request, OSHttpResponse response)
{
// m_log.DebugFormat(
// "[BASE HTTP SERVER]: HandleHTTPRequest for request to {0}, method {1}",
// request.RawUrl, request.HttpMethod);
if (!TryGetHTTPHandlerPathBased(request.RawUrl, out GenericHTTPMethod requestprocessor))
{
return SendHTML404(response);
}
// m_log.DebugFormat("[BASE HTTP SERVER]: HandleContentVerbs for request to {0}", request.RawUrl);
// This is a test. There's a workable alternative.. as this way sucks.
// We'd like to put this into a text file parhaps that's easily editable.
//
// For this test to work, I used the following secondlife.exe parameters
// "C:\Program Files\SecondLifeWindLight\SecondLifeWindLight.exe" -settings settings_windlight.xml -channel "Second Life WindLight" -set SystemLanguage en-us -loginpage http://10.1.1.2:8002/?show_login_form=TRUE -loginuri http://10.1.1.2:8002 -user 10.1.1.2
//
// Even after all that, there's still an error, but it's a start.
//
// I depend on show_login_form being in the secondlife.exe parameters to figure out
// to display the form, or process it.
// a better way would be nifty.
byte[] buffer;
string requestBody;
using(StreamReader reader = new StreamReader(request.InputStream, Encoding.UTF8))
requestBody = reader.ReadToEnd();
Hashtable keysvals = new Hashtable();
Hashtable headervals = new Hashtable();
Hashtable requestVars = new Hashtable();
string host = String.Empty;
string[] querystringkeys = request.QueryString.AllKeys;
string[] rHeaders = request.Headers.AllKeys;
keysvals.Add("body", requestBody);
keysvals.Add("uri", request.RawUrl);
keysvals.Add("content-type", request.ContentType);
keysvals.Add("http-method", request.HttpMethod);
foreach (string queryname in querystringkeys)
{
// m_log.DebugFormat(
// "[BASE HTTP SERVER]: Got query paremeter {0}={1}", queryname, request.QueryString[queryname]);
keysvals.Add(queryname, request.QueryString[queryname]);
requestVars.Add(queryname, keysvals[queryname]);
}
foreach (string headername in rHeaders)
{
// m_log.Debug("[BASE HTTP SERVER]: " + headername + "=" + request.Headers[headername]);
headervals[headername] = request.Headers[headername];
}
keysvals.Add("headers", headervals);
keysvals.Add("querystringkeys", querystringkeys);
keysvals.Add("requestvars", requestVars);
// keysvals.Add("form", request.Form);
Hashtable responsedata2 = requestprocessor(keysvals);
buffer = DoHTTPGruntWork(responsedata2, response);
return buffer;
}
private bool TryGetHTTPHandlerPathBased(string path, out GenericHTTPMethod httpHandler)
{
httpHandler = null;
// Pull out the first part of the path
// splitting the path by '/' means we'll get the following return..
// {0}/{1}/{2}
// where {0} isn't something we really control 100%
string[] pathbase = path.Split('/');
string searchquery = "/";
if (pathbase.Length < 1)
return false;
for (int i = 1; i < pathbase.Length; i++)
{
searchquery += pathbase[i];
if (pathbase.Length - 1 != i)
searchquery += "/";
}
// while the matching algorithm below doesn't require it, we're expecting a query in the form
//
// [] = optional
// /resource/UUID/action[/action]
//
// now try to get the closest match to the reigstered path
// at least for OGP, registered path would probably only consist of the /resource/
string bestMatch = null;
// m_log.DebugFormat(
// "[BASE HTTP HANDLER]: TryGetHTTPHandlerPathBased() looking for HTTP handler to match {0}", searchquery);
lock (m_HTTPHandlers)
{
foreach (string pattern in m_HTTPHandlers.Keys)
{
if (searchquery.ToLower().StartsWith(pattern.ToLower()))
{
if (String.IsNullOrEmpty(bestMatch) || searchquery.Length > bestMatch.Length)
{
// You have to specifically register for '/' and to get it, you must specifically request it
if (pattern == "/" && searchquery == "/" || pattern != "/")
bestMatch = pattern;
}
}
}
if (String.IsNullOrEmpty(bestMatch))
{
httpHandler = null;
return false;
}
else
{
if (bestMatch == "/" && searchquery != "/")
return false;
httpHandler = m_HTTPHandlers[bestMatch];
return true;
}
}
}
internal byte[] DoHTTPGruntWork(Hashtable responsedata, OSHttpResponse response)
{
int responsecode;
string responseString = String.Empty;
byte[] responseData = null;
string contentType;
if (responsedata == null)
{
responsecode = 500;
responseString = "No response could be obtained";
contentType = "text/plain";
responsedata = new Hashtable();
}
else
{
try
{
//m_log.Info("[BASE HTTP SERVER]: Doing HTTP Grunt work with response");
responsecode = (int)responsedata["int_response_code"];
if (responsedata["bin_response_data"] != null)
responseData = (byte[])responsedata["bin_response_data"];
else
responseString = (string)responsedata["str_response_string"];
contentType = (string)responsedata["content_type"];
if (responseString == null)
responseString = String.Empty;
}
catch
{
responsecode = 500;
responseString = "No response could be obtained";
contentType = "text/plain";
responsedata = new Hashtable();
}
}
if (responsedata.ContainsKey("error_status_text"))
{
response.StatusDescription = (string)responsedata["error_status_text"];
}
if (responsedata.ContainsKey("http_protocol_version"))
{
response.ProtocolVersion = (string)responsedata["http_protocol_version"];
}
if (responsedata.ContainsKey("keepalive"))
{
bool keepalive = (bool)responsedata["keepalive"];
response.KeepAlive = keepalive;
}
// Cross-Origin Resource Sharing with simple requests
if (responsedata.ContainsKey("access_control_allow_origin"))
response.AddHeader("Access-Control-Allow-Origin", (string)responsedata["access_control_allow_origin"]);
//Even though only one other part of the entire code uses HTTPHandlers, we shouldn't expect this
//and should check for NullReferenceExceptions
if (string.IsNullOrEmpty(contentType))
{
contentType = "text/html";
}
// The client ignores anything but 200 here for web login, so ensure that this is 200 for that
response.StatusCode = responsecode;
if (responsecode == (int)HttpStatusCode.Moved)
{
response.AddHeader("Location:", (string)responsedata["str_redirect_location"]);
response.StatusCode = responsecode;
}
response.AddHeader("Content-Type", contentType);
if (responsedata.ContainsKey("headers"))
{
Hashtable headerdata = (Hashtable)responsedata["headers"];
foreach (string header in headerdata.Keys)
response.AddHeader(header, headerdata[header].ToString());
}
byte[] buffer;
if (responseData != null)
{
buffer = responseData;
}
else
{
if (!(contentType.Contains("image")
|| contentType.Contains("x-shockwave-flash")
|| contentType.Contains("application/x-oar")
|| contentType.Contains("application/vnd.ll.mesh")))
{
// Text
buffer = Encoding.UTF8.GetBytes(responseString);
}
else
{
// Binary!
buffer = Convert.FromBase64String(responseString);
}
response.ContentLength64 = buffer.Length;
response.ContentEncoding = Encoding.UTF8;
}
return buffer;
}
public byte[] SendHTML404(OSHttpResponse response)
{
response.StatusCode = 404;
response.ContentType = "text/html";
string responseString = GetHTTP404();
byte[] buffer = Encoding.UTF8.GetBytes(responseString);
response.ContentLength64 = buffer.Length;
response.ContentEncoding = Encoding.UTF8;
return buffer;
}
public void Start()
{
Start(true, true);
}
///
/// Start the http server
///
///
/// If true then poll responses are performed asynchronsly.
/// Option exists to allow regression tests to perform processing synchronously.
///
public void Start(bool performPollResponsesAsync, bool runPool)
{
m_log.InfoFormat(
"[BASE HTTP SERVER]: Starting {0} server on port {1}", UseSSL ? "HTTPS" : "HTTP", Port);
try
{
//m_httpListener = new HttpListener();
NotSocketErrors = 0;
if (!m_ssl)
{
m_httpListener = tinyHTTPListener.Create(m_listenIPAddress, (int)m_port);
m_httpListener.ExceptionThrown += httpServerException;
if (DebugLevel > 0)
{
m_httpListener.LogWriter = httpserverlog;
httpserverlog.DebugLevel = 1;
}
// Uncomment this line in addition to those in HttpServerLogWriter
// if you want more detailed trace information from the HttpServer
//m_httpListener2.DisconnectHandler = httpServerDisconnectMonitor;
}
else
{
m_httpListener = tinyHTTPListener.Create(IPAddress.Any, (int)m_port, m_cert);
if(m_certificateValidationCallback != null)
m_httpListener.CertificateValidationCallback = m_certificateValidationCallback;
m_httpListener.ExceptionThrown += httpServerException;
if (DebugLevel > 0)
{
m_httpListener.LogWriter = httpserverlog;
httpserverlog.DebugLevel = 1;
}
}
m_httpListener.RequestReceived += OnRequest;
m_httpListener.Start(64);
lock(m_generalLock)
{
if (runPool)
{
if(m_pollServiceManager == null)
m_pollServiceManager = new PollServiceRequestManager(performPollResponsesAsync, 2, 25000);
m_pollServiceManager.Start();
}
}
HTTPDRunning = true;
}
catch (Exception e)
{
m_log.Error("[BASE HTTP SERVER]: Error - " + e.Message);
m_log.Error("[BASE HTTP SERVER]: Tip: Do you have permission to listen on port " + m_port + "?");
// We want this exception to halt the entire server since in current configurations we aren't too
// useful without inbound HTTP.
throw e;
}
m_requestsProcessedStat
= new Stat(
"HTTPRequestsServed",
"Number of inbound HTTP requests processed",
"",
"requests",
"httpserver",
Port.ToString(),
StatType.Pull,
MeasuresOfInterest.AverageChangeOverTime,
stat => stat.Value = RequestNumber,
StatVerbosity.Debug);
StatsManager.RegisterStat(m_requestsProcessedStat);
}
public void httpServerDisconnectMonitor(IHttpClientContext source, SocketError err)
{
switch (err)
{
case SocketError.NotSocket:
NotSocketErrors++;
break;
}
}
public void httpServerException(object source, Exception exception)
{
if (source.ToString() == "HttpServer.HttpListener" && exception.ToString().StartsWith("Mono.Security.Protocol.Tls.TlsException"))
return;
m_log.ErrorFormat("[BASE HTTP SERVER]: {0} had an exception {1}", source.ToString(), exception.ToString());
}
public void Stop(bool stopPool = false)
{
HTTPDRunning = false;
StatsManager.DeregisterStat(m_requestsProcessedStat);
try
{
lock(m_generalLock)
{
if (stopPool && m_pollServiceManager != null)
m_pollServiceManager.Stop();
}
m_httpListener.ExceptionThrown -= httpServerException;
//m_httpListener2.DisconnectHandler = null;
m_httpListener.LogWriter = null;
m_httpListener.RequestReceived -= OnRequest;
m_httpListener.Stop();
}
catch (NullReferenceException)
{
m_log.Warn("[BASE HTTP SERVER]: Null Reference when stopping HttpServer.");
}
}
public void RemoveStreamHandler(string httpMethod, string path)
{
if (m_streamHandlers.TryRemove(path, out IRequestHandler dummy))
return;
string handlerKey = GetHandlerKey(httpMethod, path);
//m_log.DebugFormat("[BASE HTTP SERVER]: Removing handler key {0}", handlerKey);
m_streamHandlers.TryRemove(handlerKey, out dummy);
}
public void RemoveStreamHandler(string path)
{
m_streamHandlers.TryRemove(path, out IRequestHandler dummy);
}
public void RemoveSimpleStreamHandler(string path)
{
if(m_simpleStreamHandlers.TryRemove(path, out ISimpleStreamHandler dummy))
return;
m_simpleStreamVarPath.TryRemove(path, out ISimpleStreamHandler dummy2);
}
public void RemoveHTTPHandler(string httpMethod, string path)
{
if (path == null) return; // Caps module isn't loaded, tries to remove handler where path = null
lock (m_HTTPHandlers)
{
if (httpMethod != null && httpMethod.Length == 0)
{
m_HTTPHandlers.Remove(path);
return;
}
m_HTTPHandlers.Remove(GetHandlerKey(httpMethod, path));
}
}
public void RemovePollServiceHTTPHandler(string httpMethod, string path)
{
m_pollHandlers.TryRemove(path, out PollServiceEventArgs dummy);
}
public void RemovePollServiceHTTPHandler(string path)
{
m_pollHandlers.TryRemove(path, out PollServiceEventArgs dummy);
}
// public bool RemoveAgentHandler(string agent, IHttpAgentHandler handler)
// {
// lock (m_agentHandlers)
// {
// IHttpAgentHandler foundHandler;
//
// if (m_agentHandlers.TryGetValue(agent, out foundHandler) && foundHandler == handler)
// {
// m_agentHandlers.Remove(agent);
// return true;
// }
// }
//
// return false;
// }
public void RemoveXmlRPCHandler(string method)
{
lock (m_rpcHandlers)
m_rpcHandlers.Remove(method);
}
public void RemoveJsonRPCHandler(string method)
{
lock(jsonRpcHandlers)
jsonRpcHandlers.Remove(method);
}
public bool RemoveLLSDHandler(string path, LLSDMethod handler)
{
lock (m_llsdHandlers)
{
LLSDMethod foundHandler;
if (m_llsdHandlers.TryGetValue(path, out foundHandler) && foundHandler == handler)
{
m_llsdHandlers.Remove(path);
return true;
}
}
return false;
}
public string GetHTTP404()
{
string file = Path.Combine(".", "http_404.html");
if (!File.Exists(file))
return getDefaultHTTP404();
StreamReader sr = File.OpenText(file);
string result = sr.ReadToEnd();
sr.Close();
return result;
}
// Fallback HTTP responses in case the HTTP error response files don't exist
private static string getDefaultHTTP404()
{
return "404 Page not found
Ooops!
The page you requested has been obsconded with by knomes. Find hippos quick!
";
}
}
public class HttpServerContextObj
{
public IHttpClientContext context = null;
public IHttpRequest req = null;
public OSHttpRequest oreq = null;
public OSHttpResponse oresp = null;
public HttpServerContextObj(IHttpClientContext contxt, IHttpRequest reqs)
{
context = contxt;
req = reqs;
}
public HttpServerContextObj(OSHttpRequest osreq, OSHttpResponse osresp)
{
oreq = osreq;
oresp = osresp;
}
}
///
/// Relays HttpServer log messages to our own logging mechanism.
///
/// To use this you must uncomment the switch section
///
/// You may also be able to get additional trace information from HttpServer if you uncomment the UseTraceLogs
/// property in StartHttp() for the HttpListener
///
public class HttpServerLogWriter : ILogWriter
{
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
public int DebugLevel {get; set;} = (int)LogPrio.Error;
public void Write(object source, LogPrio priority, string message)
{
if((int)priority < DebugLevel)
return;
switch (priority)
{
case LogPrio.Trace:
m_log.DebugFormat("[{0}]: {1}", source, message);
break;
case LogPrio.Debug:
m_log.DebugFormat("[{0}]: {1}", source, message);
break;
case LogPrio.Error:
m_log.ErrorFormat("[{0}]: {1}", source, message);
break;
case LogPrio.Info:
m_log.InfoFormat("[{0}]: {1}", source, message);
break;
case LogPrio.Warning:
m_log.WarnFormat("[{0}]: {1}", source, message);
break;
case LogPrio.Fatal:
m_log.ErrorFormat("[{0}]: FATAL! - {1}", source, message);
break;
default:
break;
}
return;
}
}
public class IndexPHPHandler : SimpleStreamHandler
{
BaseHttpServer m_server;
public IndexPHPHandler(BaseHttpServer server)
: base("/index.php")
{
m_server = server;
}
protected override void ProcessRequest(IOSHttpRequest httpRequest, IOSHttpResponse httpResponse)
{
httpResponse.KeepAlive = false;
if (m_server == null || !m_server.HTTPDRunning)
{
httpResponse.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
if (httpRequest.QueryString.Count == 0)
{
httpResponse.StatusCode = (int)HttpStatusCode.Redirect;
httpResponse.AddHeader("Location", "http://opensimulator.org");
return;
}
if (httpRequest.QueryFlags.Contains("about"))
{
httpResponse.StatusCode = (int)HttpStatusCode.Redirect;
httpResponse.AddHeader("Location", "http://opensimulator.org/wiki/0.9.2.0_Release");
return;
}
if (!httpRequest.QueryAsDictionary.TryGetValue("method", out string methods) || string.IsNullOrWhiteSpace(methods))
{
httpResponse.StatusCode = (int)HttpStatusCode.NotFound; ;
return;
}
string[] splited = methods.Split(new char[] { ',' });
string method = splited[0];
if (string.IsNullOrWhiteSpace(method))
{
httpResponse.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
SimpleStreamMethod sh = m_server.TryGetIndexPHPMethodHandler(method);
if (sh == null)
{
httpResponse.StatusCode = (int)HttpStatusCode.NotFound;
return;
}
try
{
sh?.Invoke(httpRequest, httpResponse);
}
catch
{
httpResponse.StatusCode = (int)HttpStatusCode.InternalServerError;
}
}
}
}