RemoteConsole.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720
  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.Xml;
  29. using System.Collections;
  30. using System.Collections.Generic;
  31. using System.Diagnostics;
  32. using System.Reflection;
  33. using System.Text;
  34. using System.Text.RegularExpressions;
  35. using System.Threading;
  36. using System.Timers;
  37. using OpenMetaverse;
  38. using Nini.Config;
  39. using OpenSim.Framework.Servers.HttpServer;
  40. using log4net;
  41. namespace OpenSim.Framework.Console
  42. {
  43. // A console that uses REST interfaces
  44. //
  45. public class RemoteConsole : CommandConsole
  46. {
  47. // Connection specific data, indexed by a session ID
  48. // we create when a client connects.
  49. protected class ConsoleConnection
  50. {
  51. // Last activity from the client
  52. public int last;
  53. // Last line of scrollback posted to this client
  54. public long lastLineSeen;
  55. // True if this is a new connection, e.g. has never
  56. // displayed a prompt to the user.
  57. public bool newConnection = true;
  58. }
  59. // A line in the scrollback buffer.
  60. protected class ScrollbackEntry
  61. {
  62. // The line number of this entry
  63. public long lineNumber;
  64. // The text to send to the client
  65. public string text;
  66. // The level this should be logged as. Omitted for
  67. // prompts and input echo.
  68. public string level;
  69. // True if the text above is a prompt, e.g. the
  70. // client should turn on the cursor / accept input
  71. public bool isPrompt;
  72. // True if the requested input is a command. A
  73. // client may offer help or validate input if
  74. // this is set. If false, input should be sent
  75. // as typed.
  76. public bool isCommand;
  77. // True if this text represents a line of text that
  78. // was input in response to a prompt. A client should
  79. // turn off the cursor and refrain from sending commands
  80. // until a new prompt is received.
  81. public bool isInput;
  82. }
  83. // Data that is relevant to all connections
  84. // The scrollback buffer
  85. protected List<ScrollbackEntry> m_Scrollback = new List<ScrollbackEntry>();
  86. // Monotonously incrementing line number. This may eventually
  87. // wrap. No provision is made for that case because 64 bits
  88. // is a long, long time.
  89. protected long m_lineNumber = 0;
  90. // These two variables allow us to send the correct
  91. // information about the prompt status to the client,
  92. // irrespective of what may have run off the top of the
  93. // scrollback buffer;
  94. protected bool m_expectingInput = false;
  95. protected bool m_expectingCommand = true;
  96. protected string m_lastPromptUsed;
  97. // This is the list of things received from clients.
  98. // Note: Race conditions can happen. If a client sends
  99. // something while nothing is expected, it will be
  100. // intepreted as input to the next prompt. For
  101. // commands this is largely correct. For other prompts,
  102. // YMMV.
  103. // TODO: Find a better way to fix this
  104. protected List<string> m_InputData = new List<string>();
  105. // Event to allow ReadLine to wait synchronously even though
  106. // everthing else is asynchronous here.
  107. protected ManualResetEvent m_DataEvent = new ManualResetEvent(false);
  108. // The list of sessions we maintain. Unlike other console types,
  109. // multiple users on the same console are explicitly allowed.
  110. protected Dictionary<UUID, ConsoleConnection> m_Connections =
  111. new Dictionary<UUID, ConsoleConnection>();
  112. // Timer to control expiration of sessions that have been
  113. // disconnected.
  114. protected System.Timers.Timer m_expireTimer = new System.Timers.Timer(5000);
  115. // The less interesting stuff that makes the actual server
  116. // work.
  117. protected IHttpServer m_Server = null;
  118. protected IConfigSource m_Config = null;
  119. protected string m_UserName = String.Empty;
  120. protected string m_Password = String.Empty;
  121. protected string m_AllowedOrigin = String.Empty;
  122. public RemoteConsole(string defaultPrompt) : base(defaultPrompt)
  123. {
  124. // There is something wrong with this architecture.
  125. // A prompt is sent on every single input, so why have this?
  126. // TODO: Investigate and fix.
  127. m_lastPromptUsed = defaultPrompt;
  128. // Start expiration of sesssions.
  129. m_expireTimer.Elapsed += DoExpire;
  130. m_expireTimer.Start();
  131. }
  132. public void ReadConfig(IConfigSource config)
  133. {
  134. m_Config = config;
  135. // We're pulling this from the 'Network' section for legacy
  136. // compatibility. However, this is so essentially insecure
  137. // that TLS and client certs should be used instead of
  138. // a username / password.
  139. IConfig netConfig = m_Config.Configs["Network"];
  140. if (netConfig == null)
  141. return;
  142. // Get the username and password.
  143. m_UserName = netConfig.GetString("ConsoleUser", String.Empty);
  144. m_Password = netConfig.GetString("ConsolePass", String.Empty);
  145. // Woefully underdocumented, this is what makes javascript
  146. // console clients work. Set to "*" for anywhere or (better)
  147. // to specific addresses.
  148. m_AllowedOrigin = netConfig.GetString("ConsoleAllowedOrigin", String.Empty);
  149. }
  150. public void SetServer(IHttpServer server)
  151. {
  152. // This is called by the framework to give us the server
  153. // instance (means: port) to work with.
  154. m_Server = server;
  155. // Add our handlers
  156. m_Server.AddHTTPHandler("/StartSession/", HandleHttpStartSession);
  157. m_Server.AddHTTPHandler("/CloseSession/", HandleHttpCloseSession);
  158. m_Server.AddHTTPHandler("/SessionCommand/", HandleHttpSessionCommand);
  159. }
  160. public override void Output(string text, string level)
  161. {
  162. Output(text, level, false, false, false);
  163. }
  164. protected void Output(string text, string level, bool isPrompt, bool isCommand, bool isInput)
  165. {
  166. // Increment the line number. It was 0 and they start at 1
  167. // so we need to pre-increment.
  168. m_lineNumber++;
  169. // Create and populate the new entry.
  170. ScrollbackEntry newEntry = new ScrollbackEntry();
  171. newEntry.lineNumber = m_lineNumber;
  172. newEntry.text = text;
  173. newEntry.level = level;
  174. newEntry.isPrompt = isPrompt;
  175. newEntry.isCommand = isCommand;
  176. newEntry.isInput = isInput;
  177. // Add a line to the scrollback. In some cases, that may not
  178. // actually be a line of text.
  179. lock (m_Scrollback)
  180. {
  181. // Prune the scrollback to the length se send as connect
  182. // burst to give the user some context.
  183. while (m_Scrollback.Count >= 1000)
  184. m_Scrollback.RemoveAt(0);
  185. m_Scrollback.Add(newEntry);
  186. }
  187. // Let the rest of the system know we have output something.
  188. FireOnOutput(text.Trim());
  189. // Also display it for debugging.
  190. System.Console.WriteLine(text.Trim());
  191. }
  192. public override void Output(string text)
  193. {
  194. // Output plain (non-logging style) text.
  195. Output(text, String.Empty, false, false, false);
  196. }
  197. public override string ReadLine(string p, bool isCommand, bool e)
  198. {
  199. // Output the prompt an prepare to wait. This
  200. // is called on a dedicated console thread and
  201. // needs to be synchronous. Old architecture but
  202. // not worth upgrading.
  203. if (isCommand)
  204. {
  205. m_expectingInput = true;
  206. m_expectingCommand = true;
  207. Output(p, String.Empty, true, true, false);
  208. m_lastPromptUsed = p;
  209. }
  210. else
  211. {
  212. m_expectingInput = true;
  213. Output(p, String.Empty, true, false, false);
  214. }
  215. // Here is where we wait for the user to input something.
  216. m_DataEvent.WaitOne();
  217. string cmdinput;
  218. // Check for empty input. Read input if not empty.
  219. lock (m_InputData)
  220. {
  221. if (m_InputData.Count == 0)
  222. {
  223. m_DataEvent.Reset();
  224. m_expectingInput = false;
  225. m_expectingCommand = false;
  226. return "";
  227. }
  228. cmdinput = m_InputData[0];
  229. m_InputData.RemoveAt(0);
  230. if (m_InputData.Count == 0)
  231. m_DataEvent.Reset();
  232. }
  233. m_expectingInput = false;
  234. m_expectingCommand = false;
  235. // Echo to all the other users what we have done. This
  236. // will also go to ourselves.
  237. Output (cmdinput, String.Empty, false, false, true);
  238. // If this is a command, we need to resolve and execute it.
  239. if (isCommand)
  240. {
  241. // This call will actually execute the command and create
  242. // any output associated with it. The core just gets an
  243. // empty string so it will call again immediately.
  244. string[] cmd = Commands.Resolve(Parser.Parse(cmdinput));
  245. if (cmd.Length != 0)
  246. {
  247. int i;
  248. for (i=0 ; i < cmd.Length ; i++)
  249. {
  250. if (cmd[i].Contains(" "))
  251. cmd[i] = "\"" + cmd[i] + "\"";
  252. }
  253. return String.Empty;
  254. }
  255. }
  256. // Return the raw input string if not a command.
  257. return cmdinput;
  258. }
  259. // Very simplistic static access control header.
  260. protected Hashtable CheckOrigin(Hashtable result)
  261. {
  262. if (!string.IsNullOrEmpty(m_AllowedOrigin))
  263. result["access_control_allow_origin"] = m_AllowedOrigin;
  264. return result;
  265. }
  266. /* TODO: Figure out how PollServiceHTTPHandler can access the request headers
  267. * in order to use m_AllowedOrigin as a regular expression
  268. protected Hashtable CheckOrigin(Hashtable headers, Hashtable result)
  269. {
  270. if (!string.IsNullOrEmpty(m_AllowedOrigin))
  271. {
  272. if (headers.ContainsKey("origin"))
  273. {
  274. string origin = headers["origin"].ToString();
  275. if (Regex.IsMatch(origin, m_AllowedOrigin))
  276. result["access_control_allow_origin"] = origin;
  277. }
  278. }
  279. return result;
  280. }
  281. */
  282. protected void DoExpire(Object sender, ElapsedEventArgs e)
  283. {
  284. // Iterate the list of console connections and find those we
  285. // haven't heard from for longer then the longpoll interval.
  286. // Remove them.
  287. List<UUID> expired = new List<UUID>();
  288. lock (m_Connections)
  289. {
  290. // Mark the expired ones
  291. foreach (KeyValuePair<UUID, ConsoleConnection> kvp in m_Connections)
  292. {
  293. if (System.Environment.TickCount - kvp.Value.last > 500000)
  294. expired.Add(kvp.Key);
  295. }
  296. // Delete them
  297. foreach (UUID id in expired)
  298. {
  299. m_Connections.Remove(id);
  300. CloseConnection(id);
  301. }
  302. }
  303. }
  304. // Start a new session.
  305. protected Hashtable HandleHttpStartSession(Hashtable request)
  306. {
  307. // The login is in the form of a http form post
  308. Hashtable post = DecodePostString(request["body"].ToString());
  309. Hashtable reply = new Hashtable();
  310. reply["str_response_string"] = "";
  311. reply["int_response_code"] = 401;
  312. reply["content_type"] = "text/plain";
  313. // Check user name and password
  314. if (m_UserName == String.Empty)
  315. return reply;
  316. if (post["USER"] == null || post["PASS"] == null)
  317. return reply;
  318. if (m_UserName != post["USER"].ToString() ||
  319. m_Password != post["PASS"].ToString())
  320. {
  321. return reply;
  322. }
  323. // Set up the new console connection record
  324. ConsoleConnection c = new ConsoleConnection();
  325. c.last = System.Environment.TickCount;
  326. c.lastLineSeen = 0;
  327. // Assign session ID
  328. UUID sessionID = UUID.Random();
  329. // Add connection to list.
  330. lock (m_Connections)
  331. {
  332. m_Connections[sessionID] = c;
  333. }
  334. // This call is a CAP. The URL is the authentication.
  335. string uri = "/ReadResponses/" + sessionID.ToString() + "/";
  336. m_Server.AddPollServiceHTTPHandler(
  337. uri, new PollServiceEventArgs(null, uri, HasEvents, GetEvents, NoEvents, null, sessionID,25000)); // 25 secs timeout
  338. // Our reply is an XML document.
  339. // TODO: Change this to Linq.Xml
  340. XmlDocument xmldoc = new XmlDocument();
  341. XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
  342. "", "");
  343. xmldoc.AppendChild(xmlnode);
  344. XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
  345. "");
  346. xmldoc.AppendChild(rootElement);
  347. XmlElement id = xmldoc.CreateElement("", "SessionID", "");
  348. id.AppendChild(xmldoc.CreateTextNode(sessionID.ToString()));
  349. rootElement.AppendChild(id);
  350. XmlElement prompt = xmldoc.CreateElement("", "Prompt", "");
  351. prompt.AppendChild(xmldoc.CreateTextNode(m_lastPromptUsed));
  352. rootElement.AppendChild(prompt);
  353. rootElement.AppendChild(MainConsole.Instance.Commands.GetXml(xmldoc));
  354. // Set up the response and check origin
  355. reply["str_response_string"] = xmldoc.InnerXml;
  356. reply["int_response_code"] = 200;
  357. reply["content_type"] = "text/xml";
  358. reply = CheckOrigin(reply);
  359. return reply;
  360. }
  361. // Client closes session. Clean up.
  362. protected Hashtable HandleHttpCloseSession(Hashtable request)
  363. {
  364. Hashtable post = DecodePostString(request["body"].ToString());
  365. Hashtable reply = new Hashtable();
  366. reply["str_response_string"] = "";
  367. reply["int_response_code"] = 404;
  368. reply["content_type"] = "text/plain";
  369. if (post["ID"] == null)
  370. return reply;
  371. UUID id;
  372. if (!UUID.TryParse(post["ID"].ToString(), out id))
  373. return reply;
  374. lock (m_Connections)
  375. {
  376. if (m_Connections.ContainsKey(id))
  377. {
  378. m_Connections.Remove(id);
  379. CloseConnection(id);
  380. }
  381. }
  382. XmlDocument xmldoc = new XmlDocument();
  383. XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
  384. "", "");
  385. xmldoc.AppendChild(xmlnode);
  386. XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
  387. "");
  388. xmldoc.AppendChild(rootElement);
  389. XmlElement res = xmldoc.CreateElement("", "Result", "");
  390. res.AppendChild(xmldoc.CreateTextNode("OK"));
  391. rootElement.AppendChild(res);
  392. reply["str_response_string"] = xmldoc.InnerXml;
  393. reply["int_response_code"] = 200;
  394. reply["content_type"] = "text/xml";
  395. reply = CheckOrigin(reply);
  396. return reply;
  397. }
  398. // Command received from the client.
  399. protected Hashtable HandleHttpSessionCommand(Hashtable request)
  400. {
  401. Hashtable post = DecodePostString(request["body"].ToString());
  402. Hashtable reply = new Hashtable();
  403. reply["str_response_string"] = "";
  404. reply["int_response_code"] = 404;
  405. reply["content_type"] = "text/plain";
  406. // Check the ID
  407. if (post["ID"] == null)
  408. return reply;
  409. UUID id;
  410. if (!UUID.TryParse(post["ID"].ToString(), out id))
  411. return reply;
  412. // Find the connection for that ID.
  413. lock (m_Connections)
  414. {
  415. if (!m_Connections.ContainsKey(id))
  416. return reply;
  417. }
  418. // Empty post. Just error out.
  419. if (post["COMMAND"] == null)
  420. return reply;
  421. // Place the input data in the buffer.
  422. lock (m_InputData)
  423. {
  424. m_DataEvent.Set();
  425. m_InputData.Add(post["COMMAND"].ToString());
  426. }
  427. // Create the XML reply document.
  428. XmlDocument xmldoc = new XmlDocument();
  429. XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
  430. "", "");
  431. xmldoc.AppendChild(xmlnode);
  432. XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
  433. "");
  434. xmldoc.AppendChild(rootElement);
  435. XmlElement res = xmldoc.CreateElement("", "Result", "");
  436. res.AppendChild(xmldoc.CreateTextNode("OK"));
  437. rootElement.AppendChild(res);
  438. reply["str_response_string"] = xmldoc.InnerXml;
  439. reply["int_response_code"] = 200;
  440. reply["content_type"] = "text/xml";
  441. reply = CheckOrigin(reply);
  442. return reply;
  443. }
  444. // Decode a HTTP form post to a Hashtable
  445. protected Hashtable DecodePostString(string data)
  446. {
  447. Hashtable result = new Hashtable();
  448. string[] terms = data.Split(new char[] {'&'});
  449. foreach (string term in terms)
  450. {
  451. string[] elems = term.Split(new char[] {'='});
  452. if (elems.Length == 0)
  453. continue;
  454. string name = System.Web.HttpUtility.UrlDecode(elems[0]);
  455. string value = String.Empty;
  456. if (elems.Length > 1)
  457. value = System.Web.HttpUtility.UrlDecode(elems[1]);
  458. result[name] = value;
  459. }
  460. return result;
  461. }
  462. // Close the CAP receiver for the responses for a given client.
  463. public void CloseConnection(UUID id)
  464. {
  465. try
  466. {
  467. string uri = "/ReadResponses/" + id.ToString() + "/";
  468. m_Server.RemovePollServiceHTTPHandler("", uri);
  469. }
  470. catch (Exception)
  471. {
  472. }
  473. }
  474. // Check if there is anything to send. Return true if this client has
  475. // lines pending.
  476. protected bool HasEvents(UUID RequestID, UUID sessionID)
  477. {
  478. ConsoleConnection c = null;
  479. lock (m_Connections)
  480. {
  481. if (!m_Connections.ContainsKey(sessionID))
  482. return false;
  483. c = m_Connections[sessionID];
  484. }
  485. c.last = System.Environment.TickCount;
  486. if (c.lastLineSeen < m_lineNumber)
  487. return true;
  488. return false;
  489. }
  490. // Send all pending output to the client.
  491. protected Hashtable GetEvents(UUID RequestID, UUID sessionID)
  492. {
  493. // Find the connection that goes with this client.
  494. ConsoleConnection c = null;
  495. lock (m_Connections)
  496. {
  497. if (!m_Connections.ContainsKey(sessionID))
  498. return NoEvents(RequestID, UUID.Zero);
  499. c = m_Connections[sessionID];
  500. }
  501. // If we have nothing to send, send the no events response.
  502. c.last = System.Environment.TickCount;
  503. if (c.lastLineSeen >= m_lineNumber)
  504. return NoEvents(RequestID, UUID.Zero);
  505. Hashtable result = new Hashtable();
  506. // Create the response document.
  507. XmlDocument xmldoc = new XmlDocument();
  508. XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
  509. "", "");
  510. xmldoc.AppendChild(xmlnode);
  511. XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
  512. "");
  513. //if (c.newConnection)
  514. //{
  515. // c.newConnection = false;
  516. // Output("+++" + DefaultPrompt);
  517. //}
  518. lock (m_Scrollback)
  519. {
  520. long startLine = m_lineNumber - m_Scrollback.Count;
  521. long sendStart = startLine;
  522. if (sendStart < c.lastLineSeen)
  523. sendStart = c.lastLineSeen;
  524. for (long i = sendStart ; i < m_lineNumber ; i++)
  525. {
  526. ScrollbackEntry e = m_Scrollback[(int)(i - startLine)];
  527. XmlElement res = xmldoc.CreateElement("", "Line", "");
  528. res.SetAttribute("Number", e.lineNumber.ToString());
  529. res.SetAttribute("Level", e.level);
  530. // Don't include these for the scrollback, we'll send the
  531. // real state later.
  532. if (!c.newConnection)
  533. {
  534. res.SetAttribute("Prompt", e.isPrompt ? "true" : "false");
  535. res.SetAttribute("Command", e.isCommand ? "true" : "false");
  536. res.SetAttribute("Input", e.isInput ? "true" : "false");
  537. }
  538. else if (i == m_lineNumber - 1) // Last line for a new connection
  539. {
  540. res.SetAttribute("Prompt", m_expectingInput ? "true" : "false");
  541. res.SetAttribute("Command", m_expectingCommand ? "true" : "false");
  542. res.SetAttribute("Input", (!m_expectingInput) ? "true" : "false");
  543. }
  544. else
  545. {
  546. res.SetAttribute("Input", e.isInput ? "true" : "false");
  547. }
  548. res.AppendChild(xmldoc.CreateTextNode(e.text));
  549. rootElement.AppendChild(res);
  550. }
  551. }
  552. c.lastLineSeen = m_lineNumber;
  553. c.newConnection = false;
  554. xmldoc.AppendChild(rootElement);
  555. result["str_response_string"] = xmldoc.InnerXml;
  556. result["int_response_code"] = 200;
  557. result["content_type"] = "application/xml";
  558. result["keepalive"] = false;
  559. result = CheckOrigin(result);
  560. return result;
  561. }
  562. // This is really just a no-op. It generates what is sent
  563. // to the client if the poll times out without any events.
  564. protected Hashtable NoEvents(UUID RequestID, UUID id)
  565. {
  566. Hashtable result = new Hashtable();
  567. XmlDocument xmldoc = new XmlDocument();
  568. XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
  569. "", "");
  570. xmldoc.AppendChild(xmlnode);
  571. XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
  572. "");
  573. xmldoc.AppendChild(rootElement);
  574. result["str_response_string"] = xmldoc.InnerXml;
  575. result["int_response_code"] = 200;
  576. result["content_type"] = "text/xml";
  577. result["keepalive"] = false;
  578. result = CheckOrigin(result);
  579. return result;
  580. }
  581. }
  582. }