RemoteConsole.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748
  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. if (components[0] == null || components[0] is ConsoleLevel)
  171. {
  172. if (components[0] is ConsoleLevel)
  173. level = ((ConsoleLevel)components[0]).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;
  185. if (components == null || components.Length == 0)
  186. text = format;
  187. else
  188. text = String.Format(format, components);
  189. Output(text, level, false, false, false);
  190. }
  191. protected void Output(string text, string level, bool isPrompt, bool isCommand, bool isInput)
  192. {
  193. if (level == null)
  194. level = String.Empty;
  195. // Increment the line number. It was 0 and they start at 1
  196. // so we need to pre-increment.
  197. m_lineNumber++;
  198. // Create and populate the new entry.
  199. ScrollbackEntry newEntry = new ScrollbackEntry();
  200. newEntry.lineNumber = m_lineNumber;
  201. newEntry.text = text;
  202. newEntry.level = level;
  203. newEntry.isPrompt = isPrompt;
  204. newEntry.isCommand = isCommand;
  205. newEntry.isInput = isInput;
  206. // Add a line to the scrollback. In some cases, that may not
  207. // actually be a line of text.
  208. lock (m_Scrollback)
  209. {
  210. // Prune the scrollback to the length se send as connect
  211. // burst to give the user some context.
  212. while (m_Scrollback.Count >= 1000)
  213. m_Scrollback.RemoveAt(0);
  214. m_Scrollback.Add(newEntry);
  215. }
  216. // Let the rest of the system know we have output something.
  217. FireOnOutput(text.Trim());
  218. // Also display it for debugging.
  219. System.Console.WriteLine(text.Trim());
  220. }
  221. public override string ReadLine(string p, bool isCommand, bool e)
  222. {
  223. // Output the prompt an prepare to wait. This
  224. // is called on a dedicated console thread and
  225. // needs to be synchronous. Old architecture but
  226. // not worth upgrading.
  227. if (isCommand)
  228. {
  229. m_expectingInput = true;
  230. m_expectingCommand = true;
  231. Output(p, String.Empty, true, true, false);
  232. m_lastPromptUsed = p;
  233. }
  234. else
  235. {
  236. m_expectingInput = true;
  237. Output(p, String.Empty, true, false, false);
  238. }
  239. // Here is where we wait for the user to input something.
  240. m_DataEvent.WaitOne();
  241. string cmdinput;
  242. // Check for empty input. Read input if not empty.
  243. lock (m_InputData)
  244. {
  245. if (m_InputData.Count == 0)
  246. {
  247. m_DataEvent.Reset();
  248. m_expectingInput = false;
  249. m_expectingCommand = false;
  250. return "";
  251. }
  252. cmdinput = m_InputData[0];
  253. m_InputData.RemoveAt(0);
  254. if (m_InputData.Count == 0)
  255. m_DataEvent.Reset();
  256. }
  257. m_expectingInput = false;
  258. m_expectingCommand = false;
  259. // Echo to all the other users what we have done. This
  260. // will also go to ourselves.
  261. Output (cmdinput, String.Empty, false, false, true);
  262. // If this is a command, we need to resolve and execute it.
  263. if (isCommand)
  264. {
  265. // This call will actually execute the command and create
  266. // any output associated with it. The core just gets an
  267. // empty string so it will call again immediately.
  268. string[] cmd = Commands.Resolve(Parser.Parse(cmdinput));
  269. if (cmd.Length != 0)
  270. {
  271. int i;
  272. for (i=0 ; i < cmd.Length ; i++)
  273. {
  274. if (cmd[i].Contains(" "))
  275. cmd[i] = "\"" + cmd[i] + "\"";
  276. }
  277. return String.Empty;
  278. }
  279. }
  280. // Return the raw input string if not a command.
  281. return cmdinput;
  282. }
  283. // Very simplistic static access control header.
  284. protected Hashtable CheckOrigin(Hashtable result)
  285. {
  286. if (!string.IsNullOrEmpty(m_AllowedOrigin))
  287. result["access_control_allow_origin"] = m_AllowedOrigin;
  288. return result;
  289. }
  290. /* TODO: Figure out how PollServiceHTTPHandler can access the request headers
  291. * in order to use m_AllowedOrigin as a regular expression
  292. protected Hashtable CheckOrigin(Hashtable headers, Hashtable result)
  293. {
  294. if (!string.IsNullOrEmpty(m_AllowedOrigin))
  295. {
  296. if (headers.ContainsKey("origin"))
  297. {
  298. string origin = headers["origin"].ToString();
  299. if (Regex.IsMatch(origin, m_AllowedOrigin))
  300. result["access_control_allow_origin"] = origin;
  301. }
  302. }
  303. return result;
  304. }
  305. */
  306. protected void DoExpire(Object sender, ElapsedEventArgs e)
  307. {
  308. // Iterate the list of console connections and find those we
  309. // haven't heard from for longer then the longpoll interval.
  310. // Remove them.
  311. List<UUID> expired = new List<UUID>();
  312. lock (m_Connections)
  313. {
  314. // Mark the expired ones
  315. foreach (KeyValuePair<UUID, ConsoleConnection> kvp in m_Connections)
  316. {
  317. if (System.Environment.TickCount - kvp.Value.last > 500000)
  318. expired.Add(kvp.Key);
  319. }
  320. // Delete them
  321. foreach (UUID id in expired)
  322. {
  323. m_Connections.Remove(id);
  324. CloseConnection(id);
  325. }
  326. }
  327. }
  328. // Start a new session.
  329. protected Hashtable HandleHttpStartSession(Hashtable request)
  330. {
  331. // The login is in the form of a http form post
  332. Hashtable post = DecodePostString(request["body"].ToString());
  333. Hashtable reply = new Hashtable();
  334. reply["str_response_string"] = "";
  335. reply["int_response_code"] = 401;
  336. reply["content_type"] = "text/plain";
  337. // Check user name and password
  338. if (m_UserName == String.Empty)
  339. return reply;
  340. if (post["USER"] == null || post["PASS"] == null)
  341. return reply;
  342. if (m_UserName != post["USER"].ToString() ||
  343. m_Password != post["PASS"].ToString())
  344. {
  345. return reply;
  346. }
  347. // Set up the new console connection record
  348. ConsoleConnection c = new ConsoleConnection();
  349. c.last = System.Environment.TickCount;
  350. c.lastLineSeen = 0;
  351. // Assign session ID
  352. UUID sessionID = UUID.Random();
  353. // Add connection to list.
  354. lock (m_Connections)
  355. {
  356. m_Connections[sessionID] = c;
  357. }
  358. // This call is a CAP. The URL is the authentication.
  359. string uri = "/ReadResponses/" + sessionID.ToString();
  360. m_Server.AddPollServiceHTTPHandler(new PollServiceEventArgs(null, uri, HasEvents, GetEvents, NoEvents, null, sessionID,25000)); // 25 secs timeout
  361. // Our reply is an XML document.
  362. // TODO: Change this to Linq.Xml
  363. XmlDocument xmldoc = new XmlDocument();
  364. XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
  365. "", "");
  366. xmldoc.AppendChild(xmlnode);
  367. XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
  368. "");
  369. xmldoc.AppendChild(rootElement);
  370. XmlElement id = xmldoc.CreateElement("", "SessionID", "");
  371. id.AppendChild(xmldoc.CreateTextNode(sessionID.ToString()));
  372. rootElement.AppendChild(id);
  373. XmlElement prompt = xmldoc.CreateElement("", "Prompt", "");
  374. prompt.AppendChild(xmldoc.CreateTextNode(m_lastPromptUsed));
  375. rootElement.AppendChild(prompt);
  376. rootElement.AppendChild(MainConsole.Instance.Commands.GetXml(xmldoc));
  377. // Set up the response and check origin
  378. reply["str_response_string"] = xmldoc.InnerXml;
  379. reply["int_response_code"] = 200;
  380. reply["content_type"] = "text/xml";
  381. reply = CheckOrigin(reply);
  382. return reply;
  383. }
  384. // Client closes session. Clean up.
  385. protected Hashtable HandleHttpCloseSession(Hashtable request)
  386. {
  387. Hashtable post = DecodePostString(request["body"].ToString());
  388. Hashtable reply = new Hashtable();
  389. reply["str_response_string"] = "";
  390. reply["int_response_code"] = 404;
  391. reply["content_type"] = "text/plain";
  392. if (post["ID"] == null)
  393. return reply;
  394. UUID id;
  395. if (!UUID.TryParse(post["ID"].ToString(), out id))
  396. return reply;
  397. lock (m_Connections)
  398. {
  399. if (m_Connections.ContainsKey(id))
  400. {
  401. m_Connections.Remove(id);
  402. CloseConnection(id);
  403. }
  404. }
  405. XmlDocument xmldoc = new XmlDocument();
  406. XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
  407. "", "");
  408. xmldoc.AppendChild(xmlnode);
  409. XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
  410. "");
  411. xmldoc.AppendChild(rootElement);
  412. XmlElement res = xmldoc.CreateElement("", "Result", "");
  413. res.AppendChild(xmldoc.CreateTextNode("OK"));
  414. rootElement.AppendChild(res);
  415. reply["str_response_string"] = xmldoc.InnerXml;
  416. reply["int_response_code"] = 200;
  417. reply["content_type"] = "text/xml";
  418. reply = CheckOrigin(reply);
  419. return reply;
  420. }
  421. // Command received from the client.
  422. protected Hashtable HandleHttpSessionCommand(Hashtable request)
  423. {
  424. Hashtable post = DecodePostString(request["body"].ToString());
  425. Hashtable reply = new Hashtable();
  426. reply["str_response_string"] = "";
  427. reply["int_response_code"] = 404;
  428. reply["content_type"] = "text/plain";
  429. // Check the ID
  430. if (post["ID"] == null)
  431. return reply;
  432. UUID id;
  433. if (!UUID.TryParse(post["ID"].ToString(), out id))
  434. return reply;
  435. // Find the connection for that ID.
  436. lock (m_Connections)
  437. {
  438. if (!m_Connections.ContainsKey(id))
  439. return reply;
  440. }
  441. // Empty post. Just error out.
  442. if (post["COMMAND"] == null)
  443. return reply;
  444. // Place the input data in the buffer.
  445. lock (m_InputData)
  446. {
  447. m_DataEvent.Set();
  448. m_InputData.Add(post["COMMAND"].ToString());
  449. }
  450. // Create the XML reply document.
  451. XmlDocument xmldoc = new XmlDocument();
  452. XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
  453. "", "");
  454. xmldoc.AppendChild(xmlnode);
  455. XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
  456. "");
  457. xmldoc.AppendChild(rootElement);
  458. XmlElement res = xmldoc.CreateElement("", "Result", "");
  459. res.AppendChild(xmldoc.CreateTextNode("OK"));
  460. rootElement.AppendChild(res);
  461. reply["str_response_string"] = xmldoc.InnerXml;
  462. reply["int_response_code"] = 200;
  463. reply["content_type"] = "text/xml";
  464. reply = CheckOrigin(reply);
  465. return reply;
  466. }
  467. // Decode a HTTP form post to a Hashtable
  468. protected Hashtable DecodePostString(string data)
  469. {
  470. Hashtable result = new Hashtable();
  471. string[] terms = data.Split(new char[] {'&'});
  472. foreach (string term in terms)
  473. {
  474. string[] elems = term.Split(new char[] {'='});
  475. if (elems.Length == 0)
  476. continue;
  477. string name = System.Web.HttpUtility.UrlDecode(elems[0]);
  478. string value = String.Empty;
  479. if (elems.Length > 1)
  480. value = System.Web.HttpUtility.UrlDecode(elems[1]);
  481. result[name] = value;
  482. }
  483. return result;
  484. }
  485. // Close the CAP receiver for the responses for a given client.
  486. public void CloseConnection(UUID id)
  487. {
  488. try
  489. {
  490. string uri = "/ReadResponses/" + id.ToString() + "/";
  491. m_Server.RemovePollServiceHTTPHandler("", uri);
  492. }
  493. catch (Exception)
  494. {
  495. }
  496. }
  497. // Check if there is anything to send. Return true if this client has
  498. // lines pending.
  499. protected bool HasEvents(UUID RequestID, UUID sessionID)
  500. {
  501. ConsoleConnection c = null;
  502. lock (m_Connections)
  503. {
  504. if (!m_Connections.ContainsKey(sessionID))
  505. return false;
  506. c = m_Connections[sessionID];
  507. }
  508. c.last = System.Environment.TickCount;
  509. if (c.lastLineSeen < m_lineNumber)
  510. return true;
  511. return false;
  512. }
  513. // Send all pending output to the client.
  514. protected Hashtable GetEvents(UUID RequestID, UUID sessionID)
  515. {
  516. // Find the connection that goes with this client.
  517. ConsoleConnection c = null;
  518. lock (m_Connections)
  519. {
  520. if (!m_Connections.ContainsKey(sessionID))
  521. return NoEvents(RequestID, UUID.Zero);
  522. c = m_Connections[sessionID];
  523. }
  524. // If we have nothing to send, send the no events response.
  525. c.last = System.Environment.TickCount;
  526. if (c.lastLineSeen >= m_lineNumber)
  527. return NoEvents(RequestID, UUID.Zero);
  528. Hashtable result = new Hashtable();
  529. // Create the response document.
  530. XmlDocument xmldoc = new XmlDocument();
  531. XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
  532. "", "");
  533. xmldoc.AppendChild(xmlnode);
  534. XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
  535. "");
  536. //if (c.newConnection)
  537. //{
  538. // c.newConnection = false;
  539. // Output("+++" + DefaultPrompt);
  540. //}
  541. lock (m_Scrollback)
  542. {
  543. long startLine = m_lineNumber - m_Scrollback.Count;
  544. long sendStart = startLine;
  545. if (sendStart < c.lastLineSeen)
  546. sendStart = c.lastLineSeen;
  547. for (long i = sendStart ; i < m_lineNumber ; i++)
  548. {
  549. ScrollbackEntry e = m_Scrollback[(int)(i - startLine)];
  550. XmlElement res = xmldoc.CreateElement("", "Line", "");
  551. res.SetAttribute("Number", e.lineNumber.ToString());
  552. res.SetAttribute("Level", e.level);
  553. // Don't include these for the scrollback, we'll send the
  554. // real state later.
  555. if (!c.newConnection)
  556. {
  557. res.SetAttribute("Prompt", e.isPrompt ? "true" : "false");
  558. res.SetAttribute("Command", e.isCommand ? "true" : "false");
  559. res.SetAttribute("Input", e.isInput ? "true" : "false");
  560. }
  561. else if (i == m_lineNumber - 1) // Last line for a new connection
  562. {
  563. res.SetAttribute("Prompt", m_expectingInput ? "true" : "false");
  564. res.SetAttribute("Command", m_expectingCommand ? "true" : "false");
  565. res.SetAttribute("Input", (!m_expectingInput) ? "true" : "false");
  566. }
  567. else
  568. {
  569. res.SetAttribute("Input", e.isInput ? "true" : "false");
  570. }
  571. res.AppendChild(xmldoc.CreateTextNode(e.text));
  572. rootElement.AppendChild(res);
  573. }
  574. }
  575. c.lastLineSeen = m_lineNumber;
  576. c.newConnection = false;
  577. xmldoc.AppendChild(rootElement);
  578. result["str_response_string"] = xmldoc.InnerXml;
  579. result["int_response_code"] = 200;
  580. result["content_type"] = "application/xml";
  581. result["keepalive"] = false;
  582. result = CheckOrigin(result);
  583. return result;
  584. }
  585. // This is really just a no-op. It generates what is sent
  586. // to the client if the poll times out without any events.
  587. protected Hashtable NoEvents(UUID RequestID, UUID id)
  588. {
  589. Hashtable result = new Hashtable();
  590. XmlDocument xmldoc = new XmlDocument();
  591. XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
  592. "", "");
  593. xmldoc.AppendChild(xmlnode);
  594. XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
  595. "");
  596. xmldoc.AppendChild(rootElement);
  597. result["str_response_string"] = xmldoc.InnerXml;
  598. result["int_response_code"] = 200;
  599. result["content_type"] = "text/xml";
  600. result["keepalive"] = false;
  601. result = CheckOrigin(result);
  602. return result;
  603. }
  604. }
  605. }