/* * 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; using System.Collections.Generic; using System.IO; using System.Net; using System.Reflection; using System.Security; using System.Text; using log4net; using OpenMetaverse; using OpenMetaverse.StructuredData; using Nini.Config; using OpenSim.Framework; using OpenSim.Data.MySQL; using OpenSim.Framework.Servers.HttpServer; using OpenSim.Server.Handlers.Base; namespace OpenSim.Server.Handlers.Web { public class WebServerInConnector : 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 MySQLGenericHandler m_Database = null; private Hashtable mime = new Hashtable(); private Hashtable ssi = new Hashtable(); public WebServerInConnector(IConfigSource config, IHttpServer server, string configName) : base(config, server, configName) { m_Config = config; string dllName = String.Empty; string connString = String.Empty; // // 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 MySQLGenericHandler(connString); if (m_Database == null) throw new Exception("Could not find a storage interface in the given module " + dllName); 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.GetCount("Presence", "RegionID != '00000000-0000-0000-0000-000000000000'"); // Locals online but not HGing, and HGers in world. long HGin = m_Database.GetCount("Presence", "UserID NOT IN (SELECT PrincipalID FROM UserAccounts)"); // HGers in world. long locOut = m_Database.GetCount("hg_traveling_data", "GridExternalName != '" + ssi["uri"] + "'"); // Locals that are HGing. Hashtable reply = new Hashtable(); ssi["members"] = m_Database.GetCount("UserAccounts").ToString(); ssi["sims"] = m_Database.GetCount("regions").ToString(); ssi["inworld"] = (locIn - HGin).ToString(); ssi["outworld"] = locOut.ToString(); ssi["hgers"] = HGin.ToString(); ssi["month"] = m_Database.GetCount("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}", method, reqpath, 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 { 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()) + text("email", "email", "email", fields["email"].ToString(), 0, false) + text("password", "password", "password", "", 14, 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 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"; } } }