ServerUtils.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSimulator Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. using System;
  28. using System.IO;
  29. using System.Reflection;
  30. using System.Xml;
  31. using System.Xml.Serialization;
  32. using System.Text;
  33. using System.Collections.Generic;
  34. using log4net;
  35. using Nini.Config;
  36. using OpenSim.Framework;
  37. using OpenMetaverse;
  38. using Mono.Addins;
  39. using OpenSim.Framework.Servers.HttpServer;
  40. using OpenSim.Framework.Servers;
  41. using OpenMetaverse.StructuredData; // LitJson is hidden on this
  42. [assembly:AddinRoot("Robust", OpenSim.VersionInfo.VersionNumber)]
  43. namespace OpenSim.Server.Base
  44. {
  45. [TypeExtensionPoint(Path="/Robust/Connector", Name="RobustConnector")]
  46. public interface IRobustConnector
  47. {
  48. string ConfigName
  49. {
  50. get;
  51. }
  52. bool Enabled
  53. {
  54. get;
  55. }
  56. string PluginPath
  57. {
  58. get;
  59. set;
  60. }
  61. uint Configure(IConfigSource config);
  62. void Initialize(IHttpServer server);
  63. void Unload();
  64. }
  65. public class PluginLoader
  66. {
  67. static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  68. public AddinRegistry Registry
  69. {
  70. get;
  71. private set;
  72. }
  73. public IConfigSource Config
  74. {
  75. get;
  76. private set;
  77. }
  78. public PluginLoader(IConfigSource config, string registryPath)
  79. {
  80. Config = config;
  81. Registry = new AddinRegistry(registryPath, ".");
  82. //suppress_console_output_(true);
  83. AddinManager.Initialize(registryPath);
  84. //suppress_console_output_(false);
  85. AddinManager.Registry.Update();
  86. CommandManager commandmanager = new CommandManager(Registry);
  87. AddinManager.AddExtensionNodeHandler("/Robust/Connector", OnExtensionChanged);
  88. }
  89. private static TextWriter prev_console_;
  90. // Temporarily masking the errors reported on start
  91. // This is caused by a non-managed dll in the ./bin dir
  92. // when the registry is initialized. The dll belongs to
  93. // libomv, which has a hard-coded path to "." for pinvoke
  94. // to load the openjpeg dll
  95. //
  96. // Will look for a way to fix, but for now this keeps the
  97. // confusion to a minimum. this was copied from our region
  98. // plugin loader, we have been doing this in there for a long time.
  99. //
  100. public void suppress_console_output_(bool save)
  101. {
  102. if (save)
  103. {
  104. prev_console_ = System.Console.Out;
  105. System.Console.SetOut(new StreamWriter(Stream.Null));
  106. }
  107. else
  108. {
  109. if (prev_console_ != null)
  110. System.Console.SetOut(prev_console_);
  111. }
  112. }
  113. private void OnExtensionChanged(object s, ExtensionNodeEventArgs args)
  114. {
  115. IRobustConnector connector = (IRobustConnector)args.ExtensionObject;
  116. Addin a = Registry.GetAddin(args.ExtensionNode.Addin.Id);
  117. if(a == null)
  118. {
  119. Registry.Rebuild(null);
  120. a = Registry.GetAddin(args.ExtensionNode.Addin.Id);
  121. }
  122. switch(args.Change)
  123. {
  124. case ExtensionChange.Add:
  125. if (a.AddinFile.Contains(Registry.DefaultAddinsFolder))
  126. {
  127. m_log.InfoFormat("[SERVER UTILS]: Adding {0} from registry", a.Name);
  128. connector.PluginPath = System.IO.Path.Combine(Registry.DefaultAddinsFolder,a.Name.Replace(',', '.')); }
  129. else
  130. {
  131. m_log.InfoFormat("[SERVER UTILS]: Adding {0} from ./bin", a.Name);
  132. connector.PluginPath = a.AddinFile;
  133. }
  134. LoadPlugin(connector);
  135. break;
  136. case ExtensionChange.Remove:
  137. m_log.InfoFormat("[SERVER UTILS]: Removing {0}", a.Name);
  138. UnloadPlugin(connector);
  139. break;
  140. }
  141. }
  142. private void LoadPlugin(IRobustConnector connector)
  143. {
  144. IHttpServer server = null;
  145. uint port = connector.Configure(Config);
  146. if(connector.Enabled)
  147. {
  148. server = GetServer(connector, port);
  149. connector.Initialize(server);
  150. }
  151. else
  152. {
  153. m_log.InfoFormat("[SERVER UTILS]: {0} Disabled.", connector.ConfigName);
  154. }
  155. }
  156. private void UnloadPlugin(IRobustConnector connector)
  157. {
  158. m_log.InfoFormat("[SERVER UTILS]: Unloading {0}", connector.ConfigName);
  159. connector.Unload();
  160. }
  161. private IHttpServer GetServer(IRobustConnector connector, uint port)
  162. {
  163. IHttpServer server;
  164. if(port != 0)
  165. server = MainServer.GetHttpServer(port);
  166. else
  167. server = MainServer.Instance;
  168. return server;
  169. }
  170. }
  171. public static class ServerUtils
  172. {
  173. private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
  174. public static byte[] SerializeResult(XmlSerializer xs, object data)
  175. {
  176. using (MemoryStream ms = new MemoryStream())
  177. using (XmlTextWriter xw = new XmlTextWriter(ms, Util.UTF8))
  178. {
  179. xw.Formatting = Formatting.Indented;
  180. xs.Serialize(xw, data);
  181. xw.Flush();
  182. ms.Seek(0, SeekOrigin.Begin);
  183. byte[] ret = ms.ToArray();
  184. return ret;
  185. }
  186. }
  187. /// <summary>
  188. /// Load a plugin from a dll with the given class or interface
  189. /// </summary>
  190. /// <param name="dllName"></param>
  191. /// <param name="args">The arguments which control which constructor is invoked on the plugin</param>
  192. /// <returns></returns>
  193. public static T LoadPlugin<T> (string dllName, Object[] args) where T:class
  194. {
  195. // This is good to debug configuration problems
  196. //if (dllName == string.Empty)
  197. // Util.PrintCallStack();
  198. string className = String.Empty;
  199. // The path for a dynamic plugin will contain ":" on Windows
  200. string[] parts = dllName.Split (new char[] {':'});
  201. if (parts.Length < 3)
  202. {
  203. // Linux. There will be ':' but the one we're looking for
  204. dllName = parts [0];
  205. if (parts.Length > 1)
  206. className = parts[1];
  207. }
  208. else
  209. {
  210. // This is Windows - we must replace the ":" in the path
  211. dllName = String.Format ("{0}:{1}", parts [0], parts [1]);
  212. if (parts.Length > 2)
  213. className = parts[2];
  214. }
  215. // Handle extra string arguments in a more generic way
  216. if (dllName.Contains("@"))
  217. {
  218. string[] dllNameParts = dllName.Split(new char[] {'@'});
  219. dllName = dllNameParts[dllNameParts.Length - 1];
  220. List<Object> argList = new List<Object>(args);
  221. for (int i = 0 ; i < dllNameParts.Length - 1 ; ++i)
  222. argList.Add(dllNameParts[i]);
  223. args = argList.ToArray();
  224. }
  225. return LoadPlugin<T>(dllName, className, args);
  226. }
  227. /// <summary>
  228. /// Load a plugin from a dll with the given class or interface
  229. /// </summary>
  230. /// <param name="dllName"></param>
  231. /// <param name="className"></param>
  232. /// <param name="args">The arguments which control which constructor is invoked on the plugin</param>
  233. /// <returns></returns>
  234. public static T LoadPlugin<T>(string dllName, string className, Object[] args) where T:class
  235. {
  236. string interfaceName = typeof(T).ToString();
  237. try
  238. {
  239. Assembly pluginAssembly = Assembly.LoadFrom(dllName);
  240. foreach (Type pluginType in pluginAssembly.GetTypes())
  241. {
  242. if (pluginType.IsPublic)
  243. {
  244. if (className != String.Empty
  245. && pluginType.ToString() != pluginType.Namespace + "." + className)
  246. continue;
  247. Type typeInterface = pluginType.GetInterface(interfaceName);
  248. if (typeInterface != null)
  249. {
  250. T plug = null;
  251. try
  252. {
  253. plug = (T)Activator.CreateInstance(pluginType,
  254. args);
  255. }
  256. catch (Exception e)
  257. {
  258. if (!(e is System.MissingMethodException))
  259. {
  260. m_log.Error(string.Format("[SERVER UTILS]: Error loading plugin {0} from {1}. Exception: {2}",
  261. interfaceName,
  262. dllName,
  263. e.InnerException == null ? e.Message : e.InnerException.Message),
  264. e);
  265. }
  266. m_log.ErrorFormat("[SERVER UTILS]: Error loading plugin {0}: {1} args.Length {2}", dllName, e.Message, args.Length);
  267. return null;
  268. }
  269. return plug;
  270. }
  271. }
  272. }
  273. return null;
  274. }
  275. catch (ReflectionTypeLoadException rtle)
  276. {
  277. m_log.Error(string.Format("[SERVER UTILS]: Error loading plugin from {0}:\n{1}", dllName,
  278. String.Join("\n", Array.ConvertAll(rtle.LoaderExceptions, e => e.ToString()))),
  279. rtle);
  280. return null;
  281. }
  282. catch (Exception e)
  283. {
  284. m_log.Error(string.Format("[SERVER UTILS]: Error loading plugin from {0}", dllName), e);
  285. return null;
  286. }
  287. }
  288. public static Dictionary<string, object> ParseQueryString(string query)
  289. {
  290. string[] terms = query.Split(new char[] {'&'});
  291. int nterms = terms.Length;
  292. if (nterms == 0)
  293. return new Dictionary<string, object>();
  294. Dictionary<string, object> result = new Dictionary<string, object>(nterms);
  295. string name;
  296. for(int i = 0; i < nterms; ++i)
  297. {
  298. string[] elems = terms[i].Split(new char[] {'='});
  299. if (elems.Length == 0)
  300. continue;
  301. if(String.IsNullOrWhiteSpace(elems[0]))
  302. continue;
  303. name = System.Web.HttpUtility.UrlDecode(elems[0]);
  304. if (name.EndsWith("[]"))
  305. {
  306. name = name.Substring(0, name.Length - 2);
  307. if(String.IsNullOrWhiteSpace(name))
  308. continue;
  309. if (result.ContainsKey(name))
  310. {
  311. if (!(result[name] is List<string>))
  312. continue;
  313. List<string> l = (List<string>)result[name];
  314. if (elems.Length > 1 && !String.IsNullOrWhiteSpace(elems[1]))
  315. l.Add(System.Web.HttpUtility.UrlDecode(elems[1]));
  316. else
  317. l.Add(String.Empty);
  318. }
  319. else
  320. {
  321. List<string> newList = new List<string>();
  322. if (elems.Length > 1 && !String.IsNullOrWhiteSpace(elems[1]))
  323. newList.Add(System.Web.HttpUtility.UrlDecode(elems[1]));
  324. else
  325. newList.Add(String.Empty);
  326. result[name] = newList;
  327. }
  328. }
  329. else
  330. {
  331. if (!result.ContainsKey(name))
  332. {
  333. if (elems.Length > 1 && !String.IsNullOrWhiteSpace(elems[1]))
  334. result[name] = System.Web.HttpUtility.UrlDecode(elems[1]);
  335. else
  336. result[name] = String.Empty;
  337. }
  338. }
  339. }
  340. return result;
  341. }
  342. public static string BuildQueryString(Dictionary<string, object> data)
  343. {
  344. // this is not conform to html url encoding
  345. // can only be used on Body of POST or PUT
  346. StringBuilder sb = new StringBuilder(4096);
  347. string pvalue;
  348. foreach (KeyValuePair<string, object> kvp in data)
  349. {
  350. if (kvp.Value is List<string>)
  351. {
  352. List<string> l = (List<String>)kvp.Value;
  353. int llen = l.Count;
  354. string nkey = System.Web.HttpUtility.UrlEncode(kvp.Key);
  355. for(int i = 0; i < llen; ++i)
  356. {
  357. if (sb.Length != 0)
  358. sb.Append("&");
  359. sb.Append(nkey);
  360. sb.Append("[]=");
  361. sb.Append(System.Web.HttpUtility.UrlEncode(l[i]));
  362. }
  363. }
  364. else if(kvp.Value is Dictionary<string, object>)
  365. {
  366. // encode complex structures as JSON
  367. // needed for estate bans with the encoding used on xml
  368. // encode can be here because object does contain the structure information
  369. // but decode needs to be on estateSettings (or other user)
  370. string js;
  371. try
  372. {
  373. // bypass libovm, we dont need even more useless high level maps
  374. // this should only be called once.. but no problem, i hope
  375. // (other uses may need more..)
  376. LitJson.JsonMapper.RegisterExporter<UUID>((uuid, writer) => writer.Write(uuid.ToString()) );
  377. js = LitJson.JsonMapper.ToJson(kvp.Value);
  378. }
  379. // catch(Exception e)
  380. catch
  381. {
  382. continue;
  383. }
  384. if (sb.Length != 0)
  385. sb.Append("&");
  386. sb.Append(System.Web.HttpUtility.UrlEncode(kvp.Key));
  387. sb.Append("=");
  388. sb.Append(System.Web.HttpUtility.UrlEncode(js));
  389. }
  390. else
  391. {
  392. if (sb.Length != 0)
  393. sb.Append("&");
  394. sb.Append(System.Web.HttpUtility.UrlEncode(kvp.Key));
  395. pvalue = kvp.Value.ToString();
  396. if (!String.IsNullOrEmpty(pvalue))
  397. {
  398. sb.Append("=");
  399. sb.Append(System.Web.HttpUtility.UrlEncode(pvalue));
  400. }
  401. }
  402. }
  403. return sb.ToString();
  404. }
  405. public static string BuildXmlResponse(Dictionary<string, object> data)
  406. {
  407. XmlDocument doc = new XmlDocument();
  408. XmlNode xmlnode = doc.CreateNode(XmlNodeType.XmlDeclaration,
  409. "", "");
  410. doc.AppendChild(xmlnode);
  411. XmlElement rootElement = doc.CreateElement("", "ServerResponse","");
  412. doc.AppendChild(rootElement);
  413. BuildXmlData(rootElement, data);
  414. return doc.InnerXml;
  415. }
  416. private static void BuildXmlData(XmlElement parent, Dictionary<string, object> data)
  417. {
  418. foreach (KeyValuePair<string, object> kvp in data)
  419. {
  420. if (kvp.Value == null)
  421. continue;
  422. XmlElement elem = parent.OwnerDocument.CreateElement("",
  423. XmlConvert.EncodeLocalName(kvp.Key), "");
  424. if (kvp.Value is Dictionary<string, object>)
  425. {
  426. XmlAttribute type = parent.OwnerDocument.CreateAttribute("",
  427. "type", "");
  428. type.Value = "List";
  429. elem.Attributes.Append(type);
  430. BuildXmlData(elem, (Dictionary<string, object>)kvp.Value);
  431. }
  432. else
  433. {
  434. elem.AppendChild(parent.OwnerDocument.CreateTextNode(
  435. kvp.Value.ToString()));
  436. }
  437. parent.AppendChild(elem);
  438. }
  439. }
  440. private static Dictionary<string, object> ScanXmlResponse(XmlReader xr)
  441. {
  442. Dictionary<string, object> ret = new Dictionary<string, object>();
  443. xr.Read();
  444. while (!xr.EOF && xr.NodeType != XmlNodeType.EndElement)
  445. {
  446. if (xr.IsStartElement())
  447. {
  448. string type = xr.GetAttribute("type");
  449. if (type != "List")
  450. {
  451. if (xr.IsEmptyElement)
  452. {
  453. ret[XmlConvert.DecodeName(xr.Name)] = "";
  454. xr.Read();
  455. }
  456. else
  457. ret[XmlConvert.DecodeName(xr.Name)] = xr.ReadElementContentAsString();
  458. }
  459. else
  460. {
  461. string name = XmlConvert.DecodeName(xr.Name);
  462. if (xr.IsEmptyElement)
  463. ret[name] = new Dictionary<string, object>();
  464. else
  465. ret[name] = ScanXmlResponse(xr);
  466. xr.Read();
  467. }
  468. }
  469. else
  470. xr.Read();
  471. }
  472. return ret;
  473. }
  474. public static Dictionary<string, object> ParseXmlResponse(string data)
  475. {
  476. //m_log.DebugFormat("[XXX]: received xml string: {0}", data);
  477. try
  478. {
  479. XmlReaderSettings xset = new XmlReaderSettings() { IgnoreWhitespace = true, IgnoreComments = true, ConformanceLevel = ConformanceLevel.Fragment, CloseInput = true };
  480. XmlParserContext xpc = new XmlParserContext(null, null, null, XmlSpace.None);
  481. xpc.Encoding = Util.UTF8NoBomEncoding;
  482. using (XmlReader xr = XmlReader.Create(new StringReader(data), xset, xpc))
  483. {
  484. if(!xr.ReadToFollowing("ServerResponse"))
  485. return new Dictionary<string, object>();
  486. return ScanXmlResponse(xr);
  487. }
  488. }
  489. catch (Exception e)
  490. {
  491. m_log.DebugFormat("[serverUtils.ParseXmlResponse]: failed error: {0}\n --string:\n{1}\n", e.Message, data);
  492. }
  493. return new Dictionary<string, object>();
  494. }
  495. public static Dictionary<string, object> ParseXmlResponse(Stream src)
  496. {
  497. //m_log.DebugFormat("[XXX]: received xml string: {0}", data);
  498. try
  499. {
  500. XmlReaderSettings xset = new XmlReaderSettings() { IgnoreWhitespace = true, IgnoreComments = true, ConformanceLevel = ConformanceLevel.Fragment, CloseInput = true };
  501. XmlParserContext xpc = new XmlParserContext(null, null, null, XmlSpace.None);
  502. xpc.Encoding = Util.UTF8NoBomEncoding;
  503. using (XmlReader xr = XmlReader.Create(src, xset, xpc))
  504. {
  505. if (!xr.ReadToFollowing("ServerResponse"))
  506. return new Dictionary<string, object>();
  507. return ScanXmlResponse(xr);
  508. }
  509. }
  510. catch (Exception e)
  511. {
  512. m_log.DebugFormat("[serverUtils.ParseXmlResponse]: failed error: {0}", e.Message);
  513. }
  514. return new Dictionary<string, object>();
  515. }
  516. public static IConfig GetConfig(string configFile, string configName)
  517. {
  518. IConfig config;
  519. if (File.Exists(configFile))
  520. {
  521. IConfigSource configsource = new IniConfigSource(configFile);
  522. config = configsource.Configs[configName];
  523. }
  524. else
  525. config = null;
  526. return config;
  527. }
  528. public static IConfigSource LoadInitialConfig(string url)
  529. {
  530. IConfigSource source = new XmlConfigSource();
  531. m_log.InfoFormat("[SERVER UTILS]: {0} is a http:// URI, fetching ...", url);
  532. // The ini file path is a http URI
  533. // Try to read it
  534. try
  535. {
  536. IConfigSource cs;
  537. using( XmlReader r = XmlReader.Create(url))
  538. {
  539. cs = new XmlConfigSource(r);
  540. source.Merge(cs);
  541. }
  542. }
  543. catch (Exception e)
  544. {
  545. m_log.FatalFormat("[SERVER UTILS]: Exception reading config from URI {0}\n" + e.ToString(), url);
  546. Environment.Exit(1);
  547. }
  548. return source;
  549. }
  550. }
  551. }