using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Net; using System.Reflection; using System.Security; using System.Text; using log4net; using Nini.Config; using OpenMetaverse; using OpenMetaverse.StructuredData; using OpenSim.Data.MySQL; using OpenSim.Framework; using OpenSim.Framework.Servers.HttpServer; using OpenSim.Server.Handlers.Base; namespace OpenSim.Server.Handlers.Web { public class WebServerConnector : ServiceConnector { // This is all slow and clunky, it's not a real web server, just something to use if you don't want a real one. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); private IConfigSource m_Config; protected MySQLRaw m_database = null; private Hashtable mime = new Hashtable(); private Hashtable ssi = new Hashtable(); public WebServerConnector(IConfigSource config, IHttpServer server, string configName) : base(config, server, configName) { string dllName = String.Empty; string connString = String.Empty; m_Config = config; // Try reading the [DatabaseService] section, if it exists IConfig dbConfig = m_Config.Configs["DatabaseService"]; if (dbConfig != null) { if (dllName == String.Empty) dllName = dbConfig.GetString("StorageProvider", String.Empty); if (connString == String.Empty) connString = dbConfig.GetString("ConnectionString", String.Empty); } if (dllName.Equals(String.Empty)) throw new Exception("No StorageProvider configured"); //// TODO - Should do the plugin thing to pick between database backends. //// Or not, we are all using MariaDB anyway. // m_Database = LoadPlugin(dllName, new Object[] { connString }); m_database = new MySQLRaw(connString); mime.Add(".gz", "application/gzip"); mime.Add(".js", "application/javascript"); mime.Add(".json", "application/json"); mime.Add(".pdf", "application/pdf"); mime.Add(".rtf", "application/rtf"); mime.Add(".zip", "application/zip"); mime.Add(".xz", "application/x-xz"); mime.Add(".gif", "image/gif"); mime.Add(".png", "image/png"); mime.Add(".jp2", "image/jp2"); mime.Add(".jpg2", "image/jp2"); mime.Add(".jpe", "image/jpeg"); mime.Add(".jpg", "image/jpeg"); mime.Add(".jpeg", "image/jpeg"); mime.Add(".svg", "image/svg+xml"); mime.Add(".svgz", "image/svg+xml"); mime.Add(".tif", "image/tiff"); mime.Add(".tiff", "image/tiff"); mime.Add(".css", "text/css"); mime.Add(".html", "text/html"); mime.Add(".htm", "text/html"); mime.Add(".shtml", "text/html"); // mime.Add(".md", "text/markdown"); // mime.Add(".markdown","text/markdown"); mime.Add(".txt", "text/plain"); IConfig cfg = m_Config.Configs["GridInfoService"]; string HomeURI = Util.GetConfigVarFromSections(m_Config, "HomeURI", new string[] { "Startup", "Hypergrid" }, String.Empty); ssi.Add("grid", cfg.GetString("gridname", "my grid")); ssi.Add("uri", cfg.GetString("login", HomeURI)); ssi.Add("version", VersionInfo.Version); server.AddHTTPHandler("/web/", WebRequestHandler); } private Hashtable WebRequestHandler(Hashtable request) { long locIn = m_database.Count("Presence", "RegionID != '00000000-0000-0000-0000-000000000000'"); // Locals online but not HGing, and HGers in world. long HGin = m_database.Count("Presence", "UserID NOT IN (SELECT PrincipalID FROM UserAccounts)"); // HGers in world. long locOut = m_database.Count("hg_traveling_data", "GridExternalName != '" + ssi["uri"] + "'"); // Locals that are HGing. Hashtable reply = new Hashtable(); ssi["members"] = m_database.Count("UserAccounts").ToString(); ssi["sims"] = m_database.Count("regions").ToString(); ssi["inworld"] = (locIn - HGin).ToString(); ssi["outworld"] = locOut.ToString(); ssi["hgers"] = HGin.ToString(); ssi["month"] = m_database.Count("GridUser", "Login > UNIX_TIMESTAMP(FROM_UNIXTIME(UNIX_TIMESTAMP(now()) - 2419200))").ToString(); string reqpath = (string) request["uri"]; string[] query = (string[]) request["querystringkeys"]; Hashtable headers = (Hashtable) request["headers"]; string method = (string) request["http-method"]; string type = (string) request["content-type"]; string body = (string) request["body"]; string file = reqpath.Remove(0, 5); string path = Path.Combine(Util.webDir(), file); m_log.InfoFormat("[WEB SERVICE]: {0} method path {1} type {2} body {3}.", method, reqpath, type, body); foreach (DictionaryEntry h in headers) m_log.InfoFormat("[WEB SERVICE]: {0} method path {1} header {2} = {3}", method, reqpath, (string) h.Key, (string) h.Value); foreach (String q in query) m_log.InfoFormat("[WEB SERVICE]: {0} method path {1} query {2} value {3}", method, reqpath, q, (string) request[q]); reply["int_response_code"] = 200; if ("GET" == method) { if (File.Exists(path)) { string m = (string) mime[Path.GetExtension(path).ToLower()]; reply["content_type"] = m; if ((null == m) || ("text/" != m.Substring(0, 5))) reply["bin_response_data"] = File.ReadAllBytes(path); else { StreamReader csr = File.OpenText(path); string content = csr.ReadToEnd(); // Slow and wasteful, but I'm expecting only tiny web files, not accessed very often. foreach (DictionaryEntry v in ssi) { content = content.Replace("", (string) v.Value); } reply["str_response_string"] = content; csr.Close(); } } else { if ("account.html" == file) reply["str_response_string"] = loginPage(null, ""); else { m_log.ErrorFormat("[WEB SERVICE]: Unable to read {0}.", path); reply["int_response_code"] = 404; reply["content_type"] = "text/html"; reply["str_response_string"] = "404 Unknown page" + "404 error, can't find the " + reqpath + " page.

 

"; } } } else if ("POST" == method) { Hashtable fields = new Hashtable(); string[] bdy = body.Split('&'); body = ""; foreach (String bd in bdy) { string[] b = bd.Split('='); if (b.Length == 0) continue; String n = System.Web.HttpUtility.UrlDecode(b[0]); String v = ""; if (b.Length > 1) v = System.Web.HttpUtility.UrlDecode(b[1]); fields[n] = v; body = body + "

" + n + " = " + v + "

\n"; } if ("account.html" == file) { if ("logout" == fields["doit"].ToString()) reply["str_response_string"] = loginPage(null, "Logged out."); else if ("create" == fields["doit"].ToString()) { if ("" == fields["email"].ToString()) reply["str_response_string"] = loginPage(fields, "Please supply an email address when creating an account."); else { reply["str_response_string"] = loggedOnPage(body, fields); } } else if ("list" == fields["doit"].ToString()) { List< Hashtable > rows = m_database.Select("UserAccounts", "CONCAT(FirstName,' ',LastName) as Name,UserTitle as Title,UserLevel as Level,UserFlags as Flags,PrincipalID as UUID", "", "Name"); reply["str_response_string"] = "member accounts" + table(rows, new string[5] {"Name", "Title", "Level", "Flags", "UUID"}, "member accounts", "account.html?doit=edit&token=" + fields["token"].ToString(), "UUID") + "

" + button("my account") + "

"; } else { reply["str_response_string"] = loggedOnPage(body, fields); } } else { m_log.ErrorFormat("[WEB SERVICE]: No such POST target {0}.", path); reply["int_response_code"] = 404; reply["content_type"] = "text/html"; reply["str_response_string"] = "404 Unknown page" + "404 error, can't find the " + reqpath + " page.

 

"; } } else { m_log.ErrorFormat("[WEB SERVICE]: UNKNOWN method {0} path {1}.", method, reqpath); reply["int_response_code"] = 404; reply["content_type"] = "text/html"; reply["str_response_string"] = "Unknown method" + "HUH! For " + reqpath + " page.

 

"; } m_log.Info("[WEB SERVICE]: "); return reply; } private string loginPage(Hashtable fields, string message) { string f = ""; string l = ""; string e = ""; if (null != fields) { f = fields["firstName"].ToString(); l = fields["lastName"].ToString(); e = fields["email"].ToString(); } return header(ssi["grid"] + " account") + form("account.html", "", text("text", "first name", "firstName", f, 16, true) + text("text", "last name", "lastName", l, 16, true) + text("email", "email", "email", e, 0, false) + text("password", "password", "password", "", 14,true) + button("create") + button("login") ) + "

" + message + "

" + footer(); } private string loggedOnPage(string body, Hashtable fields) { return header(ssi["grid"] + " account") + "

" + ssi["grid"] + " account for " + fields["firstName"].ToString() + " " + fields["lastName"].ToString() + "

" + form("account.html", fields["token"].ToString(), hidden("firstName", fields["firstName"].ToString()) + hidden("lastName", fields["lastName"].ToString()) // + hidden("UUID", fields["UUID"].ToString()) + text("email", "email", "email", fields["email"].ToString(), 0, false) + text("password", "password", "password", "", 14, false) // + text("title", "text", "title", fields["title"].ToString(), 0, false) + select("type", "type", option("", false) + option("approved", true) + option("disabled", false) + option("god", false) ) + button("delete") + button("list") + button("logout") // + button("read") + button("update") ) + body + footer(); } private string header(string title) { return "\n \n " + title + "\n \n \n"; } private string table(List< Hashtable > rows, string[] fields, string caption, string URL, string id) { string tbl = ""; bool head = true; string address = ""; string addrend = ""; foreach (Hashtable row in rows) { if (0 == fields.Length) { int c = 0; foreach (DictionaryEntry r in row) c++; fields = new string[c]; c = 0; foreach (DictionaryEntry r in row) fields[c++] = (string) r.Key; } string line = ""; address = ""; if ("" != URL) { address = ""; if (head) { foreach (string s in fields) line = line + ""; tbl = tbl + line + "\n"; head = false; } line = ""; foreach (string s in fields) { if (s == id) line = line + ""; else line = line + ""; } tbl = tbl + line + "\n"; } return tbl + "
" + caption + "
" + s + "
" + address + row[s] + addrend + "" + row[s] + "
"; } private string form(string action, string token, string form) { return "
\n" + hidden("token", token) + form + "
\n"; } private string hidden(string name, string val) { return " \n"; } private string text(string type, string title, string name, string val, int max, bool required) { string extra = ""; if (0 < max) extra = extra + " maxlength=\"" + max.ToString() + "\""; if (required) extra = extra + " required"; if ("" != val) val = "value=\"" + val + "\""; return "

" + title + " :

\n"; } private string select(string title, string name, string options) { return "

" + title + " : \n \n

\n"; } private string option(string title, bool selected) { string sel = ""; if (selected) sel = " selected"; return " \n"; } private string button(string title) { return " \n"; } private string footer() { return " \n\n"; } } }