RemoteConsole.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744
  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, params object[] components)
  162. {
  163. string level = null;
  164. if (components != null && components.Length > 0)
  165. {
  166. if (components[0] == null || components[0] is ConsoleLevel)
  167. {
  168. if (components[0] is ConsoleLevel)
  169. level = ((ConsoleLevel)components[0]).ToString();
  170. if (components.Length > 1)
  171. {
  172. object[] tmp = new object[components.Length - 1];
  173. Array.Copy(components, 1, tmp, 0, components.Length - 1);
  174. components = tmp;
  175. }
  176. else
  177. components = null;
  178. }
  179. }
  180. string text;
  181. if (components == null || components.Length == 0)
  182. text = format;
  183. else
  184. text = 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 == String.Empty)
  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(
  357. uri, new PollServiceEventArgs(null, uri, HasEvents, GetEvents, NoEvents, null, sessionID,25000)); // 25 secs timeout
  358. // Our reply is an XML document.
  359. // TODO: Change this to Linq.Xml
  360. XmlDocument xmldoc = new XmlDocument();
  361. XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
  362. "", "");
  363. xmldoc.AppendChild(xmlnode);
  364. XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
  365. "");
  366. xmldoc.AppendChild(rootElement);
  367. XmlElement id = xmldoc.CreateElement("", "SessionID", "");
  368. id.AppendChild(xmldoc.CreateTextNode(sessionID.ToString()));
  369. rootElement.AppendChild(id);
  370. XmlElement prompt = xmldoc.CreateElement("", "Prompt", "");
  371. prompt.AppendChild(xmldoc.CreateTextNode(m_lastPromptUsed));
  372. rootElement.AppendChild(prompt);
  373. rootElement.AppendChild(MainConsole.Instance.Commands.GetXml(xmldoc));
  374. // Set up the response and check origin
  375. reply["str_response_string"] = xmldoc.InnerXml;
  376. reply["int_response_code"] = 200;
  377. reply["content_type"] = "text/xml";
  378. reply = CheckOrigin(reply);
  379. return reply;
  380. }
  381. // Client closes session. Clean up.
  382. protected Hashtable HandleHttpCloseSession(Hashtable request)
  383. {
  384. Hashtable post = DecodePostString(request["body"].ToString());
  385. Hashtable reply = new Hashtable();
  386. reply["str_response_string"] = "";
  387. reply["int_response_code"] = 404;
  388. reply["content_type"] = "text/plain";
  389. if (post["ID"] == null)
  390. return reply;
  391. UUID id;
  392. if (!UUID.TryParse(post["ID"].ToString(), out id))
  393. return reply;
  394. lock (m_Connections)
  395. {
  396. if (m_Connections.ContainsKey(id))
  397. {
  398. m_Connections.Remove(id);
  399. CloseConnection(id);
  400. }
  401. }
  402. XmlDocument xmldoc = new XmlDocument();
  403. XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
  404. "", "");
  405. xmldoc.AppendChild(xmlnode);
  406. XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
  407. "");
  408. xmldoc.AppendChild(rootElement);
  409. XmlElement res = xmldoc.CreateElement("", "Result", "");
  410. res.AppendChild(xmldoc.CreateTextNode("OK"));
  411. rootElement.AppendChild(res);
  412. reply["str_response_string"] = xmldoc.InnerXml;
  413. reply["int_response_code"] = 200;
  414. reply["content_type"] = "text/xml";
  415. reply = CheckOrigin(reply);
  416. return reply;
  417. }
  418. // Command received from the client.
  419. protected Hashtable HandleHttpSessionCommand(Hashtable request)
  420. {
  421. Hashtable post = DecodePostString(request["body"].ToString());
  422. Hashtable reply = new Hashtable();
  423. reply["str_response_string"] = "";
  424. reply["int_response_code"] = 404;
  425. reply["content_type"] = "text/plain";
  426. // Check the ID
  427. if (post["ID"] == null)
  428. return reply;
  429. UUID id;
  430. if (!UUID.TryParse(post["ID"].ToString(), out id))
  431. return reply;
  432. // Find the connection for that ID.
  433. lock (m_Connections)
  434. {
  435. if (!m_Connections.ContainsKey(id))
  436. return reply;
  437. }
  438. // Empty post. Just error out.
  439. if (post["COMMAND"] == null)
  440. return reply;
  441. // Place the input data in the buffer.
  442. lock (m_InputData)
  443. {
  444. m_DataEvent.Set();
  445. m_InputData.Add(post["COMMAND"].ToString());
  446. }
  447. // Create the XML reply document.
  448. XmlDocument xmldoc = new XmlDocument();
  449. XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
  450. "", "");
  451. xmldoc.AppendChild(xmlnode);
  452. XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
  453. "");
  454. xmldoc.AppendChild(rootElement);
  455. XmlElement res = xmldoc.CreateElement("", "Result", "");
  456. res.AppendChild(xmldoc.CreateTextNode("OK"));
  457. rootElement.AppendChild(res);
  458. reply["str_response_string"] = xmldoc.InnerXml;
  459. reply["int_response_code"] = 200;
  460. reply["content_type"] = "text/xml";
  461. reply = CheckOrigin(reply);
  462. return reply;
  463. }
  464. // Decode a HTTP form post to a Hashtable
  465. protected Hashtable DecodePostString(string data)
  466. {
  467. Hashtable result = new Hashtable();
  468. string[] terms = data.Split(new char[] {'&'});
  469. foreach (string term in terms)
  470. {
  471. string[] elems = term.Split(new char[] {'='});
  472. if (elems.Length == 0)
  473. continue;
  474. string name = System.Web.HttpUtility.UrlDecode(elems[0]);
  475. string value = String.Empty;
  476. if (elems.Length > 1)
  477. value = System.Web.HttpUtility.UrlDecode(elems[1]);
  478. result[name] = value;
  479. }
  480. return result;
  481. }
  482. // Close the CAP receiver for the responses for a given client.
  483. public void CloseConnection(UUID id)
  484. {
  485. try
  486. {
  487. string uri = "/ReadResponses/" + id.ToString() + "/";
  488. m_Server.RemovePollServiceHTTPHandler("", uri);
  489. }
  490. catch (Exception)
  491. {
  492. }
  493. }
  494. // Check if there is anything to send. Return true if this client has
  495. // lines pending.
  496. protected bool HasEvents(UUID RequestID, UUID sessionID)
  497. {
  498. ConsoleConnection c = null;
  499. lock (m_Connections)
  500. {
  501. if (!m_Connections.ContainsKey(sessionID))
  502. return false;
  503. c = m_Connections[sessionID];
  504. }
  505. c.last = System.Environment.TickCount;
  506. if (c.lastLineSeen < m_lineNumber)
  507. return true;
  508. return false;
  509. }
  510. // Send all pending output to the client.
  511. protected Hashtable GetEvents(UUID RequestID, UUID sessionID)
  512. {
  513. // Find the connection that goes with this client.
  514. ConsoleConnection c = null;
  515. lock (m_Connections)
  516. {
  517. if (!m_Connections.ContainsKey(sessionID))
  518. return NoEvents(RequestID, UUID.Zero);
  519. c = m_Connections[sessionID];
  520. }
  521. // If we have nothing to send, send the no events response.
  522. c.last = System.Environment.TickCount;
  523. if (c.lastLineSeen >= m_lineNumber)
  524. return NoEvents(RequestID, UUID.Zero);
  525. Hashtable result = new Hashtable();
  526. // Create the response document.
  527. XmlDocument xmldoc = new XmlDocument();
  528. XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
  529. "", "");
  530. xmldoc.AppendChild(xmlnode);
  531. XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
  532. "");
  533. //if (c.newConnection)
  534. //{
  535. // c.newConnection = false;
  536. // Output("+++" + DefaultPrompt);
  537. //}
  538. lock (m_Scrollback)
  539. {
  540. long startLine = m_lineNumber - m_Scrollback.Count;
  541. long sendStart = startLine;
  542. if (sendStart < c.lastLineSeen)
  543. sendStart = c.lastLineSeen;
  544. for (long i = sendStart ; i < m_lineNumber ; i++)
  545. {
  546. ScrollbackEntry e = m_Scrollback[(int)(i - startLine)];
  547. XmlElement res = xmldoc.CreateElement("", "Line", "");
  548. res.SetAttribute("Number", e.lineNumber.ToString());
  549. res.SetAttribute("Level", e.level);
  550. // Don't include these for the scrollback, we'll send the
  551. // real state later.
  552. if (!c.newConnection)
  553. {
  554. res.SetAttribute("Prompt", e.isPrompt ? "true" : "false");
  555. res.SetAttribute("Command", e.isCommand ? "true" : "false");
  556. res.SetAttribute("Input", e.isInput ? "true" : "false");
  557. }
  558. else if (i == m_lineNumber - 1) // Last line for a new connection
  559. {
  560. res.SetAttribute("Prompt", m_expectingInput ? "true" : "false");
  561. res.SetAttribute("Command", m_expectingCommand ? "true" : "false");
  562. res.SetAttribute("Input", (!m_expectingInput) ? "true" : "false");
  563. }
  564. else
  565. {
  566. res.SetAttribute("Input", e.isInput ? "true" : "false");
  567. }
  568. res.AppendChild(xmldoc.CreateTextNode(e.text));
  569. rootElement.AppendChild(res);
  570. }
  571. }
  572. c.lastLineSeen = m_lineNumber;
  573. c.newConnection = false;
  574. xmldoc.AppendChild(rootElement);
  575. result["str_response_string"] = xmldoc.InnerXml;
  576. result["int_response_code"] = 200;
  577. result["content_type"] = "application/xml";
  578. result["keepalive"] = false;
  579. result = CheckOrigin(result);
  580. return result;
  581. }
  582. // This is really just a no-op. It generates what is sent
  583. // to the client if the poll times out without any events.
  584. protected Hashtable NoEvents(UUID RequestID, UUID id)
  585. {
  586. Hashtable result = new Hashtable();
  587. XmlDocument xmldoc = new XmlDocument();
  588. XmlNode xmlnode = xmldoc.CreateNode(XmlNodeType.XmlDeclaration,
  589. "", "");
  590. xmldoc.AppendChild(xmlnode);
  591. XmlElement rootElement = xmldoc.CreateElement("", "ConsoleSession",
  592. "");
  593. xmldoc.AppendChild(rootElement);
  594. result["str_response_string"] = xmldoc.InnerXml;
  595. result["int_response_code"] = 200;
  596. result["content_type"] = "text/xml";
  597. result["keepalive"] = false;
  598. result = CheckOrigin(result);
  599. return result;
  600. }
  601. }
  602. }