ServerUtils.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621
  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.Length == 0)
  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 not List<string> l)
  312. continue;
  313. if (elems.Length > 1 && !string.IsNullOrWhiteSpace(elems[1]))
  314. l.Add(System.Web.HttpUtility.UrlDecode(elems[1]));
  315. else
  316. l.Add(string.Empty);
  317. }
  318. else
  319. {
  320. List<string> newList = new List<string>();
  321. if (elems.Length > 1 && !String.IsNullOrWhiteSpace(elems[1]))
  322. newList.Add(System.Web.HttpUtility.UrlDecode(elems[1]));
  323. else
  324. newList.Add(String.Empty);
  325. result[name] = newList;
  326. }
  327. }
  328. else
  329. {
  330. if (!result.ContainsKey(name))
  331. {
  332. if (elems.Length > 1 && !String.IsNullOrWhiteSpace(elems[1]))
  333. result[name] = System.Web.HttpUtility.UrlDecode(elems[1]);
  334. else
  335. result[name] = String.Empty;
  336. }
  337. }
  338. }
  339. return result;
  340. }
  341. public static string BuildQueryString(Dictionary<string, object> data)
  342. {
  343. // this is not conform to html url encoding
  344. // can only be used on Body of POST or PUT
  345. StringBuilder sb = new StringBuilder(4096);
  346. string pvalue;
  347. foreach (KeyValuePair<string, object> kvp in data)
  348. {
  349. if (kvp.Value is List<string> l)
  350. {
  351. string nkey = System.Web.HttpUtility.UrlEncode(kvp.Key);
  352. for (int i = 0; i < l.Count; ++i)
  353. {
  354. if (sb.Length != 0)
  355. sb.Append('&');
  356. sb.Append(nkey);
  357. sb.Append("[]=");
  358. sb.Append(System.Web.HttpUtility.UrlEncode(l[i]));
  359. }
  360. }
  361. else if (kvp.Value is Dictionary<string, object>)
  362. {
  363. // encode complex structures as JSON
  364. string js;
  365. try
  366. {
  367. LitJson.JsonMapper.RegisterExporter<UUID>((uuid, writer) => writer.Write(uuid.ToString()));
  368. js = LitJson.JsonMapper.ToJson(kvp.Value);
  369. }
  370. //catch(Exception e)
  371. catch
  372. {
  373. continue;
  374. }
  375. if (sb.Length != 0)
  376. sb.Append('&');
  377. sb.Append(System.Web.HttpUtility.UrlEncode(kvp.Key));
  378. sb.Append('=');
  379. sb.Append(System.Web.HttpUtility.UrlEncode(js));
  380. }
  381. else
  382. {
  383. if (sb.Length != 0)
  384. sb.Append('&');
  385. sb.Append(System.Web.HttpUtility.UrlEncode(kvp.Key));
  386. pvalue = kvp.Value.ToString();
  387. if (!string.IsNullOrEmpty(pvalue))
  388. {
  389. sb.Append('=');
  390. sb.Append(System.Web.HttpUtility.UrlEncode(pvalue));
  391. }
  392. }
  393. }
  394. return sb.ToString();
  395. }
  396. public static string BuildXmlResponse(Dictionary<string, object> data)
  397. {
  398. XmlDocument doc = new XmlDocument();
  399. XmlNode xmlnode = doc.CreateNode(XmlNodeType.XmlDeclaration, "", "");
  400. doc.AppendChild(xmlnode);
  401. XmlElement rootElement = doc.CreateElement("", "ServerResponse", "");
  402. doc.AppendChild(rootElement);
  403. BuildXmlData(rootElement, data);
  404. return doc.InnerXml;
  405. }
  406. private static void BuildXmlData(XmlElement parent, Dictionary<string, object> data)
  407. {
  408. foreach (KeyValuePair<string, object> kvp in data)
  409. {
  410. if (kvp.Value is null)
  411. continue;
  412. XmlElement elem = parent.OwnerDocument.CreateElement("", XmlConvert.EncodeLocalName(kvp.Key), "");
  413. if (kvp.Value is Dictionary<string, object> dic)
  414. {
  415. XmlAttribute type = parent.OwnerDocument.CreateAttribute("", "type", "");
  416. type.Value = "List";
  417. elem.Attributes.Append(type);
  418. BuildXmlData(elem, dic);
  419. }
  420. else
  421. {
  422. elem.AppendChild(parent.OwnerDocument.CreateTextNode(kvp.Value.ToString()));
  423. }
  424. parent.AppendChild(elem);
  425. }
  426. }
  427. private static Dictionary<string, object> ScanXmlResponse(XmlReader xr)
  428. {
  429. Dictionary<string, object> ret = new Dictionary<string, object>();
  430. xr.Read();
  431. while (!xr.EOF && xr.NodeType != XmlNodeType.EndElement)
  432. {
  433. if (xr.IsStartElement())
  434. {
  435. string type = xr.GetAttribute("type");
  436. if (type != "List")
  437. {
  438. if (xr.IsEmptyElement)
  439. {
  440. ret[XmlConvert.DecodeName(xr.Name)] = "";
  441. xr.Read();
  442. }
  443. else
  444. ret[XmlConvert.DecodeName(xr.Name)] = xr.ReadElementContentAsString();
  445. }
  446. else
  447. {
  448. string name = XmlConvert.DecodeName(xr.Name);
  449. if (xr.IsEmptyElement)
  450. ret[name] = new Dictionary<string, object>();
  451. else
  452. ret[name] = ScanXmlResponse(xr);
  453. xr.Read();
  454. }
  455. }
  456. else
  457. xr.Read();
  458. }
  459. return ret;
  460. }
  461. private static readonly XmlReaderSettings ParseXmlStringResponseXmlReaderSettings = new()
  462. {
  463. IgnoreWhitespace = true,
  464. IgnoreComments = true,
  465. ConformanceLevel = ConformanceLevel.Fragment,
  466. CloseInput = true,
  467. MaxCharactersInDocument = 50_000_000
  468. };
  469. private static readonly XmlParserContext ParseXmlResponseXmlParserContext = new(null, null, null, XmlSpace.None)
  470. {
  471. Encoding = Util.UTF8NoBomEncoding
  472. };
  473. public static Dictionary<string, object> ParseXmlResponse(string data)
  474. {
  475. if(!string.IsNullOrEmpty(data))
  476. {
  477. try
  478. {
  479. using XmlReader xr = XmlReader.Create(new StringReader(data),
  480. ParseXmlStringResponseXmlReaderSettings, ParseXmlResponseXmlParserContext);
  481. if (xr.ReadToFollowing("ServerResponse"))
  482. return ScanXmlResponse(xr);
  483. }
  484. catch (Exception e)
  485. {
  486. m_log.Debug($"[serverUtils.ParseXmlResponse]: failed error: {e.Message}\n --string:\n{data}\n");
  487. }
  488. }
  489. return [];
  490. }
  491. private static readonly XmlReaderSettings ParseXmlStreamResponseXmlReaderSettings = new()
  492. {
  493. IgnoreWhitespace = true,
  494. IgnoreComments = true,
  495. ConformanceLevel = ConformanceLevel.Fragment,
  496. CloseInput = true,
  497. MaxCharactersInDocument = 50_000_000
  498. };
  499. public static Dictionary<string, object> ParseXmlResponse(Stream src)
  500. {
  501. using XmlReader xr = XmlReader.Create(src,
  502. ParseXmlStreamResponseXmlReaderSettings, ParseXmlResponseXmlParserContext);
  503. if (xr.ReadToFollowing("ServerResponse"))
  504. return ScanXmlResponse(xr);
  505. return new Dictionary<string, object>();
  506. }
  507. public static IConfig GetConfig(string configFile, string configName)
  508. {
  509. IConfig config;
  510. if (File.Exists(configFile))
  511. {
  512. IConfigSource configsource = new IniConfigSource(configFile);
  513. config = configsource.Configs[configName];
  514. }
  515. else
  516. config = null;
  517. return config;
  518. }
  519. public static IConfigSource LoadInitialConfig(string url)
  520. {
  521. IConfigSource source = new XmlConfigSource();
  522. m_log.InfoFormat("[SERVER UTILS]: {0} is a http:// URI, fetching ...", url);
  523. // The ini file path is a http URI
  524. // Try to read it
  525. try
  526. {
  527. IConfigSource cs;
  528. using (XmlReader r = XmlReader.Create(url))
  529. {
  530. cs = new XmlConfigSource(r);
  531. source.Merge(cs);
  532. }
  533. }
  534. catch (Exception e)
  535. {
  536. m_log.FatalFormat("[SERVER UTILS]: Exception reading config from URI {0}\n" + e.ToString(), url);
  537. Environment.Exit(1);
  538. }
  539. return source;
  540. }
  541. }
  542. }