1
0

RemoteConsole.cs 26 KB

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