PageRenderTime 80ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 0ms

/extensions/irc/js/lib/irc.js

https://bitbucket.org/mkato/mozilla-1.9.0-win64
JavaScript | 3790 lines | 2883 code | 550 blank | 357 comment | 516 complexity | 71aa5380f81e7bee862db20b8e42021b MD5 | raw file
Possible License(s): LGPL-3.0, MIT, BSD-3-Clause, MPL-2.0-no-copyleft-exception, GPL-2.0, LGPL-2.1
  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  2. *
  3. * ***** BEGIN LICENSE BLOCK *****
  4. * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  5. *
  6. * The contents of this file are subject to the Mozilla Public License Version
  7. * 1.1 (the "License"); you may not use this file except in compliance with
  8. * the License. You may obtain a copy of the License at
  9. * http://www.mozilla.org/MPL/
  10. *
  11. * Software distributed under the License is distributed on an "AS IS" basis,
  12. * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  13. * for the specific language governing rights and limitations under the
  14. * License.
  15. *
  16. * The Original Code is JSIRC Library.
  17. *
  18. * The Initial Developer of the Original Code is
  19. * New Dimensions Consulting, Inc.
  20. * Portions created by the Initial Developer are Copyright (C) 1999
  21. * the Initial Developer. All Rights Reserved.
  22. *
  23. * Contributor(s):
  24. * Robert Ginda, rginda@ndcico.com, original author
  25. *
  26. * Alternatively, the contents of this file may be used under the terms of
  27. * either the GNU General Public License Version 2 or later (the "GPL"), or
  28. * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  29. * in which case the provisions of the GPL or the LGPL are applicable instead
  30. * of those above. If you wish to allow use of your version of this file only
  31. * under the terms of either the GPL or the LGPL, and not to allow others to
  32. * use your version of this file under the terms of the MPL, indicate your
  33. * decision by deleting the provisions above and replace them with the notice
  34. * and other provisions required by the GPL or the LGPL. If you do not delete
  35. * the provisions above, a recipient may use your version of this file under
  36. * the terms of any one of the MPL, the GPL or the LGPL.
  37. *
  38. * ***** END LICENSE BLOCK ***** */
  39. const JSIRC_ERR_NO_SOCKET = "JSIRCE:NS";
  40. const JSIRC_ERR_EXHAUSTED = "JSIRCE:E";
  41. const JSIRC_ERR_CANCELLED = "JSIRCE:C";
  42. const JSIRC_ERR_NO_SECURE = "JSIRCE:NO_SECURE";
  43. const JSIRC_ERR_OFFLINE = "JSIRCE:OFFLINE";
  44. const JSIRC_ERR_PAC_LOADING = "JSIRCE:PAC_LOADING";
  45. function userIsMe (user)
  46. {
  47. switch (user.TYPE)
  48. {
  49. case "IRCUser":
  50. return (user == user.parent.me);
  51. break;
  52. case "IRCChanUser":
  53. return (user.__proto__ == user.parent.parent.me);
  54. break;
  55. default:
  56. return false;
  57. }
  58. return false;
  59. }
  60. /*
  61. * Attached to event objects in onRawData
  62. */
  63. function decodeParam(number, charsetOrObject)
  64. {
  65. if (!charsetOrObject)
  66. charsetOrObject = this.currentObject;
  67. var rv = toUnicode(this.params[number], charsetOrObject);
  68. return rv;
  69. }
  70. // JavaScript won't let you delete things declared with "var", workaround:
  71. window.i = 1;
  72. const NET_OFFLINE = i++; // Initial, disconected.
  73. const NET_WAITING = i++; // Waiting before trying.
  74. const NET_CONNECTING = i++; // Trying a connect...
  75. const NET_CANCELLING = i++; // Cancelling connect.
  76. const NET_ONLINE = i++; // Connected ok.
  77. const NET_DISCONNECTING = i++; // Disconnecting.
  78. delete window.i;
  79. /*
  80. * irc network
  81. */
  82. function CIRCNetwork (name, serverList, eventPump, temporary)
  83. {
  84. this.unicodeName = name;
  85. this.viewName = name;
  86. this.canonicalName = name;
  87. this.encodedName = name;
  88. this.servers = new Object();
  89. this.serverList = new Array();
  90. this.ignoreList = new Object();
  91. this.ignoreMaskCache = new Object();
  92. this.state = NET_OFFLINE;
  93. this.temporary = Boolean(temporary);
  94. for (var i = 0; i < serverList.length; ++i)
  95. {
  96. var server = serverList[i];
  97. var password = ("password" in server) ? server.password : null;
  98. var isSecure = ("isSecure" in server) ? server.isSecure : false;
  99. this.serverList.push(new CIRCServer(this, server.name, server.port, isSecure,
  100. password));
  101. }
  102. this.eventPump = eventPump;
  103. if ("onInit" in this)
  104. this.onInit();
  105. }
  106. /** Clients should override this stuff themselves **/
  107. CIRCNetwork.prototype.INITIAL_NICK = "js-irc";
  108. CIRCNetwork.prototype.INITIAL_NAME = "INITIAL_NAME";
  109. CIRCNetwork.prototype.INITIAL_DESC = "INITIAL_DESC";
  110. /* set INITIAL_CHANNEL to "" if you don't want a primary channel */
  111. CIRCNetwork.prototype.INITIAL_CHANNEL = "#jsbot";
  112. CIRCNetwork.prototype.INITIAL_UMODE = "+iw";
  113. CIRCNetwork.prototype.MAX_CONNECT_ATTEMPTS = 5;
  114. CIRCNetwork.prototype.PAC_RECONNECT_DELAY = 5 * 1000;
  115. CIRCNetwork.prototype.getReconnectDelayMs = function() { return 15000; }
  116. CIRCNetwork.prototype.stayingPower = false;
  117. // "http" = use HTTP proxy, "none" = none, anything else = auto.
  118. CIRCNetwork.prototype.PROXY_TYPE_OVERRIDE = "";
  119. CIRCNetwork.prototype.TYPE = "IRCNetwork";
  120. CIRCNetwork.prototype.getURL =
  121. function net_geturl(target, flags)
  122. {
  123. if (this.temporary)
  124. return this.serverList[0].getURL(target, flags);
  125. /* Determine whether to use the irc:// or ircs:// scheme */
  126. var scheme = "irc";
  127. if ((("primServ" in this) && this.primServ.isConnected &&
  128. this.primServ.isSecure) ||
  129. this.hasOnlySecureServers())
  130. {
  131. scheme = "ircs"
  132. }
  133. var obj = {host: this.unicodeName, scheme: scheme};
  134. if (target)
  135. obj.target = target;
  136. if (flags)
  137. {
  138. for (var i = 0; i < flags.length; i++)
  139. obj[flags[i]] = true;
  140. }
  141. return constructIRCURL(obj);
  142. }
  143. CIRCNetwork.prototype.getUser =
  144. function net_getuser (nick)
  145. {
  146. if ("primServ" in this && this.primServ)
  147. return this.primServ.getUser(nick);
  148. return null;
  149. }
  150. CIRCNetwork.prototype.addServer =
  151. function net_addsrv(host, port, isSecure, password)
  152. {
  153. this.serverList.push(new CIRCServer(this, host, port, isSecure, password));
  154. }
  155. // Checks if a network has a secure server in its list.
  156. CIRCNetwork.prototype.hasSecureServer =
  157. function net_hasSecure()
  158. {
  159. for (var i = 0; i < this.serverList.length; i++)
  160. {
  161. if (this.serverList[i].isSecure)
  162. return true;
  163. }
  164. return false;
  165. }
  166. CIRCNetwork.prototype.hasOnlySecureServers =
  167. function net_hasOnlySecure()
  168. {
  169. for (var i = 0; i < this.serverList.length; i++)
  170. {
  171. if (!this.serverList[i].isSecure)
  172. return false;
  173. }
  174. return true;
  175. }
  176. CIRCNetwork.prototype.clearServerList =
  177. function net_clearserverlist()
  178. {
  179. /* Note: we don't have to worry about being connected, since primServ
  180. * keeps the currently connected server alive if we still need it.
  181. */
  182. this.servers = new Object();
  183. this.serverList = new Array();
  184. }
  185. /** Trigger an onDoConnect event after a delay. */
  186. CIRCNetwork.prototype.delayedConnect =
  187. function net_delayedConnect(eventProperties)
  188. {
  189. function reconnectFn(network, eventProperties)
  190. {
  191. network.immediateConnect(eventProperties);
  192. };
  193. if ((-1 != this.MAX_CONNECT_ATTEMPTS) &&
  194. (this.connectAttempt >= this.MAX_CONNECT_ATTEMPTS))
  195. {
  196. this.state = NET_OFFLINE;
  197. var ev = new CEvent("network", "error", this, "onError");
  198. ev.debug = "Connection attempts exhausted, giving up.";
  199. ev.errorCode = JSIRC_ERR_EXHAUSTED;
  200. this.eventPump.addEvent(ev);
  201. return;
  202. }
  203. this.state = NET_WAITING;
  204. this.reconnectTimer = setTimeout(reconnectFn,
  205. this.getReconnectDelayMs(),
  206. this,
  207. eventProperties);
  208. }
  209. /**
  210. * Immediately trigger an onDoConnect event. Use delayedConnect for automatic
  211. * repeat attempts, instead, to throttle the attempts to a reasonable pace.
  212. */
  213. CIRCNetwork.prototype.immediateConnect =
  214. function net_immediateConnect(eventProperties)
  215. {
  216. var ev = new CEvent("network", "do-connect", this, "onDoConnect");
  217. if (typeof eventProperties != "undefined")
  218. for (var key in eventProperties)
  219. ev[key] = eventProperties[key];
  220. this.eventPump.addEvent(ev);
  221. }
  222. CIRCNetwork.prototype.connect =
  223. function net_connect(requireSecurity)
  224. {
  225. if ("primServ" in this && this.primServ.isConnected)
  226. return true;
  227. // We need to test for secure servers in the network object here,
  228. // because without them all connection attempts will fail anyway.
  229. if (requireSecurity && !this.hasSecureServer())
  230. {
  231. // No secure server, cope.
  232. ev = new CEvent ("network", "error", this, "onError");
  233. ev.server = this;
  234. ev.debug = "No connection attempted: no secure servers in list";
  235. ev.errorCode = JSIRC_ERR_NO_SECURE;
  236. this.eventPump.addEvent(ev);
  237. return false;
  238. }
  239. this.state = NET_CONNECTING;
  240. this.connectAttempt = 0; // actual connection attempts
  241. this.connectCandidate = 0; // incl. requireSecurity non-attempts
  242. this.nextHost = 0;
  243. this.requireSecurity = requireSecurity || false;
  244. this.immediateConnect({"password": null});
  245. return true;
  246. }
  247. CIRCNetwork.prototype.quit =
  248. function net_quit (reason)
  249. {
  250. if (this.isConnected())
  251. this.primServ.logout(reason);
  252. }
  253. CIRCNetwork.prototype.cancel =
  254. function net_cancel()
  255. {
  256. // We're online, pull the plug on the current connection, or...
  257. if (this.state == NET_ONLINE)
  258. {
  259. this.quit();
  260. }
  261. // We're waiting for the 001, too late to throw a reconnect, or...
  262. else if (this.state == NET_CONNECTING)
  263. {
  264. this.state = NET_CANCELLING;
  265. this.primServ.connection.disconnect();
  266. // Throw the necessary error events:
  267. ev = new CEvent ("network", "error", this, "onError");
  268. ev.server = this;
  269. ev.debug = "Connect sequence was cancelled.";
  270. ev.errorCode = JSIRC_ERR_CANCELLED;
  271. this.eventPump.addEvent(ev);
  272. }
  273. // We're waiting for onDoConnect, so try a reconnect (which will fail us)
  274. else if (this.state == NET_WAITING)
  275. {
  276. this.state = NET_CANCELLING;
  277. // onDoConnect will throw the error events for us, as it will fail
  278. this.immediateConnect();
  279. }
  280. else
  281. {
  282. dd("Network cancel in odd state: " + this.state);
  283. }
  284. }
  285. /*
  286. * Handles a request to connect to a primary server.
  287. */
  288. CIRCNetwork.prototype.onDoConnect =
  289. function net_doconnect(e)
  290. {
  291. const NS_ERROR_OFFLINE = 0x804b0010;
  292. var c;
  293. // Clear the timer, if there is one.
  294. if ("reconnectTimer" in this)
  295. {
  296. clearTimeout(this.reconnectTimer);
  297. delete this.reconnectTimer;
  298. }
  299. var ev;
  300. if (this.state == NET_CANCELLING)
  301. {
  302. if ("primServ" in this && this.primServ.connection)
  303. this.primServ.connection.disconnect();
  304. else
  305. this.state = NET_OFFLINE;
  306. ev = new CEvent ("network", "error", this, "onError");
  307. ev.server = this;
  308. ev.debug = "Connect sequence was cancelled.";
  309. ev.errorCode = JSIRC_ERR_CANCELLED;
  310. this.eventPump.addEvent(ev);
  311. return false;
  312. }
  313. if ("primServ" in this && this.primServ.isConnected)
  314. return true;
  315. this.connectAttempt++;
  316. this.connectCandidate++;
  317. this.state = NET_CONNECTING; /* connection is considered "made" when server
  318. * sends a 001 message (see server.on001) */
  319. var host = this.nextHost++;
  320. if (host >= this.serverList.length)
  321. {
  322. this.nextHost = 1;
  323. host = 0;
  324. }
  325. if (this.serverList[host].isSecure || !this.requireSecurity)
  326. {
  327. ev = new CEvent ("network", "startconnect", this, "onStartConnect");
  328. ev.debug = "Connecting to " + this.serverList[host].unicodeName + ":" +
  329. this.serverList[host].port + ", attempt " + this.connectAttempt +
  330. " of " + this.MAX_CONNECT_ATTEMPTS + "...";
  331. ev.host = this.serverList[host].hostname;
  332. ev.port = this.serverList[host].port;
  333. ev.server = this.serverList[host];
  334. ev.connectAttempt = this.connectAttempt;
  335. ev.reconnectDelayMs = this.getReconnectDelayMs();
  336. this.eventPump.addEvent (ev);
  337. try
  338. {
  339. if (!this.serverList[host].connect(null))
  340. {
  341. /* connect failed, try again */
  342. this.delayedConnect();
  343. }
  344. }
  345. catch(ex)
  346. {
  347. this.state = NET_OFFLINE;
  348. ev = new CEvent("network", "error", this, "onError");
  349. ev.server = this;
  350. ev.debug = "Exception opening socket: " + ex;
  351. ev.errorCode = JSIRC_ERR_NO_SOCKET;
  352. if ((typeof ex == "object") && (ex.result == NS_ERROR_OFFLINE))
  353. ev.errorCode = JSIRC_ERR_OFFLINE;
  354. if ((typeof ex == "string") && (ex == JSIRC_ERR_PAC_LOADING))
  355. {
  356. ev.errorCode = JSIRC_ERR_PAC_LOADING;
  357. ev.retryDelay = CIRCNetwork.prototype.PAC_RECONNECT_DELAY;
  358. /* PAC loading is not a problem with any specific server. We'll
  359. * retry the connection in 5 seconds.
  360. */
  361. this.nextHost--;
  362. this.state = NET_WAITING;
  363. setTimeout(function(n) { n.immediateConnect() },
  364. ev.retryDelay, this);
  365. }
  366. this.eventPump.addEvent(ev);
  367. }
  368. }
  369. else
  370. {
  371. /* Server doesn't use SSL as requested, try next one.
  372. * In the meantime, correct the connection attempt counter */
  373. this.connectAttempt--;
  374. this.immediateConnect();
  375. }
  376. return true;
  377. }
  378. CIRCNetwork.prototype.isConnected =
  379. function net_connected (e)
  380. {
  381. return ("primServ" in this && this.primServ.isConnected);
  382. }
  383. CIRCNetwork.prototype.ignore =
  384. function net_ignore (hostmask)
  385. {
  386. var input = getHostmaskParts(hostmask);
  387. if (input.mask in this.ignoreList)
  388. return false;
  389. this.ignoreList[input.mask] = input;
  390. this.ignoreMaskCache = new Object();
  391. return true;
  392. }
  393. CIRCNetwork.prototype.unignore =
  394. function net_ignore (hostmask)
  395. {
  396. var input = getHostmaskParts(hostmask);
  397. if (!(input.mask in this.ignoreList))
  398. return false;
  399. delete this.ignoreList[input.mask];
  400. this.ignoreMaskCache = new Object();
  401. return true;
  402. }
  403. /*
  404. * irc server
  405. */
  406. function CIRCServer (parent, hostname, port, isSecure, password)
  407. {
  408. var serverName = hostname + ":" + port;
  409. var s;
  410. if (serverName in parent.servers)
  411. {
  412. s = parent.servers[serverName];
  413. }
  414. else
  415. {
  416. s = this;
  417. s.channels = new Object();
  418. s.users = new Object();
  419. }
  420. s.unicodeName = serverName;
  421. s.viewName = serverName;
  422. s.canonicalName = serverName;
  423. s.encodedName = serverName;
  424. s.hostname = hostname;
  425. s.port = port;
  426. s.parent = parent;
  427. s.isSecure = isSecure;
  428. s.password = password;
  429. s.connection = null;
  430. s.isConnected = false;
  431. s.sendQueue = new Array();
  432. s.lastSend = new Date("1/1/1980");
  433. s.lastPingSent = null;
  434. s.lastPing = null;
  435. s.sendsThisRound = 0;
  436. s.savedLine = "";
  437. s.lag = -1;
  438. s.usersStable = true;
  439. s.supports = null;
  440. s.channelTypes = null;
  441. s.channelModes = null;
  442. s.channelCount = -1;
  443. s.userModes = null;
  444. s.maxLineLength = 400;
  445. s.capab = new Object();
  446. parent.servers[s.canonicalName] = s;
  447. if ("onInit" in s)
  448. s.onInit();
  449. return s;
  450. }
  451. CIRCServer.prototype.MAX_LINES_PER_SEND = 0; /* unlimited */
  452. CIRCServer.prototype.MS_BETWEEN_SENDS = 1500;
  453. CIRCServer.prototype.READ_TIMEOUT = 100;
  454. CIRCServer.prototype.TOO_MANY_LINES_MSG = "\01ACTION has said too much\01";
  455. CIRCServer.prototype.VERSION_RPLY = "JS-IRC Library v0.01, " +
  456. "Copyright (C) 1999 Robert Ginda; rginda@ndcico.com";
  457. CIRCServer.prototype.OS_RPLY = "Unknown";
  458. CIRCServer.prototype.HOST_RPLY = "Unknown";
  459. CIRCServer.prototype.DEFAULT_REASON = "no reason";
  460. /* true means on352 code doesn't collect hostmask, username, etc. */
  461. CIRCServer.prototype.LIGHTWEIGHT_WHO = false;
  462. /* -1 == never, 0 == prune onQuit, >0 == prune when >X ms old */
  463. CIRCServer.prototype.PRUNE_OLD_USERS = -1;
  464. CIRCServer.prototype.TYPE = "IRCServer";
  465. // Define functions to set modes so they're easily readable.
  466. // name is the name used on the CIRCChanMode object
  467. // getValue is a function returning the value the canonicalmode should be set to
  468. // given a certain modifier and appropriate data.
  469. CIRCServer.prototype.canonicalChanModes = {
  470. i: {
  471. name: "invite",
  472. getValue: function (modifier) { return (modifier == "+"); }
  473. },
  474. m: {
  475. name: "moderated",
  476. getValue: function (modifier) { return (modifier == "+"); }
  477. },
  478. n: {
  479. name: "publicMessages",
  480. getValue: function (modifier) { return (modifier == "-"); }
  481. },
  482. t: {
  483. name: "publicTopic",
  484. getValue: function (modifier) { return (modifier == "-"); }
  485. },
  486. s: {
  487. name: "secret",
  488. getValue: function (modifier) { return (modifier == "+"); }
  489. },
  490. p: {
  491. name: "pvt",
  492. getValue: function (modifier) { return (modifier == "+"); }
  493. },
  494. k: {
  495. name: "key",
  496. getValue: function (modifier, data)
  497. {
  498. if (modifier == "+")
  499. return data;
  500. else
  501. return "";
  502. }
  503. },
  504. l: {
  505. name: "limit",
  506. getValue: function (modifier, data)
  507. {
  508. // limit is special - we return -1 if there is no limit.
  509. if (modifier == "-")
  510. return -1;
  511. else
  512. return data;
  513. }
  514. }
  515. };
  516. CIRCServer.prototype.toLowerCase =
  517. function serv_tolowercase(str)
  518. {
  519. /* This is an implementation that lower-cases strings according to the
  520. * prevailing CASEMAPPING setting for the server. Values for this are:
  521. *
  522. * o "ascii": The ASCII characters 97 to 122 (decimal) are defined as
  523. * the lower-case characters of ASCII 65 to 90 (decimal). No other
  524. * character equivalency is defined.
  525. * o "strict-rfc1459": The ASCII characters 97 to 125 (decimal) are
  526. * defined as the lower-case characters of ASCII 65 to 93 (decimal).
  527. * No other character equivalency is defined.
  528. * o "rfc1459": The ASCII characters 97 to 126 (decimal) are defined as
  529. * the lower-case characters of ASCII 65 to 94 (decimal). No other
  530. * character equivalency is defined.
  531. *
  532. */
  533. function replaceFunction(chr)
  534. {
  535. return String.fromCharCode(chr.charCodeAt(0) + 32);
  536. }
  537. var mapping = "rfc1459";
  538. if (this.supports)
  539. mapping = this.supports.casemapping;
  540. /* NOTE: There are NO breaks in this switch. This is CORRECT.
  541. * Each mapping listed is a super-set of those below, thus we only
  542. * transform the extra characters, and then fall through.
  543. */
  544. switch (mapping)
  545. {
  546. case "rfc1459":
  547. str = str.replace(/\^/g, replaceFunction);
  548. case "strict-rfc1459":
  549. str = str.replace(/[\[\\\]]/g, replaceFunction);
  550. case "ascii":
  551. str = str.replace(/[A-Z]/g, replaceFunction);
  552. }
  553. return str;
  554. }
  555. CIRCServer.prototype.getURL =
  556. function serv_geturl(target, flags)
  557. {
  558. var scheme = (this.isSecure ? "ircs" : "irc");
  559. var obj = {host: this.hostname, scheme: scheme, isserver: true,
  560. port: this.port, needpass: Boolean(this.password)};
  561. if (target)
  562. obj.target = target;
  563. if (flags)
  564. {
  565. for (var i = 0; i < flags.length; i++)
  566. obj[flags[i]] = true;
  567. }
  568. return constructIRCURL(obj);
  569. }
  570. CIRCServer.prototype.getUser =
  571. function chan_getuser(nick)
  572. {
  573. var tnick = this.toLowerCase(nick);
  574. if (tnick in this.users)
  575. return this.users[tnick];
  576. tnick = this.toLowerCase(fromUnicode(nick, this));
  577. if (tnick in this.users)
  578. return this.users[tnick];
  579. return null;
  580. }
  581. CIRCServer.prototype.getChannel =
  582. function chan_getchannel(name)
  583. {
  584. var tname = this.toLowerCase(name);
  585. if (tname in this.channels)
  586. return this.channels[tname];
  587. tname = this.toLowerCase(fromUnicode(name, this));
  588. if (tname in this.channels)
  589. return this.channels[tname];
  590. return null;
  591. }
  592. CIRCServer.prototype.connect =
  593. function serv_connect (password)
  594. {
  595. try
  596. {
  597. this.connection = new CBSConnection();
  598. }
  599. catch (ex)
  600. {
  601. ev = new CEvent ("server", "error", this, "onError");
  602. ev.server = this;
  603. ev.debug = "Couldn't create socket :" + ex;
  604. ev.errorCode = JSIRC_ERR_NO_SOCKET;
  605. ev.exception = ex;
  606. this.parent.eventPump.addEvent (ev);
  607. return false;
  608. }
  609. var config = { isSecure: this.isSecure };
  610. if (this.parent.PROXY_TYPE_OVERRIDE)
  611. config.proxy = this.parent.PROXY_TYPE_OVERRIDE;
  612. if (this.connection.connect(this.hostname, this.port, config))
  613. {
  614. var ev = new CEvent("server", "connect", this, "onConnect");
  615. if (password)
  616. this.password = password;
  617. ev.server = this;
  618. this.parent.eventPump.addEvent (ev);
  619. this.isConnected = true;
  620. if (jsenv.HAS_NSPR_EVENTQ)
  621. this.connection.startAsyncRead(this);
  622. else
  623. this.parent.eventPump.addEvent(new CEvent("server", "poll", this,
  624. "onPoll"));
  625. }
  626. return true;
  627. }
  628. /*
  629. * What to do when the client connects to it's primary server
  630. */
  631. CIRCServer.prototype.onConnect =
  632. function serv_onconnect (e)
  633. {
  634. this.parent.primServ = e.server;
  635. this.login(this.parent.INITIAL_NICK, this.parent.INITIAL_NAME,
  636. this.parent.INITIAL_DESC);
  637. return true;
  638. }
  639. CIRCServer.prototype.onStreamDataAvailable =
  640. function serv_sda (request, inStream, sourceOffset, count)
  641. {
  642. var ev = new CEvent ("server", "data-available", this,
  643. "onDataAvailable");
  644. ev.line = this.connection.readData(0, count);
  645. /* route data-available as we get it. the data-available handler does
  646. * not do much, so we can probably get away with this without starving
  647. * the UI even under heavy input traffic.
  648. */
  649. this.parent.eventPump.routeEvent(ev);
  650. }
  651. CIRCServer.prototype.onStreamClose =
  652. function serv_sockdiscon(status)
  653. {
  654. var ev = new CEvent ("server", "disconnect", this, "onDisconnect");
  655. ev.server = this;
  656. ev.disconnectStatus = status;
  657. if (ev.disconnectStatus == NS_ERROR_BINDING_ABORTED)
  658. ev.disconnectStatus = NS_ERROR_ABORT;
  659. this.parent.eventPump.addEvent (ev);
  660. }
  661. CIRCServer.prototype.flushSendQueue =
  662. function serv_flush()
  663. {
  664. this.sendQueue.length = 0;
  665. dd("sendQueue flushed.");
  666. return true;
  667. }
  668. CIRCServer.prototype.login =
  669. function serv_login(nick, name, desc)
  670. {
  671. nick = nick.replace(/ /g, "_");
  672. name = name.replace(/ /g, "_");
  673. if (!nick)
  674. nick = "nick";
  675. if (!name)
  676. name = nick;
  677. if (!desc)
  678. desc = nick;
  679. this.me = new CIRCUser(this, nick, null, name);
  680. if (this.password)
  681. this.sendData("PASS " + this.password + "\n");
  682. this.changeNick(this.me.unicodeName);
  683. this.sendData("USER " + name + " * * :" +
  684. fromUnicode(desc, this) + "\n");
  685. }
  686. CIRCServer.prototype.logout =
  687. function serv_logout(reason)
  688. {
  689. if (reason == null || typeof reason == "undefined")
  690. reason = this.DEFAULT_REASON;
  691. this.quitting = true;
  692. this.connection.sendData("QUIT :" +
  693. fromUnicode(reason, this.parent) + "\n");
  694. this.connection.disconnect();
  695. }
  696. CIRCServer.prototype.addTarget =
  697. function serv_addtarget(name)
  698. {
  699. if (arrayIndexOf(this.channelTypes, name[0]) != -1) {
  700. return this.addChannel(name);
  701. } else {
  702. return this.addUser(name);
  703. }
  704. }
  705. CIRCServer.prototype.addChannel =
  706. function serv_addchan(unicodeName, charset)
  707. {
  708. return new CIRCChannel(this, unicodeName, fromUnicode(unicodeName, charset));
  709. }
  710. CIRCServer.prototype.addUser =
  711. function serv_addusr(unicodeName, name, host)
  712. {
  713. return new CIRCUser(this, unicodeName, null, name, host);
  714. }
  715. CIRCServer.prototype.getChannelsLength =
  716. function serv_chanlen()
  717. {
  718. var i = 0;
  719. for (var p in this.channels)
  720. i++;
  721. return i;
  722. }
  723. CIRCServer.prototype.getUsersLength =
  724. function serv_chanlen()
  725. {
  726. var i = 0;
  727. for (var p in this.users)
  728. i++;
  729. return i;
  730. }
  731. CIRCServer.prototype.sendData =
  732. function serv_senddata (msg)
  733. {
  734. this.queuedSendData (msg);
  735. }
  736. CIRCServer.prototype.queuedSendData =
  737. function serv_senddata (msg)
  738. {
  739. if (this.sendQueue.length == 0)
  740. this.parent.eventPump.addEvent (new CEvent ("server", "senddata",
  741. this, "onSendData"));
  742. arrayInsertAt (this.sendQueue, 0, new String(msg));
  743. }
  744. /*
  745. * Takes care not to let more than MAX_LINES_PER_SEND lines out per
  746. * cycle. Cycle's are defined as the time between onPoll calls.
  747. */
  748. CIRCServer.prototype.messageTo =
  749. function serv_messto (code, target, msg, ctcpCode)
  750. {
  751. var lines = String(msg).split ("\n");
  752. var sendable = 0, i;
  753. var pfx = "", sfx = "";
  754. if (this.MAX_LINES_PER_SEND &&
  755. this.sendsThisRound > this.MAX_LINES_PER_SEND)
  756. return false;
  757. if (ctcpCode)
  758. {
  759. pfx = "\01" + ctcpCode;
  760. sfx = "\01";
  761. }
  762. for (i in lines)
  763. {
  764. if (lines[i])
  765. {
  766. while (lines[i].length > this.maxLineLength)
  767. {
  768. var extraLine = lines[i].substr(0, this.maxLineLength - 5);
  769. var pos = extraLine.lastIndexOf(" ");
  770. if ((pos >= 0) && (pos >= this.maxLineLength - 15))
  771. {
  772. // Smart-split.
  773. extraLine = lines[i].substr(0, pos) + "...";
  774. lines[i] = "..." + lines[i].substr(extraLine.length - 2);
  775. }
  776. else
  777. {
  778. // Dump-split.
  779. extraLine = lines[i].substr(0, this.maxLineLength);
  780. lines[i] = lines[i].substr(extraLine.length);
  781. }
  782. if (!this.messageTo(code, target, extraLine, ctcpCode))
  783. return false;
  784. }
  785. }
  786. }
  787. // Check this again, since we may have actually sent stuff in the loop above.
  788. if (this.MAX_LINES_PER_SEND &&
  789. this.sendsThisRound > this.MAX_LINES_PER_SEND)
  790. return false;
  791. for (i in lines)
  792. if ((lines[i] != "") || ctcpCode) sendable++;
  793. for (i in lines)
  794. {
  795. if (this.MAX_LINES_PER_SEND && (
  796. ((this.sendsThisRound == this.MAX_LINES_PER_SEND - 1) &&
  797. (sendable > this.MAX_LINES_PER_SEND)) ||
  798. this.sendsThisRound == this.MAX_LINES_PER_SEND))
  799. {
  800. this.sendData ("PRIVMSG " + target + " :" +
  801. this.TOO_MANY_LINES_MSG + "\n");
  802. this.sendsThisRound++;
  803. return true;
  804. }
  805. if ((lines[i] != "") || ctcpCode)
  806. {
  807. var line = code + " " + target + " :" + pfx;
  808. this.sendsThisRound++;
  809. if (lines[i] != "")
  810. {
  811. if (ctcpCode)
  812. line += " ";
  813. line += lines[i] + sfx;
  814. }
  815. else
  816. line += sfx;
  817. //dd ("-*- irc sending '" + line + "'");
  818. this.sendData(line + "\n");
  819. }
  820. }
  821. return true;
  822. }
  823. CIRCServer.prototype.sayTo =
  824. function serv_sayto (target, msg)
  825. {
  826. this.messageTo("PRIVMSG", target, msg);
  827. }
  828. CIRCServer.prototype.noticeTo =
  829. function serv_noticeto (target, msg)
  830. {
  831. this.messageTo("NOTICE", target, msg);
  832. }
  833. CIRCServer.prototype.actTo =
  834. function serv_actto (target, msg)
  835. {
  836. this.messageTo("PRIVMSG", target, msg, "ACTION");
  837. }
  838. CIRCServer.prototype.ctcpTo =
  839. function serv_ctcpto (target, code, msg, method)
  840. {
  841. msg = msg || "";
  842. method = method || "PRIVMSG";
  843. code = code.toUpperCase();
  844. if (code == "PING" && !msg)
  845. msg = Number(new Date());
  846. this.messageTo(method, target, msg, code);
  847. }
  848. CIRCServer.prototype.changeNick =
  849. function serv_changenick(newNick)
  850. {
  851. this.sendData("NICK " + fromUnicode(newNick, this) + "\n");
  852. }
  853. CIRCServer.prototype.updateLagTimer =
  854. function serv_uptimer()
  855. {
  856. this.connection.sendData("PING :LAGTIMER\n");
  857. this.lastPing = this.lastPingSent = new Date();
  858. }
  859. CIRCServer.prototype.userhost =
  860. function serv_userhost(target)
  861. {
  862. this.sendData("USERHOST " + fromUnicode(target, this) + "\n");
  863. }
  864. CIRCServer.prototype.userip =
  865. function serv_userip(target)
  866. {
  867. this.sendData("USERIP " + fromUnicode(target, this) + "\n");
  868. }
  869. CIRCServer.prototype.who =
  870. function serv_who(target)
  871. {
  872. this.sendData("WHO " + fromUnicode(target, this) + "\n");
  873. }
  874. /**
  875. * Abstracts the whois command.
  876. *
  877. * @param target intended user(s).
  878. */
  879. CIRCServer.prototype.whois =
  880. function serv_whois (target)
  881. {
  882. this.sendData("WHOIS " + fromUnicode(target, this) + "\n");
  883. }
  884. CIRCServer.prototype.whowas =
  885. function serv_whowas(target, limit)
  886. {
  887. if (typeof limit == "undefined")
  888. limit = 1;
  889. else if (limit == 0)
  890. limit = "";
  891. this.sendData("WHOWAS " + fromUnicode(target, this) + " " + limit + "\n");
  892. }
  893. CIRCServer.prototype.onDisconnect =
  894. function serv_disconnect(e)
  895. {
  896. function stateChangeFn(network, state) {
  897. network.state = state;
  898. };
  899. function delayedConnectFn(network) {
  900. network.delayedConnect();
  901. };
  902. /* If we're not connected and get this, it means we have almost certainly
  903. * encountered a read or write error on the socket post-disconnect. There's
  904. * no point propagating this any further, as we've already notified the
  905. * user of the disconnect (with the right error).
  906. */
  907. if (!this.isConnected)
  908. return;
  909. // Don't reconnect from a certificate error.
  910. var certErrors = [SEC_ERROR_EXPIRED_CERTIFICATE, SEC_ERROR_UNKNOWN_ISSUER,
  911. SEC_ERROR_UNTRUSTED_ISSUER, SEC_ERROR_UNTRUSTED_CERT,
  912. SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE,
  913. SEC_ERROR_CA_CERT_INVALID, SEC_ERROR_INADEQUATE_KEY_USAGE,
  914. SSL_ERROR_BAD_CERT_DOMAIN];
  915. var certError = arrayContains(certErrors, e.disconnectStatus);
  916. // Don't reconnect if our connection was aborted.
  917. var wasAborted = (e.disconnectStatus == NS_ERROR_ABORT);
  918. var dontReconnect = certError || wasAborted;
  919. if (((this.parent.state == NET_CONNECTING) && !dontReconnect) ||
  920. /* fell off while connecting, try again */
  921. (this.parent.primServ == this) && (this.parent.state == NET_ONLINE) &&
  922. (!("quitting" in this) && this.parent.stayingPower && !dontReconnect))
  923. { /* fell off primary server, reconnect to any host in the serverList */
  924. setTimeout(delayedConnectFn, 0, this.parent);
  925. }
  926. else
  927. {
  928. setTimeout(stateChangeFn, 0, this.parent, NET_OFFLINE);
  929. }
  930. e.server = this;
  931. e.set = "network";
  932. e.destObject = this.parent;
  933. e.quitting = this.quitting;
  934. for (var c in this.channels)
  935. {
  936. this.channels[c].users = new Object();
  937. this.channels[c].active = false;
  938. }
  939. this.connection = null;
  940. this.isConnected = false;
  941. delete this.quitting;
  942. }
  943. CIRCServer.prototype.onSendData =
  944. function serv_onsenddata (e)
  945. {
  946. if (!this.isConnected || (this.parent.state == NET_CANCELLING))
  947. {
  948. dd ("Can't send to disconnected socket");
  949. this.flushSendQueue();
  950. return false;
  951. }
  952. var d = new Date();
  953. this.sendsThisRound = 0;
  954. // Wheee, some sanity checking! (there's been at least one case of lastSend
  955. // ending up in the *future* at this point, which kinda busts things)
  956. if (this.lastSend > d)
  957. this.lastSend = 0;
  958. if (((d - this.lastSend) >= this.MS_BETWEEN_SENDS) &&
  959. this.sendQueue.length > 0)
  960. {
  961. var s = this.sendQueue.pop();
  962. if (s)
  963. {
  964. try
  965. {
  966. this.connection.sendData(s);
  967. }
  968. catch(ex)
  969. {
  970. dd("Exception in queued send: " + ex);
  971. this.flushSendQueue();
  972. var ev = new CEvent("server", "disconnect",
  973. this, "onDisconnect");
  974. ev.server = this;
  975. ev.reason = "error";
  976. ev.exception = ex;
  977. ev.disconnectStatus = NS_ERROR_ABORT;
  978. this.parent.eventPump.addEvent(ev);
  979. return false;
  980. }
  981. this.lastSend = d;
  982. }
  983. }
  984. else
  985. {
  986. this.parent.eventPump.addEvent(new CEvent("event-pump", "yield",
  987. null, ""));
  988. }
  989. if (this.sendQueue.length > 0)
  990. this.parent.eventPump.addEvent(new CEvent("server", "senddata",
  991. this, "onSendData"));
  992. return true;
  993. }
  994. CIRCServer.prototype.onPoll =
  995. function serv_poll(e)
  996. {
  997. var lines;
  998. var ex;
  999. var ev;
  1000. try
  1001. {
  1002. if (this.parent.state != NET_CANCELLING)
  1003. line = this.connection.readData(this.READ_TIMEOUT);
  1004. }
  1005. catch (ex)
  1006. {
  1007. dd ("*** Caught exception " + ex + " reading from server " +
  1008. this.hostname);
  1009. if (jsenv.HAS_RHINO && (ex instanceof java.lang.ThreadDeath))
  1010. {
  1011. dd("### catching a ThreadDeath");
  1012. throw(ex);
  1013. }
  1014. else
  1015. {
  1016. ev = new CEvent ("server", "disconnect", this, "onDisconnect");
  1017. ev.server = this;
  1018. ev.reason = "error";
  1019. ev.exception = ex;
  1020. ev.disconnectStatus = NS_ERROR_ABORT;
  1021. this.parent.eventPump.addEvent (ev);
  1022. return false;
  1023. }
  1024. }
  1025. this.parent.eventPump.addEvent (new CEvent ("server", "poll", this,
  1026. "onPoll"));
  1027. if (line)
  1028. {
  1029. ev = new CEvent ("server", "data-available", this, "onDataAvailable");
  1030. ev.line = line;
  1031. this.parent.eventPump.routeEvent(ev);
  1032. }
  1033. return true;
  1034. }
  1035. CIRCServer.prototype.onDataAvailable =
  1036. function serv_ppline(e)
  1037. {
  1038. var line = e.line;
  1039. if (line == "")
  1040. return false;
  1041. var incomplete = (line[line.length - 1] != '\n');
  1042. var lines = line.split("\n");
  1043. if (this.savedLine)
  1044. {
  1045. lines[0] = this.savedLine + lines[0];
  1046. this.savedLine = "";
  1047. }
  1048. if (incomplete)
  1049. this.savedLine = lines.pop();
  1050. for (var i in lines)
  1051. {
  1052. var ev = new CEvent("server", "rawdata", this, "onRawData");
  1053. ev.data = lines[i].replace(/\r/g, "");
  1054. if (ev.data)
  1055. {
  1056. if (ev.data.match(/^(?::[^ ]+ )?(?:32[123]|352|315) /i))
  1057. this.parent.eventPump.addBulkEvent(ev);
  1058. else
  1059. this.parent.eventPump.addEvent(ev);
  1060. }
  1061. }
  1062. return true;
  1063. }
  1064. /*
  1065. * onRawData begins shaping the event by parsing the IRC message at it's
  1066. * simplest level. After onRawData, the event will have the following
  1067. * properties:
  1068. * name value
  1069. *
  1070. * set............"server"
  1071. * type..........."parsedata"
  1072. * destMethod....."onParsedData"
  1073. * destObject.....server (this)
  1074. * server.........server (this)
  1075. * connection.....CBSConnection (this.connection)
  1076. * source.........the <prefix> of the message (if it exists)
  1077. * user...........user object initialized with data from the message <prefix>
  1078. * params.........array containing the parameters of the message
  1079. * code...........the first parameter (most messages have this)
  1080. *
  1081. * See Section 2.3.1 of RFC 1459 for details on <prefix>, <middle> and
  1082. * <trailing> tokens.
  1083. */
  1084. CIRCServer.prototype.onRawData =
  1085. function serv_onRawData(e)
  1086. {
  1087. var ary;
  1088. var l = e.data;
  1089. if (l.length == 0)
  1090. {
  1091. dd ("empty line on onRawData?");
  1092. return false;
  1093. }
  1094. if (l[0] == ":")
  1095. {
  1096. // Must split only on REAL spaces here, not just any old whitespace.
  1097. ary = l.match(/:([^ ]+) +(.*)/);
  1098. e.source = ary[1];
  1099. l = ary[2];
  1100. ary = e.source.match(/([^ ]+)!([^ ]+)@(.*)/);
  1101. if (ary)
  1102. {
  1103. e.user = new CIRCUser(this, null, ary[1], ary[2], ary[3]);
  1104. }
  1105. else
  1106. {
  1107. ary = e.source.match(/([^ ]+)@(.*)/);
  1108. if (ary)
  1109. {
  1110. e.user = new CIRCUser(this, null, ary[1], null, ary[2]);
  1111. }
  1112. else
  1113. {
  1114. ary = e.source.match(/([^ ]+)!(.*)/);
  1115. if (ary)
  1116. e.user = new CIRCUser(this, null, ary[1], ary[2], null);
  1117. }
  1118. }
  1119. }
  1120. e.ignored = false;
  1121. if (("user" in e) && e.user && ("ignoreList" in this.parent))
  1122. {
  1123. // Assumption: if "ignoreList" is in this.parent, we assume that:
  1124. // a) it's an array.
  1125. // b) ignoreMaskCache also exists, and
  1126. // c) it's an array too.
  1127. if (!(e.source in this.parent.ignoreMaskCache))
  1128. {
  1129. for (var m in this.parent.ignoreList)
  1130. {
  1131. if (hostmaskMatches(e.user, this.parent.ignoreList[m]))
  1132. {
  1133. e.ignored = true;
  1134. break;
  1135. }
  1136. }
  1137. /* Save this exact source in the cache, with results of tests. */
  1138. this.parent.ignoreMaskCache[e.source] = e.ignored;
  1139. }
  1140. else
  1141. {
  1142. e.ignored = this.parent.ignoreMaskCache[e.source];
  1143. }
  1144. }
  1145. e.server = this;
  1146. var sep = l.indexOf(" :");
  1147. if (sep != -1) /* <trailing> param, if there is one */
  1148. {
  1149. var trail = l.substr (sep + 2, l.length);
  1150. e.params = l.substr(0, sep).split(/ +/);
  1151. e.params[e.params.length] = trail;
  1152. }
  1153. else
  1154. {
  1155. e.params = l.split(/ +/);
  1156. }
  1157. e.decodeParam = decodeParam;
  1158. e.code = e.params[0].toUpperCase();
  1159. // Ignore all Privmsg and Notice messages here.
  1160. if (e.ignored && ((e.code == "PRIVMSG") || (e.code == "NOTICE")))
  1161. return true;
  1162. e.type = "parseddata";
  1163. e.destObject = this;
  1164. e.destMethod = "onParsedData";
  1165. return true;
  1166. }
  1167. /*
  1168. * onParsedData forwards to next event, based on |e.code|
  1169. */
  1170. CIRCServer.prototype.onParsedData =
  1171. function serv_onParsedData(e)
  1172. {
  1173. e.type = this.toLowerCase(e.code);
  1174. if (!e.code[0])
  1175. {
  1176. dd (dumpObjectTree (e));
  1177. return false;
  1178. }
  1179. e.destMethod = "on" + e.code[0].toUpperCase() +
  1180. e.code.substr (1, e.code.length).toLowerCase();
  1181. if (typeof this[e.destMethod] == "function")
  1182. e.destObject = this;
  1183. else if (typeof this["onUnknown"] == "function")
  1184. e.destMethod = "onUnknown";
  1185. else if (typeof this.parent[e.destMethod] == "function")
  1186. {
  1187. e.set = "network";
  1188. e.destObject = this.parent;
  1189. }
  1190. else
  1191. {
  1192. e.set = "network";
  1193. e.destObject = this.parent;
  1194. e.destMethod = "onUnknown";
  1195. }
  1196. return true;
  1197. }
  1198. /* User changed topic */
  1199. CIRCServer.prototype.onTopic =
  1200. function serv_topic (e)
  1201. {
  1202. e.channel = new CIRCChannel(this, null, e.params[1]);
  1203. e.channel.topicBy = e.user.unicodeName;
  1204. e.channel.topicDate = new Date();
  1205. e.channel.topic = toUnicode(e.params[2], e.channel);
  1206. e.destObject = e.channel;
  1207. e.set = "channel";
  1208. return true;
  1209. }
  1210. /* Successful login */
  1211. CIRCServer.prototype.on001 =
  1212. function serv_001 (e)
  1213. {
  1214. this.parent.connectAttempt = 0;
  1215. this.parent.connectCandidate = 0;
  1216. this.parent.state = NET_ONLINE;
  1217. // nextHost is incremented after picking a server. Push it back here.
  1218. this.parent.nextHost--;
  1219. /* servers won't send a nick change notification if user was forced
  1220. * to change nick while logging in (eg. nick already in use.) We need
  1221. * to verify here that what the server thinks our name is, matches what
  1222. * we think it is. If not, the server wins.
  1223. */
  1224. if (e.params[1] != e.server.me.encodedName)
  1225. {
  1226. renameProperty(e.server.users, e.server.me.canonicalName,
  1227. this.toLowerCase(e.params[1]));
  1228. e.server.me.changeNick(toUnicode(e.params[1], this));
  1229. }
  1230. /* Set up supports defaults here.
  1231. * This is so that we don't waste /huge/ amounts of RAM for the network's
  1232. * servers just because we know about them. Until we connect, that is.
  1233. * These defaults are taken from the draft 005 RPL_ISUPPORTS here:
  1234. * http://www.ietf.org/internet-drafts/draft-brocklesby-irc-isupport-02.txt
  1235. */
  1236. this.supports = new Object();
  1237. this.supports.modes = 3;
  1238. this.supports.maxchannels = 10;
  1239. this.supports.nicklen = 9;
  1240. this.supports.casemapping = "rfc1459";
  1241. this.supports.channellen = 200;
  1242. this.supports.chidlen = 5;
  1243. /* Make sure it's possible to tell if we've actually got a 005 message. */
  1244. this.supports.rpl_isupport = false;
  1245. this.channelTypes = [ '#', '&' ];
  1246. /* This next one isn't in the isupport draft, but instead is defaulting to
  1247. * the codes we understand. It should be noted, some servers include the
  1248. * mode characters (o, h, v) in the 'a' list, although the draft spec says
  1249. * they should be treated as type 'b'. Luckly, in practise this doesn't
  1250. * matter, since both 'a' and 'b' types always take a parameter in the
  1251. * MODE message, and parsing is not affected. */
  1252. this.channelModes = {
  1253. a: ['b'],
  1254. b: ['k'],
  1255. c: ['l'],
  1256. d: ['i', 'm', 'n', 'p', 's', 't']
  1257. };
  1258. // Default to support of v/+ and o/@ only.
  1259. this.userModes = [
  1260. { mode: 'o', symbol: '@' },
  1261. { mode: 'v', symbol: '+' }
  1262. ];
  1263. // Assume the server supports no extra interesting commands.
  1264. this.servCmds = {};
  1265. if (this.parent.INITIAL_UMODE)
  1266. {
  1267. e.server.sendData("MODE " + e.server.me.encodedName + " :" +
  1268. this.parent.INITIAL_UMODE + "\n");
  1269. }
  1270. this.parent.users = this.users;
  1271. e.destObject = this.parent;
  1272. e.set = "network";
  1273. }
  1274. /* server features */
  1275. CIRCServer.prototype.on005 =
  1276. function serv_005 (e)
  1277. {
  1278. var oldCaseMapping = this.supports["casemapping"];
  1279. /* Drop params 0 and 1. */
  1280. for (var i = 2; i < e.params.length; i++) {
  1281. var itemStr = e.params[i];
  1282. /* Items may be of the forms:
  1283. * NAME
  1284. * -NAME
  1285. * NAME=value
  1286. * Value may be empty on occasion.
  1287. * No value allowed for -NAME items.
  1288. */
  1289. var item = itemStr.match(/^(-?)([A-Z]+)(=(.*))?$/i);
  1290. if (! item)
  1291. continue;
  1292. var name = item[2].toLowerCase();
  1293. if (("3" in item) && item[3])
  1294. {
  1295. // And other items are stored as-is, though numeric items
  1296. // get special treatment to make our life easier later.
  1297. if (("4" in item) && item[4].match(/^\d+$/))
  1298. this.supports[name] = Number(item[4]);
  1299. else
  1300. this.supports[name] = item[4];
  1301. }
  1302. else
  1303. {
  1304. // Boolean-type items stored as 'true'.
  1305. this.supports[name] = !(("1" in item) && item[1] == "-");
  1306. }
  1307. }
  1308. // Update all users and channels if the casemapping changed.
  1309. if (this.supports["casemapping"] != oldCaseMapping)
  1310. {
  1311. var newName, encodedName;
  1312. for (var user in this.users)
  1313. {
  1314. newName = this.toLowerCase(this.users[user].encodedName);
  1315. renameProperty(this.users, user, newName);
  1316. this.users[newName].canonicalName = newName;
  1317. }
  1318. for (var channel in this.channels)
  1319. {
  1320. newName = this.toLowerCase(this.channels[channel].encodedName);
  1321. renameProperty(this.channels, this.channels[channel].canonicalName,
  1322. newName);
  1323. this.channels[channel].canonicalName = newName;
  1324. for (user in this.channels[channel].users)
  1325. {
  1326. encodedName = this.channels[channel].users[user].encodedName;
  1327. newName = this.toLowerCase(encodedName);
  1328. renameProperty(this.channels[channel].users, user, newName);
  1329. }
  1330. }
  1331. }
  1332. // Supported 'special' items:
  1333. // CHANTYPES (--> channelTypes{}),
  1334. // PREFIX (--> userModes[{mode,symbol}]),
  1335. // CHANMODES (--> channelModes{a:[], b:[], c:[], d:[]}).
  1336. var m;
  1337. if ("chantypes" in this.supports)
  1338. {
  1339. this.channelTypes = [];
  1340. for (m = 0; m < this.supports.chantypes.length; m++)
  1341. this.channelTypes.push( this.supports.chantypes[m] );
  1342. }
  1343. if ("prefix" in this.supports)
  1344. {
  1345. var mlist = this.supports.prefix.match(/^\((.*)\)(.*)$/i);
  1346. if ((! mlist) || (mlist[1].length != mlist[2].length))
  1347. {
  1348. dd ("** Malformed PREFIX entry in 005 SUPPORTS message **");
  1349. }
  1350. else
  1351. {
  1352. this.userModes = [];
  1353. for (m = 0; m < mlist[1].length; m++)
  1354. this.userModes.push( { mode: mlist[1][m],
  1355. symbol: mlist[2][m] } );
  1356. }
  1357. }
  1358. if ("chanmodes" in this.supports)
  1359. {
  1360. var cmlist = this.supports.chanmodes.split(/,/);
  1361. if ((!cmlist) || (cmlist.length < 4))
  1362. {
  1363. dd ("** Malformed CHANMODES entry in 005 SUPPORTS message **");
  1364. }
  1365. else
  1366. {
  1367. // 4 types - list, set-unset-param, set-only-param, flag.
  1368. this.channelModes = {
  1369. a: cmlist[0].split(''),
  1370. b: cmlist[1].split(''),
  1371. c: cmlist[2].split(''),
  1372. d: cmlist[3].split('')
  1373. };
  1374. }
  1375. }
  1376. if ("cmds" in this.supports)
  1377. {
  1378. // Map this.supports.cmds [comma-list] into this.servCmds [props].
  1379. var cmdlist = this.supports.cmds.split(/,/);
  1380. for (var i = 0; i < cmdlist.length; i++)
  1381. this.servCmds[cmdlist[i].toLowerCase()] = true;
  1382. }
  1383. this.supports.rpl_isupport = true;
  1384. e.destObject = this.parent;
  1385. e.set = "network";
  1386. return true;
  1387. }
  1388. /* users */
  1389. CIRCServer.prototype.on251 =
  1390. function serv_251(e)
  1391. {
  1392. // 251 is the first message we get after 005, so it's now safe to do
  1393. // things that might depend upon server features.
  1394. if (("namesx" in this.supports) && this.supports.namesx)
  1395. this.sendData("PROTOCTL NAMESX\n");
  1396. if (this.parent.INITIAL_CHANNEL)
  1397. {
  1398. this.parent.primChan = this.addChannel(this.parent.INITIAL_CHANNEL);
  1399. this.parent.primChan.join();
  1400. }
  1401. e.destObject = this.parent;
  1402. e.set = "network";
  1403. }
  1404. /* channels */
  1405. CIRCServer.prototype.on254 =
  1406. function serv_254(e)
  1407. {
  1408. this.channelCount = e.params[2];
  1409. e.destObject = this.parent;
  1410. e.set = "network";
  1411. }
  1412. /* CAPAB response */
  1413. CIRCServer.prototype.on290 =
  1414. function my_290 (e)
  1415. {
  1416. // we expect some sort of identifier
  1417. if (e.params.length < 2)
  1418. return;
  1419. switch (e.params[2])
  1420. {
  1421. case "IDENTIFY-MSG":
  1422. /* Every message comes prefixed with either + or -
  1423. + indicates the user is registered
  1424. - indicates the user is not registered */
  1425. this.capab.identifyMsg = true;
  1426. break;
  1427. }
  1428. e.destObject = this.parent;
  1429. e.set = "network";
  1430. }
  1431. /* user away message */
  1432. CIRCServer.prototype.on301 =
  1433. function serv_301(e)
  1434. {
  1435. e.user = new CIRCUser(this, null, e.params[2]);
  1436. e.user.awayMessage = e.decodeParam(3, e.user);
  1437. e.destObject = this.parent;
  1438. e.set = "network";
  1439. }
  1440. /* whois name */
  1441. CIRCServer.prototype.on311 =
  1442. function serv_311 (e)
  1443. {
  1444. e.user = new CIRCUser(this, null, e.params[2], e.params[3], e.params[4]);
  1445. e.user.desc = e.decodeParam(6, e.user);
  1446. e.destObject = this.parent;
  1447. e.set = "network";
  1448. this.pendingWhoisLines = e.user;
  1449. }
  1450. /* whois server */
  1451. CIRCServer.prototype.on312 =
  1452. function serv_312 (e)
  1453. {
  1454. e.user = new CIRCUser(this, null, e.params[2]);
  1455. e.user.connectionHost = e.params[3];
  1456. e.destObject = this.parent;
  1457. e.set = "network";
  1458. }
  1459. /* whois idle time */
  1460. CIRCServer.prototype.on317 =
  1461. function serv_317 (e)
  1462. {
  1463. e.user = new CIRCUser(this, null, e.params[2]);
  1464. e.user.idleSeconds = e.params[3];
  1465. e.destObject = this.parent;
  1466. e.set = "network";
  1467. }
  1468. /* whois channel list */
  1469. CIRCServer.prototype.on319 =
  1470. function serv_319(e)
  1471. {
  1472. e.user = new CIRCUser(this, null, e.params[2]);
  1473. e.destObject = this.parent;
  1474. e.set = "network";
  1475. }
  1476. /* end of whois */
  1477. CIRCServer.prototype.on318 =
  1478. function serv_318(e)
  1479. {
  1480. e.user = new CIRCUser(this, null, e.params[2]);
  1481. if ("pendingWhoisLines" in this)
  1482. delete this.pendingWhoisLines;
  1483. e.destObject = this.parent;
  1484. e.set = "network";
  1485. }
  1486. /* ircu's 330 numeric ("X is logged in as Y") */
  1487. CIRCServer.prototype.on330 =
  1488. function serv_330(e)
  1489. {
  1490. e.user = new CIRCUser(this, null, e.params[2]);
  1491. e.destObject = this.parent;
  1492. e.set = "network";
  1493. }
  1494. /* TOPIC reply - no topic set */
  1495. CIRCServer.prototype.on331 =
  1496. function serv_331 (e)
  1497. {
  1498. e.channel = new CIRCChannel(this, null, e.params[2]);
  1499. e.channel.topic = "";
  1500. e.destObject = e.channel;
  1501. e.set = "channel";
  1502. return true;
  1503. }
  1504. /* TOPIC reply - topic set */
  1505. CIRCServer.prototype.on332 =
  1506. function serv_332 (e)
  1507. {
  1508. e.channel = new CIRCChannel(this, null, e.params[2]);
  1509. e.channel.topic = toUnicode(e.params[3], e.channel);
  1510. e.destObject = e.channel;
  1511. e.set = "channel";
  1512. return true;
  1513. }
  1514. /* topic information */
  1515. CIRCServer.prototype.on333 =
  1516. function serv_333 (e)
  1517. {
  1518. e.channel = new CIRCChannel(this, null, e.params[2]);
  1519. e.channel.topicBy = toUnicode(e.params[3], this);
  1520. e.channel.topicDate = new Date(Number(e.params[4]) * 1000);
  1521. e.destObject = e.channel;
  1522. e.set = "channel";
  1523. return true;
  1524. }
  1525. /* who reply */
  1526. CIRCServer.prototype.on352 =
  1527. function serv_352 (e)
  1528. {
  1529. e.userHasChanges = false;
  1530. if (this.LIGHTWEIGHT_WHO)
  1531. {
  1532. e.user = new CIRCUser(this, null, e.params[6]);
  1533. }
  1534. else
  1535. {
  1536. e.user = new CIRCUser(this, null, e.params[6], e.params[3], e.params[4]);
  1537. e.user.connectionHost = e.params[5];
  1538. if (8 in e.params)
  1539. {
  1540. var ary = e.params[8].match(/(?:(\d+)\s)?(.*)/);
  1541. e.user.hops = ary[1];
  1542. var desc = fromUnicode(ary[2], e.user);
  1543. if (e.user.desc != desc)
  1544. {
  1545. e.userHasChanges = true;
  1546. e.user.desc = desc;
  1547. }
  1548. }
  1549. }
  1550. var away = (e.params[7][0] == "G");
  1551. if (e.user.isAway != away)
  1552. {
  1553. e.userHasChanges = true;
  1554. e.user.isAway = away;
  1555. }
  1556. e.destObject = this.parent;
  1557. e.set = "network";
  1558. return true;
  1559. }
  1560. /* end of who */
  1561. CIRCServer.prototype.on315 =
  1562. function serv_315 (e)
  1563. {
  1564. e.user = new CIRCUser(this, null, e.params[2]);
  1565. e.destObject = this.parent;
  1566. e.set = "network";
  1567. return true;
  1568. }
  1569. /* names reply */
  1570. CIRCServer.prototype.on353 =
  1571. function serv_353 (e)
  1572. {
  1573. e.channel = new CIRCChannel(this, null, e.params[3]);
  1574. if (e.channel.usersStable)
  1575. {
  1576. e.channel.users = new Object();
  1577. e.channel.usersStable = false;
  1578. }
  1579. e.destObject = e.channel;
  1580. e.set = "channel";
  1581. var nicks = e.params[4].split (" ");
  1582. var mList = this.userModes;
  1583. for (var n in nicks)
  1584. {
  1585. var nick = nicks[n];
  1586. if (nick == "")
  1587. break;
  1588. var modes = new Array();
  1589. do
  1590. {
  1591. var found = false;
  1592. for (var m in mList)
  1593. {
  1594. if (nick[0] == mList[m].symbol)
  1595. {
  1596. nick = nick.substr(1);
  1597. modes.push(mList[m].mode);
  1598. found = true;
  1599. break;
  1600. }
  1601. }
  1602. } while (found && ("namesx" in this.supports) && this.supports.namesx);
  1603. new CIRCChanUser(e.channel, null, nick, modes, true);
  1604. }
  1605. return true;
  1606. }
  1607. /* end of names */
  1608. CIRCServer.prototype.on366 =
  1609. function serv_366 (e)
  1610. {
  1611. e.channel = new CIRCChannel(this, null, e.params[2]);
  1612. e.destObject = e.channel;
  1613. e.set = "channel";
  1614. e.channel.usersStable = true;
  1615. return true;
  1616. }
  1617. /* channel time stamp? */
  1618. CIRCServer.prototype.on329 =
  1619. function serv_329 (e)
  1620. {
  1621. e.channel = new CIRCChannel(this, null, e.params[2]);
  1622. e.destObject = e.channel;
  1623. e.set = "channel";
  1624. e.channel.timeStamp = new Date (Number(e.params[3]) * 1000);
  1625. return true;
  1626. }
  1627. /* channel mode reply */
  1628. CIRCServer.prototype.on324 =
  1629. function serv_324 (e)
  1630. {
  1631. e.channel = new CIRCChannel(this, null, e.params[2]);
  1632. e.destObject = this;
  1633. e.type = "chanmode";
  1634. e.destMethod = "onChanMode";
  1635. return true;
  1636. }
  1637. /* channel ban entry */
  1638. CIRCServer.prototype.on367 =
  1639. function serv_367(e)
  1640. {
  1641. e.channel = new CIRCChannel(this, null, e.params[2]);
  1642. e.destObject = e.channel;
  1643. e.set = "channel";
  1644. e.ban = e.params[3];
  1645. e.user = new CIRCUser(this, null, e.params[4]);
  1646. e.banTime = new Date (Number(e.params[5]) * 1000);
  1647. if (typeof e.channel.bans[e.ban] == "undefined")
  1648. {
  1649. e.channel.bans[e.ban] = {host: e.ban, user: e.user, time: e.banTime };
  1650. var ban_evt = new CEvent("channel", "ban", e.channel, "onBan");
  1651. ban_evt.channel = e.channel;
  1652. ban_evt.ban = e.ban;
  1653. ban_evt.source = e.user;
  1654. this.parent.eventPump.addEvent(ban_evt);
  1655. }
  1656. return true;
  1657. }
  1658. /* channel ban list end */
  1659. CIRCServer.prototype.on368 =
  1660. function serv_368(e)
  1661. {
  1662. e.channel = new CIRCChannel(this, null, e.params[2]);
  1663. e.destObject = e.channel;
  1664. e.set = "channel";
  1665. /* This flag is cleared in a timeout (which occurs right after the current
  1666. * message has been processed) so that the new event target (the channel)
  1667. * will still have the flag set when it executes.
  1668. */
  1669. if ("pendingBanList" in e.channel)
  1670. setTimeout(function() { delete e.channel.pendingBanList; }, 0);
  1671. return true;
  1672. }
  1673. /* channel except entry */
  1674. CIRCServer.prototype.on348 =
  1675. function serv_348(e)
  1676. {
  1677. e.channel = new CIRCChannel(this, null, e.params[2]);
  1678. e.destObject = e.channel;
  1679. e.set = "channel";
  1680. e.except = e.params[3];
  1681. e.user = new CIRCUser(this, null, e.params[4]);
  1682. e.exceptTime = new Date (Number(e.params[5]) * 1000);
  1683. if (typeof e.channel.excepts[e.except] == "undefined")
  1684. {
  1685. e.channel.excepts[e.except] = {host: e.except, user: e.user,
  1686. time: e.exceptTime };
  1687. }
  1688. return true;
  1689. }
  1690. /* channel except list end */
  1691. CIRCServer.prototype.on349 =
  1692. function serv_349(e)
  1693. {
  1694. e.channel = new CIRCChannel(this, null, e.params[2]);
  1695. e.destObject = e.channel;
  1696. e.set = "channel";
  1697. if ("pendingExceptList" in e.channel)
  1698. setTimeout(function (){ delete e.channel.pendingExceptList; }, 0);
  1699. return true;
  1700. }
  1701. /* don't have operator perms */
  1702. CIRCServer.prototype.on482 =
  1703. function serv_482(e)
  1704. {
  1705. e.channel = new CIRCChannel(this, null, e.params[2]);
  1706. e.destObject = e.channel;
  1707. e.set = "channel";
  1708. /* Some servers (e.g. Hybrid) don't let you get the except list without ops,
  1709. * so we might be waiting for this list forever otherwise.
  1710. */
  1711. if ("pendingExceptList" in e.channel)
  1712. setTimeout(function (){ delete e.channel.pendingExceptList; }, 0);
  1713. return true;
  1714. }
  1715. /* userhost reply */
  1716. CIRCServer.prototype.on302 =
  1717. function serv_302(e)
  1718. {
  1719. var list = e.params[2].split(/\s+/);
  1720. for (var i = 0; i < list.length; i++)
  1721. {
  1722. // <reply> ::= <nick>['*'] '=' <'+'|'-'><hostname>
  1723. // '*' == IRCop. '+' == here, '-' == away.
  1724. var data = list[i].match(/^(.*)(\*?)=([-+])(.*)@(.*)$/);
  1725. if (data)
  1726. this.addUser(data[1], data[4], data[5]);
  1727. }
  1728. e.destObject = this.parent;
  1729. e.set = "network";
  1730. return true;
  1731. }
  1732. /* user changed the mode */
  1733. CIRCServer.prototype.onMode =
  1734. function serv_mode (e)
  1735. {
  1736. e.destObject = this;
  1737. /* modes are not allowed in +channels -> no need to test that here.. */
  1738. if (arrayIndexOf(this.channelTypes, e.params[1][0]) != -1)
  1739. {
  1740. e.channel = new CIRCChannel(this, null, e.params[1]);
  1741. if ("user" in e && e.user)
  1742. e.user = new CIRCChanUser(e.channel, e.user.unicodeName);
  1743. e.type = "chanmode";
  1744. e.destMethod = "onChanMode";
  1745. }
  1746. else
  1747. {
  1748. e.type = "usermode";
  1749. e.destMethod = "onUserMode";
  1750. }
  1751. return true;
  1752. }
  1753. CIRCServer.prototype.onUserMode =
  1754. function serv_usermode (e)
  1755. {
  1756. e.user = new CIRCUser(this, null, e.params[1])
  1757. e.user.modestr = e.params[2];
  1758. e.destObject = this.parent;
  1759. e.set = "network";
  1760. // usermode usually happens on connect, after the MOTD, so it's a good
  1761. // place to kick off the lag timer.
  1762. this.updateLagTimer();
  1763. return true;
  1764. }
  1765. CIRCServer.prototype.onChanMode =
  1766. function serv_chanmode (e)
  1767. {
  1768. var modifier = "";
  1769. var params_eaten = 0;
  1770. var BASE_PARAM;
  1771. if (e.code.toUpperCase() == "MODE")
  1772. BASE_PARAM = 2;
  1773. else
  1774. if (e.code == "324")
  1775. BASE_PARAM = 3;
  1776. else
  1777. {
  1778. dd ("** INVALID CODE in ChanMode event **");
  1779. return false;
  1780. }
  1781. var mode_str = e.params[BASE_PARAM];
  1782. params_eaten++;
  1783. e.modeStr = mode_str;
  1784. e.usersAffected = new Array();
  1785. var nick;
  1786. var user;
  1787. var umList = this.userModes;
  1788. var cmList = this.channelModes;
  1789. var modeMap = this.canonicalChanModes;
  1790. var canonicalModeValue;
  1791. for (var i = 0; i < mode_str.length ; i++)
  1792. {
  1793. /* Take care of modifier first. */
  1794. if ((mode_str[i] == '+') || (mode_str[i] == '-'))
  1795. {
  1796. modifier = mode_str[i];
  1797. continue;
  1798. }
  1799. var done = false;
  1800. for (var m in umList)
  1801. {
  1802. if ((mode_str[i] == umList[m].mode) && (modifier != ""))
  1803. {
  1804. nick = e.params[BASE_PARAM + params_eaten];
  1805. user = new CIRCChanUser(e.channel, null, nick,
  1806. [ modifier + umList[m].mode ]);
  1807. params_eaten++;
  1808. e.usersAffected.push (user);
  1809. done = true;
  1810. break;
  1811. }
  1812. }
  1813. if (done)
  1814. continue;
  1815. // Update legacy canonical modes if necessary.
  1816. if (mode_str[i] in modeMap)
  1817. {
  1818. // Get the data in case we need it, but don't increment the counter.
  1819. var datacounter = BASE_PARAM + params_eaten;
  1820. var data = (datacounter in e.params) ? e.params[datacounter] : null;
  1821. canonicalModeValue = modeMap[mode_str[i]].getValue(modifier, data);
  1822. e.channel.mode[modeMap[mode_str[i]].name] = canonicalModeValue;
  1823. }
  1824. if (arrayContains(cmList.a, mode_str[i]))
  1825. {
  1826. var data = e.params[BASE_PARAM + params_eaten++];
  1827. if (modifier == "+")
  1828. {
  1829. e.channel.mode.modeA[data] = true;
  1830. }
  1831. else
  1832. {
  1833. if (data in e.channel.mode.modeA)
  1834. {
  1835. delete e.channel.mode.modeA[data];
  1836. }
  1837. else
  1838. {
  1839. dd("** Trying to remove channel mode '" + mode_str[i] +
  1840. "'/'" + data + "' which does not exist in list.");
  1841. }
  1842. }
  1843. }
  1844. else if (arrayContains(cmList.b, mode_str[i]))
  1845. {
  1846. var data = e.params[BASE_PARAM + params_eaten++];
  1847. if (modifier == "+")
  1848. {
  1849. e.channel.mode.modeB[mode_str[i]] = data;
  1850. }
  1851. else
  1852. {
  1853. // Save 'null' even though we have some data.
  1854. e.channel.mode.modeB[mode_str[i]] = null;
  1855. }
  1856. }
  1857. else if (arrayContains(cmList.c, mode_str[i]))
  1858. {
  1859. if (modifier == "+")
  1860. {
  1861. var data = e.params[BASE_PARAM + params_eaten++];
  1862. e.channel.mode.modeC[mode_str[i]] = data;
  1863. }
  1864. else
  1865. {
  1866. e.channel.mode.modeC[mode_str[i]] = null;
  1867. }
  1868. }
  1869. else if (arrayContains(cmList.d, mode_str[i]))
  1870. {
  1871. e.channel.mode.modeD[mode_str[i]] = (modifier == "+");
  1872. }
  1873. else
  1874. {
  1875. dd("** UNKNOWN mode symbol '" + mode_str[i] + "' in ChanMode event **");
  1876. }
  1877. }
  1878. e.destObject = e.channel;
  1879. e.set = "channel";
  1880. return true;
  1881. }
  1882. CIRCServer.prototype.onNick =
  1883. function serv_nick (e)
  1884. {
  1885. var newNick = e.params[1];
  1886. var newKey = this.toLowerCase(newNick);
  1887. var oldKey = e.user.canonicalName;
  1888. var ev;
  1889. renameProperty (this.users, oldKey, newKey);
  1890. e.oldNick = e.user.unicodeName;
  1891. e.user.changeNick(toUnicode(newNick, this));
  1892. for (var c in this.channels)
  1893. {
  1894. if (this.channels[c].active &&
  1895. ((oldKey in this.channels[c].users) || e.user == this.me))
  1896. {
  1897. var cuser = this.channels[c].users[oldKey];
  1898. renameProperty (this.channels[c].users, oldKey, newKey);
  1899. // User must be a channel user, update sort name for userlist,
  1900. // before we route the event further:
  1901. cuser.updateSortName();
  1902. ev = new CEvent ("channel", "nick", this.channels[c], "onNick");
  1903. ev.channel = this.channels[c];
  1904. ev.user = cuser;
  1905. ev.server = this;
  1906. ev.oldNick = e.oldNick;
  1907. this.parent.eventPump.routeEvent(ev);
  1908. }
  1909. }
  1910. if (e.user == this.me)
  1911. {
  1912. /* if it was me, tell the network about the nick change as well */
  1913. ev = new CEvent ("network", "nick", this.parent, "onNick");
  1914. ev.user = e.user;
  1915. ev.server = this;
  1916. ev.oldNick = e.oldNick;
  1917. this.parent.eventPump.routeEvent(ev);
  1918. }
  1919. e.destObject = e.user;
  1920. e.set = "user";
  1921. return true;
  1922. }
  1923. CIRCServer.prototype.onQuit =
  1924. function serv_quit (e)
  1925. {
  1926. var reason = e.decodeParam(1);
  1927. for (var c in e.server.channels)
  1928. {
  1929. if (e.server.channels[c].active &&
  1930. e.user.canonicalName in e.server.channels[c].users)
  1931. {
  1932. var ev = new CEvent ("channel", "quit", e.server.channels[c],
  1933. "onQuit");
  1934. ev.user = e.server.channels[c].users[e.user.canonicalName];
  1935. ev.channel = e.server.channels[c];
  1936. ev.server = ev.channel.parent;
  1937. ev.reason = reason;
  1938. this.parent.eventPump.routeEvent(ev);
  1939. delete e.server.channels[c].users[e.user.canonicalName];
  1940. }
  1941. }
  1942. this.users[e.user.canonicalName].lastQuitMessage = reason;
  1943. this.users[e.user.canonicalName].lastQuitDate = new Date();
  1944. // 0 == prune onQuit.
  1945. if (this.PRUNE_OLD_USERS == 0)
  1946. delete this.users[e.user.canonicalName];
  1947. e.reason = reason;
  1948. e.destObject = e.user;
  1949. e.set = "user";
  1950. return true;
  1951. }
  1952. CIRCServer.prototype.onPart =
  1953. function serv_part (e)
  1954. {
  1955. e.channel = new CIRCChannel(this, null, e.params[1]);
  1956. e.reason = (e.params.length > 2) ? e.decodeParam(2, e.channel) : "";
  1957. e.user = new CIRCChanUser(e.channel, e.user.unicodeName);
  1958. if (userIsMe(e.user))
  1959. {
  1960. e.channel.active = false;
  1961. e.channel.joined = false;
  1962. }
  1963. e.channel.removeUser(e.user.encodedName);
  1964. e.destObject = e.channel;
  1965. e.set = "channel";
  1966. return true;
  1967. }
  1968. CIRCServer.prototype.onKick =
  1969. function serv_kick (e)
  1970. {
  1971. e.channel = new CIRCChannel(this, null, e.params[1]);
  1972. e.lamer = new CIRCChanUser(e.channel, null, e.params[2]);
  1973. delete e.channel.users[e.lamer.canonicalName];
  1974. if (userIsMe(e.lamer))
  1975. {
  1976. e.channel.active = false;
  1977. e.channel.joined = false;
  1978. }
  1979. e.reason = e.decodeParam(3, e.channel);
  1980. e.destObject = e.channel;
  1981. e.set = "channel";
  1982. return true;
  1983. }
  1984. CIRCServer.prototype.onJoin =
  1985. function serv_join(e)
  1986. {
  1987. e.channel = new CIRCChannel(this, null, e.params[1]);
  1988. // Passing undefined here because CIRCChanUser doesn't like "null"
  1989. e.user = new CIRCChanUser(e.channel, e.user.unicodeName, null,
  1990. undefined, true);
  1991. if (userIsMe(e.user))
  1992. {
  1993. var delayFn1 = function(t) {
  1994. if (!e.channel.active)
  1995. return;
  1996. // Give us the channel mode!
  1997. e.server.sendData("MODE " + e.channel.encodedName + "\n");
  1998. };
  1999. // Between 1s - 3s.
  2000. setTimeout(delayFn1, 1000 + 2000 * Math.random(), this);
  2001. var delayFn2 = function(t) {
  2002. if (!e.channel.active)
  2003. return;
  2004. // Get a full list of bans and exceptions, if supported.
  2005. if (arrayContains(t.channelModes.a, "b"))
  2006. {
  2007. e.server.sendData("MODE " + e.channel.encodedName + " +b\n");
  2008. e.channel.pendingBanList = true;
  2009. }
  2010. if (arrayContains(t.channelModes.a, "e"))
  2011. {
  2012. e.server.sendData("MODE " + e.channel.encodedName + " +e\n");
  2013. e.channel.pendingExceptList = true;
  2014. }
  2015. };
  2016. // Between 10s - 20s.
  2017. setTimeout(delayFn2, 10000 + 10000 * Math.random(), this);
  2018. /* Clean up the topic, since servers don't always send RPL_NOTOPIC
  2019. * (no topic set) when joining a channel without a topic. In fact,
  2020. * the RFC even fails to mention sending a RPL_NOTOPIC after a join!
  2021. */
  2022. e.channel.topic = "";
  2023. e.channel.topicBy = null;
  2024. e.channel.topicDate = null;
  2025. // And we're in!
  2026. e.channel.active = true;
  2027. e.channel.joined = true;
  2028. }
  2029. e.destObject = e.channel;
  2030. e.set = "channel";
  2031. return true;
  2032. }
  2033. CIRCServer.prototype.onPing =
  2034. function serv_ping (e)
  2035. {
  2036. /* non-queued send, so we can calcualte lag */
  2037. this.connection.sendData("PONG :" + e.params[1] + "\n");
  2038. this.updateLagTimer();
  2039. e.destObject = this.parent;
  2040. e.set = "network";
  2041. return true;
  2042. }
  2043. CIRCServer.prototype.onPong =
  2044. function serv_pong (e)
  2045. {
  2046. if (e.params[2] != "LAGTIMER")
  2047. return true;
  2048. if (this.lastPingSent)
  2049. this.lag = (new Date() - this.lastPingSent) / 1000;
  2050. this.lastPingSent = null;
  2051. e.destObject = this.parent;
  2052. e.set = "network";
  2053. return true;
  2054. }
  2055. CIRCServer.prototype.onInvite =
  2056. function serv_invite(e)
  2057. {
  2058. e.channel = new CIRCChannel(this, null, e.params[2]);
  2059. e.destObject = this.parent;
  2060. e.set = "network";
  2061. }
  2062. CIRCServer.prototype.onNotice =
  2063. CIRCServer.prototype.onPrivmsg =
  2064. function serv_notice_privmsg (e)
  2065. {
  2066. var targetName = e.params[1];
  2067. if (this.userModes)
  2068. {
  2069. // Strip off one (and only one) user mode prefix.
  2070. for (var i = 0; i < this.userModes.length; i++)
  2071. {
  2072. if (targetName[0] == this.userModes[i].symbol)
  2073. {
  2074. e.msgPrefix = this.userModes[i];
  2075. targetName = targetName.substr(1);
  2076. break;
  2077. }
  2078. }
  2079. }
  2080. /* setting replyTo provides a standard place to find the target for */
  2081. /* replies associated with this event. */
  2082. if (arrayIndexOf(this.channelTypes, targetName[0]) != -1)
  2083. {
  2084. e.channel = new CIRCChannel(this, null, targetName);
  2085. if ("user" in e)
  2086. e.user = new CIRCChanUser(e.channel, e.user.unicodeName);
  2087. e.replyTo = e.channel;
  2088. e.set = "channel";
  2089. }
  2090. else if (!("user" in e))
  2091. {
  2092. e.set = "network";
  2093. e.destObject = this.parent;
  2094. return true;
  2095. }
  2096. else
  2097. {
  2098. e.set = "user";
  2099. e.replyTo = e.user; /* send replies to the user who sent the message */
  2100. }
  2101. // The CAPAB IDENTIFY-MSG stuff for freenode
  2102. if (this.capab.identifyMsg)
  2103. {
  2104. e.identifyMsg = false;
  2105. var flag = e.params[2].substring(0,1);
  2106. if (flag == "+")
  2107. {
  2108. e.identifyMsg = true;
  2109. e.params[2] = e.params[2].substring(1);
  2110. }
  2111. else if (flag == "-")
  2112. {
  2113. e.params[2] = e.params[2].substring(1);
  2114. }
  2115. else
  2116. {
  2117. // Just print to console on failure - or we'd spam the user
  2118. dd("Warning: IDENTIFY-MSG is on, but there's no message flags");
  2119. }
  2120. }
  2121. if (e.params[2].search (/^\x01[^ ]+.*\x01$/) != -1)
  2122. {
  2123. if (e.code == "NOTICE")
  2124. {
  2125. e.type = "ctcp-reply";
  2126. e.destMethod = "onCTCPReply";
  2127. }
  2128. else // e.code == "PRIVMSG"
  2129. {
  2130. e.type = "ctcp";
  2131. e.destMethod = "onCTCP";
  2132. }
  2133. e.set = "server";
  2134. e.destObject = this;
  2135. }
  2136. else
  2137. {
  2138. e.msg = e.decodeParam(2, e.replyTo);
  2139. e.destObject = e.replyTo;
  2140. }
  2141. return true;
  2142. }
  2143. CIRCServer.prototype.onWallops =
  2144. function serv_wallops(e)
  2145. {
  2146. if (("user" in e) && e.user)
  2147. {
  2148. e.msg = e.decodeParam(1, e.user);
  2149. e.replyTo = e.user;
  2150. }
  2151. else
  2152. {
  2153. e.msg = e.decodeParam(1);
  2154. e.replyTo = this;
  2155. }
  2156. e.destObject = this.parent;
  2157. e.set = "network";
  2158. return true;
  2159. }
  2160. CIRCServer.prototype.onCTCPReply =
  2161. function serv_ctcpr (e)
  2162. {
  2163. var ary = e.params[2].match (/^\x01([^ ]+) ?(.*)\x01$/i);
  2164. if (ary == null)
  2165. return false;
  2166. e.CTCPData = ary[2] ? ary[2] : "";
  2167. e.CTCPCode = ary[1].toLowerCase();
  2168. e.type = "ctcp-reply-" + e.CTCPCode;
  2169. e.destMethod = "onCTCPReply" + ary[1][0].toUpperCase() +
  2170. ary[1].substr (1, ary[1].length).toLowerCase();
  2171. if (typeof this[e.destMethod] != "function")
  2172. { /* if there's no place to land the event here, try to forward it */
  2173. e.destObject = this.parent;
  2174. e.set = "network";
  2175. if (typeof e.destObject[e.destMethod] != "function")
  2176. { /* if there's no place to forward it, send it to unknownCTCP */
  2177. e.type = "unk-ctcp-reply";
  2178. e.destMethod = "onUnknownCTCPReply";
  2179. if (e.destMethod in this)
  2180. {
  2181. e.set = "server";
  2182. e.destObject = this;
  2183. }
  2184. else
  2185. {
  2186. e.set = "network";
  2187. e.destObject = this.parent;
  2188. }
  2189. }
  2190. }
  2191. else
  2192. e.destObject = this;
  2193. return true;
  2194. }
  2195. CIRCServer.prototype.onCTCP =
  2196. function serv_ctcp (e)
  2197. {
  2198. var ary = e.params[2].match (/^\x01([^ ]+) ?(.*)\x01$/i);
  2199. if (ary == null)
  2200. return false;
  2201. e.CTCPData = ary[2] ? ary[2] : "";
  2202. e.CTCPCode = ary[1].toLowerCase();
  2203. if (e.CTCPCode.search (/^reply/i) == 0)
  2204. {
  2205. dd ("dropping spoofed reply.");
  2206. return false;
  2207. }
  2208. e.CTCPCode = toUnicode(e.CTCPCode, e.replyTo);
  2209. e.CTCPData = toUnicode(e.CTCPData, e.replyTo);
  2210. e.type = "ctcp-" + e.CTCPCode;
  2211. e.destMethod = "onCTCP" + ary[1][0].toUpperCase() +
  2212. ary[1].substr (1, ary[1].length).toLowerCase();
  2213. if (typeof this[e.destMethod] != "function")
  2214. { /* if there's no place to land the event here, try to forward it */
  2215. e.destObject = e.replyTo;
  2216. e.set = (e.replyTo == e.user) ? "user" : "channel";
  2217. if (typeof e.replyTo[e.destMethod] != "function")
  2218. { /* if there's no place to forward it, send it to unknownCTCP */
  2219. e.type = "unk-ctcp";
  2220. e.destMethod = "onUnknownCTCP";
  2221. }
  2222. }
  2223. else
  2224. e.destObject = this;
  2225. return true;
  2226. }
  2227. CIRCServer.prototype.onCTCPClientinfo =
  2228. function serv_ccinfo (e)
  2229. {
  2230. var clientinfo = new Array();
  2231. if (e.CTCPData)
  2232. {
  2233. var cmdName = "onCTCP" + e.CTCPData[0].toUpperCase() +
  2234. e.CTCPData.substr (1, e.CTCPData.length).toLowerCase();
  2235. var helpName = cmdName.replace(/^onCTCP/, "CTCPHelp");
  2236. // Check we support the command.
  2237. if (cmdName in this)
  2238. {
  2239. // Do we have help for it?
  2240. if (helpName in this)
  2241. {
  2242. var msg;
  2243. if (typeof this[helpName] == "function")
  2244. msg = this[helpName]();
  2245. else
  2246. msg = this[helpName];
  2247. e.user.ctcp("CLIENTINFO", msg, "NOTICE");
  2248. }
  2249. else
  2250. {
  2251. e.user.ctcp("CLIENTINFO",
  2252. getMsg(MSG_ERR_NO_CTCP_HELP, e.CTCPData), "NOTICE");
  2253. }
  2254. }
  2255. else
  2256. {
  2257. e.user.ctcp("CLIENTINFO",
  2258. getMsg(MSG_ERR_NO_CTCP_CMD, e.CTCPData), "NOTICE");
  2259. }
  2260. return true;
  2261. }
  2262. for (var fname in this)
  2263. {
  2264. var ary = fname.match(/^onCTCP(.+)/);
  2265. if (ary && ary[1].search(/^Reply/) == -1)
  2266. clientinfo.push (ary[1].toUpperCase());
  2267. }
  2268. e.user.ctcp("CLIENTINFO", clientinfo.join(" "), "NOTICE");
  2269. return true;
  2270. }
  2271. CIRCServer.prototype.onCTCPAction =
  2272. function serv_cact (e)
  2273. {
  2274. e.destObject = e.replyTo;
  2275. e.set = (e.replyTo == e.user) ? "user" : "channel";
  2276. }
  2277. CIRCServer.prototype.onCTCPFinger =
  2278. function serv_cfinger (e)
  2279. {
  2280. e.user.ctcp("FINGER", this.parent.INITIAL_DESC, "NOTICE");
  2281. return true;
  2282. }
  2283. CIRCServer.prototype.onCTCPTime =
  2284. function serv_cping (e)
  2285. {
  2286. e.user.ctcp("TIME", new Date(), "NOTICE");
  2287. return true;
  2288. }
  2289. CIRCServer.prototype.onCTCPVersion =
  2290. function serv_cver (e)
  2291. {
  2292. var lines = e.server.VERSION_RPLY.split ("\n");
  2293. for (var i in lines)
  2294. e.user.ctcp("VERSION", lines[i], "NOTICE");
  2295. e.destObject = e.replyTo;
  2296. e.set = (e.replyTo == e.user) ? "user" : "channel";
  2297. return true;
  2298. }
  2299. CIRCServer.prototype.onCTCPSource =
  2300. function serv_csrc (e)
  2301. {
  2302. e.user.ctcp("SOURCE", this.SOURCE_RPLY, "NOTICE");
  2303. return true;
  2304. }
  2305. CIRCServer.prototype.onCTCPOs =
  2306. function serv_os(e)
  2307. {
  2308. e.user.ctcp("OS", this.OS_RPLY, "NOTICE");
  2309. return true;
  2310. }
  2311. CIRCServer.prototype.onCTCPHost =
  2312. function serv_host(e)
  2313. {
  2314. e.user.ctcp("HOST", this.HOST_RPLY, "NOTICE");
  2315. return true;
  2316. }
  2317. CIRCServer.prototype.onCTCPPing =
  2318. function serv_cping (e)
  2319. {
  2320. /* non-queued send */
  2321. this.connection.sendData("NOTICE " + e.user.encodedName + " :\01PING " +
  2322. e.CTCPData + "\01\n");
  2323. e.destObject = e.replyTo;
  2324. e.set = (e.replyTo == e.user) ? "user" : "channel";
  2325. return true;
  2326. }
  2327. CIRCServer.prototype.onCTCPDcc =
  2328. function serv_dcc (e)
  2329. {
  2330. var ary = e.CTCPData.match (/([^ ]+)? ?(.*)/);
  2331. e.DCCData = ary[2];
  2332. e.type = "dcc-" + ary[1].toLowerCase();
  2333. e.destMethod = "onDCC" + ary[1][0].toUpperCase() +
  2334. ary[1].substr (1, ary[1].length).toLowerCase();
  2335. if (typeof this[e.destMethod] != "function")
  2336. { /* if there's no place to land the event here, try to forward it */
  2337. e.destObject = e.replyTo;
  2338. e.set = (e.replyTo == e.user) ? "user" : "channel";
  2339. }
  2340. else
  2341. e.destObject = this;
  2342. return true;
  2343. }
  2344. CIRCServer.prototype.onDCCChat =
  2345. function serv_dccchat (e)
  2346. {
  2347. var ary = e.DCCData.match (/(chat) (\d+) (\d+)/i);
  2348. if (ary == null)
  2349. return false;
  2350. e.id = ary[2];
  2351. // Checky longword --> dotted IP conversion.
  2352. var host = Number(e.id).toString(16);
  2353. e.host = Number("0x" + host.substr(0, 2)) + "." +
  2354. Number("0x" + host.substr(2, 2)) + "." +
  2355. Number("0x" + host.substr(4, 2)) + "." +
  2356. Number("0x" + host.substr(6, 2));
  2357. e.port = Number(ary[3]);
  2358. e.destObject = e.replyTo;
  2359. e.set = (e.replyTo == e.user) ? "user" : "channel";
  2360. return true;
  2361. }
  2362. CIRCServer.prototype.onDCCSend =
  2363. function serv_dccsend (e)
  2364. {
  2365. var ary = e.DCCData.match(/([^ ]+) (\d+) (\d+) (\d+)/);
  2366. /* Just for mIRC: filenames with spaces may be enclosed in double-quotes.
  2367. * (though by default it replaces spaces with underscores, but we might as
  2368. * well cope). */
  2369. if ((ary[1][0] == '"') || (ary[1][ary[1].length - 1] == '"'))
  2370. ary = e.DCCData.match(/"(.+)" (\d+) (\d+) (\d+)/);
  2371. if (ary == null)
  2372. return false;
  2373. e.file = ary[1];
  2374. e.id = ary[2];
  2375. // Cheeky longword --> dotted IP conversion.
  2376. var host = Number(e.id).toString(16);
  2377. e.host = Number("0x" + host.substr(0, 2)) + "." +
  2378. Number("0x" + host.substr(2, 2)) + "." +
  2379. Number("0x" + host.substr(4, 2)) + "." +
  2380. Number("0x" + host.substr(6, 2));
  2381. e.port = Number(ary[3]);
  2382. e.size = Number(ary[4]);
  2383. e.destObject = e.replyTo;
  2384. e.set = (e.replyTo == e.user) ? "user" : "channel";
  2385. return true;
  2386. }
  2387. /*
  2388. * channel
  2389. */
  2390. function CIRCChannel(parent, unicodeName, encodedName)
  2391. {
  2392. // Both unicodeName and encodedName are optional, but at least one must be
  2393. // present.
  2394. if (!encodedName && !unicodeName)
  2395. throw "Hey! Come on, I need either an encoded or a Unicode name.";
  2396. if (!encodedName)
  2397. encodedName = fromUnicode(unicodeName, parent);
  2398. var canonicalName = parent.toLowerCase(encodedName);
  2399. if (canonicalName in parent.channels)
  2400. return parent.channels[canonicalName];
  2401. this.parent = parent;
  2402. this.encodedName = encodedName;
  2403. this.canonicalName = canonicalName;
  2404. this.unicodeName = unicodeName || toUnicode(encodedName, this);
  2405. this.viewName = this.unicodeName;
  2406. this.users = new Object();
  2407. this.bans = new Object();
  2408. this.excepts = new Object();
  2409. this.mode = new CIRCChanMode(this);
  2410. this.usersStable = true;
  2411. /* These next two flags represent a subtle difference in state:
  2412. * active - in the channel, from the server's point of view.
  2413. * joined - in the channel, from the user's point of view.
  2414. * e.g. parting the channel clears both, but being disconnected only
  2415. * clears |active| - the user still wants to be in the channel, even
  2416. * though they aren't physically able to until we've reconnected.
  2417. */
  2418. this.active = false;
  2419. this.joined = false;
  2420. this.parent.channels[this.canonicalName] = this;
  2421. if ("onInit" in this)
  2422. this.onInit();
  2423. return this;
  2424. }
  2425. CIRCChannel.prototype.TYPE = "IRCChannel";
  2426. CIRCChannel.prototype.topic = "";
  2427. CIRCChannel.prototype.getURL =
  2428. function chan_geturl()
  2429. {
  2430. var target = this.encodedName;
  2431. var flags = this.mode.key ? ["needkey"] : [];
  2432. if ((target[0] == "#") && (target.length > 1) &&
  2433. arrayIndexOf(this.parent.channelTypes, target[1]) == -1)
  2434. {
  2435. /* First character is "#" (which we're allowed to omit), and the
  2436. * following character is NOT a valid prefix, so it's safe to remove.
  2437. */
  2438. target = target.substr(1);
  2439. }
  2440. return this.parent.parent.getURL(target, flags);
  2441. }
  2442. CIRCChannel.prototype.rehome =
  2443. function chan_rehome(newParent)
  2444. {
  2445. delete this.parent.channels[this.canonicalName];
  2446. this.parent = newParent;
  2447. this.parent.channels[this.canonicalName] = this;
  2448. }
  2449. CIRCChannel.prototype.addUser =
  2450. function chan_adduser (unicodeName, modes)
  2451. {
  2452. return new CIRCChanUser(this, unicodeName, null, modes);
  2453. }
  2454. CIRCChannel.prototype.getUser =
  2455. function chan_getuser(nick)
  2456. {
  2457. // Try assuming it's an encodedName first.
  2458. nick = this.parent.toLowerCase(nick);
  2459. if (nick in this.users)
  2460. return this.users[nick];
  2461. // Ok, failed, so try assuming it's a unicodeName.
  2462. nick = this.parent.toLowerCase(fromUnicode(nick, this.parent));
  2463. if (nick in this.users)
  2464. return this.users[nick];
  2465. return null;
  2466. }
  2467. CIRCChannel.prototype.removeUser =
  2468. function chan_removeuser(nick)
  2469. {
  2470. // Try assuming it's an encodedName first.
  2471. nick = this.parent.toLowerCase(nick);
  2472. if (nick in this.users)
  2473. delete this.users[nick]; // see ya
  2474. // Ok, failed, so try assuming it's a unicodeName.
  2475. nick = this.parent.toLowerCase(fromUnicode(nick, this.parent));
  2476. if (nick in this.users)
  2477. delete this.users[nick];
  2478. }
  2479. CIRCChannel.prototype.getUsersLength =
  2480. function chan_userslen (mode)
  2481. {
  2482. var i = 0;
  2483. var p;
  2484. this.opCount = 0;
  2485. this.halfopCount = 0;
  2486. this.voiceCount = 0;
  2487. if (typeof mode == "undefined")
  2488. {
  2489. for (p in this.users)
  2490. {
  2491. if (this.users[p].isOp)
  2492. this.opCount++;
  2493. if (this.users[p].isHalfOp)
  2494. this.halfopCount++;
  2495. if (this.users[p].isVoice)
  2496. this.voiceCount++;
  2497. i++;
  2498. }
  2499. }
  2500. else
  2501. {
  2502. for (p in this.users)
  2503. if (arrayContains(this.users[p].modes, mode))
  2504. i++;
  2505. }
  2506. return i;
  2507. }
  2508. CIRCChannel.prototype.iAmOp =
  2509. function chan_amop()
  2510. {
  2511. return this.active && this.users[this.parent.me.canonicalName].isOp;
  2512. }
  2513. CIRCChannel.prototype.iAmHalfOp =
  2514. function chan_amhalfop()
  2515. {
  2516. return this.active && this.users[this.parent.me.canonicalName].isHalfOp;
  2517. }
  2518. CIRCChannel.prototype.iAmVoice =
  2519. function chan_amvoice()
  2520. {
  2521. return this.active && this.users[this.parent.me.canonicalName].isVoice;
  2522. }
  2523. CIRCChannel.prototype.setTopic =
  2524. function chan_topic (str)
  2525. {
  2526. this.parent.sendData ("TOPIC " + this.encodedName + " :" +
  2527. fromUnicode(str, this) + "\n");
  2528. }
  2529. CIRCChannel.prototype.say =
  2530. function chan_say (msg)
  2531. {
  2532. this.parent.sayTo(this.encodedName, fromUnicode(msg, this));
  2533. }
  2534. CIRCChannel.prototype.act =
  2535. function chan_say (msg)
  2536. {
  2537. this.parent.actTo(this.encodedName, fromUnicode(msg, this));
  2538. }
  2539. CIRCChannel.prototype.notice =
  2540. function chan_notice (msg)
  2541. {
  2542. this.parent.noticeTo(this.encodedName, fromUnicode(msg, this));
  2543. }
  2544. CIRCChannel.prototype.ctcp =
  2545. function chan_ctcpto (code, msg, type)
  2546. {
  2547. msg = msg || "";
  2548. type = type || "PRIVMSG";
  2549. this.parent.ctcpTo(this.encodedName, fromUnicode(code, this),
  2550. fromUnicode(msg, this), type);
  2551. }
  2552. CIRCChannel.prototype.join =
  2553. function chan_join (key)
  2554. {
  2555. if (!key)
  2556. key = "";
  2557. this.parent.sendData ("JOIN " + this.encodedName + " " + key + "\n");
  2558. return true;
  2559. }
  2560. CIRCChannel.prototype.part =
  2561. function chan_part (reason)
  2562. {
  2563. if (!reason)
  2564. reason = "";
  2565. this.parent.sendData ("PART " + this.encodedName + " :" +
  2566. fromUnicode(reason, this) + "\n");
  2567. this.users = new Object();
  2568. return true;
  2569. }
  2570. /**
  2571. * Invites a user to a channel.
  2572. *
  2573. * @param nick the user name to invite.
  2574. */
  2575. CIRCChannel.prototype.invite =
  2576. function chan_inviteuser (nick)
  2577. {
  2578. var rawNick = fromUnicode(nick, this.parent);
  2579. this.parent.sendData("INVITE " + rawNick + " " + this.encodedName + "\n");
  2580. return true;
  2581. }
  2582. CIRCChannel.prototype.findUsers =
  2583. function chan_findUsers(mask)
  2584. {
  2585. var ary = [];
  2586. var unchecked = 0;
  2587. mask = getHostmaskParts(mask);
  2588. for (var nick in this.users)
  2589. {
  2590. var user = this.users[nick];
  2591. if (!user.host || !user.name)
  2592. unchecked++;
  2593. else if (hostmaskMatches(user, mask))
  2594. ary.push(user);
  2595. }
  2596. return { users: ary, unchecked: unchecked };
  2597. }
  2598. /*
  2599. * channel mode
  2600. */
  2601. function CIRCChanMode (parent)
  2602. {
  2603. this.parent = parent;
  2604. this.modeA = new Object();
  2605. this.modeB = new Object();
  2606. this.modeC = new Object();
  2607. this.modeD = new Object();
  2608. this.invite = false;
  2609. this.moderated = false;
  2610. this.publicMessages = true;
  2611. this.publicTopic = true;
  2612. this.secret = false;
  2613. this.pvt = false;
  2614. this.key = "";
  2615. this.limit = -1;
  2616. }
  2617. CIRCChanMode.prototype.TYPE = "IRCChanMode";
  2618. CIRCChanMode.prototype.getModeStr =
  2619. function chan_modestr (f)
  2620. {
  2621. var str = "";
  2622. var modeCparams = "";
  2623. /* modeA are 'list' ones, and so should not be shown.
  2624. * modeB are 'param' ones, like +k key, so we wont show them either.
  2625. * modeC are 'on-param' ones, like +l limit, which we will show.
  2626. * modeD are 'boolean' ones, which we will definitely show.
  2627. */
  2628. // Add modeD:
  2629. for (var m in this.modeD)
  2630. {
  2631. if (this.modeD[m])
  2632. str += m;
  2633. }
  2634. // Add modeC, save parameters for adding all the way at the end:
  2635. for (var m in this.modeC)
  2636. {
  2637. if (this.modeC[m])
  2638. {
  2639. str += m;
  2640. modeCparams += " " + this.modeC[m];
  2641. }
  2642. }
  2643. // Add parameters:
  2644. if (str)
  2645. str = "+" + str + modeCparams;
  2646. return str;
  2647. }
  2648. CIRCChanMode.prototype.setMode =
  2649. function chanm_mode (modestr)
  2650. {
  2651. this.parent.parent.sendData ("MODE " + this.parent.encodedName + " " +
  2652. modestr + "\n");
  2653. return true;
  2654. }
  2655. CIRCChanMode.prototype.setLimit =
  2656. function chanm_limit (n)
  2657. {
  2658. if ((typeof n == "undefined") || (n <= 0))
  2659. {
  2660. this.parent.parent.sendData("MODE " + this.parent.encodedName +
  2661. " -l\n");
  2662. }
  2663. else
  2664. {
  2665. this.parent.parent.sendData("MODE " + this.parent.encodedName + " +l " +
  2666. Number(n) + "\n");
  2667. }
  2668. return true;
  2669. }
  2670. CIRCChanMode.prototype.lock =
  2671. function chanm_lock (k)
  2672. {
  2673. this.parent.parent.sendData("MODE " + this.parent.encodedName + " +k " +
  2674. k + "\n");
  2675. return true;
  2676. }
  2677. CIRCChanMode.prototype.unlock =
  2678. function chan_unlock (k)
  2679. {
  2680. this.parent.parent.sendData("MODE " + this.parent.encodedName + " -k " +
  2681. k + "\n");
  2682. return true;
  2683. }
  2684. CIRCChanMode.prototype.setModerated =
  2685. function chan_moderate (f)
  2686. {
  2687. var modifier = (f) ? "+" : "-";
  2688. this.parent.parent.sendData("MODE " + this.parent.encodedName + " " +
  2689. modifier + "m\n");
  2690. return true;
  2691. }
  2692. CIRCChanMode.prototype.setPublicMessages =
  2693. function chan_pmessages (f)
  2694. {
  2695. var modifier = (f) ? "-" : "+";
  2696. this.parent.parent.sendData("MODE " + this.parent.encodedName + " " +
  2697. modifier + "n\n");
  2698. return true;
  2699. }
  2700. CIRCChanMode.prototype.setPublicTopic =
  2701. function chan_ptopic (f)
  2702. {
  2703. var modifier = (f) ? "-" : "+";
  2704. this.parent.parent.sendData("MODE " + this.parent.encodedName + " " +
  2705. modifier + "t\n");
  2706. return true;
  2707. }
  2708. CIRCChanMode.prototype.setInvite =
  2709. function chan_invite (f)
  2710. {
  2711. var modifier = (f) ? "+" : "-";
  2712. this.parent.parent.sendData("MODE " + this.parent.encodedName + " " +
  2713. modifier + "i\n");
  2714. return true;
  2715. }
  2716. CIRCChanMode.prototype.setPvt =
  2717. function chan_pvt (f)
  2718. {
  2719. var modifier = (f) ? "+" : "-";
  2720. this.parent.parent.sendData("MODE " + this.parent.encodedName + " " +
  2721. modifier + "p\n");
  2722. return true;
  2723. }
  2724. CIRCChanMode.prototype.setSecret =
  2725. function chan_secret (f)
  2726. {
  2727. var modifier = (f) ? "+" : "-";
  2728. this.parent.parent.sendData("MODE " + this.parent.encodedName + " " +
  2729. modifier + "s\n");
  2730. return true;
  2731. }
  2732. /*
  2733. * user
  2734. */
  2735. function CIRCUser(parent, unicodeName, encodedName, name, host)
  2736. {
  2737. // Both unicodeName and encodedName are optional, but at least one must be
  2738. // present.
  2739. if (!encodedName && !unicodeName)
  2740. throw "Hey! Come on, I need either an encoded or a Unicode name.";
  2741. if (!encodedName)
  2742. encodedName = fromUnicode(unicodeName, parent);
  2743. var canonicalName = parent.toLowerCase(encodedName);
  2744. if (canonicalName in parent.users)
  2745. {
  2746. var existingUser = parent.users[canonicalName];
  2747. if (name)
  2748. existingUser.name = name;
  2749. if (host)
  2750. existingUser.host = host;
  2751. return existingUser;
  2752. }
  2753. this.parent = parent;
  2754. this.encodedName = encodedName;
  2755. this.canonicalName = canonicalName;
  2756. this.unicodeName = unicodeName || toUnicode(encodedName, this.parent);
  2757. this.viewName = this.unicodeName;
  2758. this.name = name;
  2759. this.host = host;
  2760. this.desc = "";
  2761. this.connectionHost = null;
  2762. this.isAway = false;
  2763. this.modestr = this.parent.parent.INITIAL_UMODE;
  2764. this.parent.users[this.canonicalName] = this;
  2765. if ("onInit" in this)
  2766. this.onInit();
  2767. return this;
  2768. }
  2769. CIRCUser.prototype.TYPE = "IRCUser";
  2770. CIRCUser.prototype.getURL =
  2771. function usr_geturl()
  2772. {
  2773. return this.parent.parent.getURL(this.encodedName, ["isnick"]);
  2774. }
  2775. CIRCUser.prototype.rehome =
  2776. function usr_rehome(newParent)
  2777. {
  2778. delete this.parent.users[this.canonicalName];
  2779. this.parent = newParent;
  2780. this.parent.users[this.canonicalName] = this;
  2781. }
  2782. CIRCUser.prototype.changeNick =
  2783. function usr_changenick(unicodeName)
  2784. {
  2785. this.unicodeName = unicodeName;
  2786. this.viewName = this.unicodeName;
  2787. this.encodedName = fromUnicode(this.unicodeName, this.parent);
  2788. this.canonicalName = this.parent.toLowerCase(this.encodedName);
  2789. }
  2790. CIRCUser.prototype.getHostMask =
  2791. function usr_hostmask (pfx)
  2792. {
  2793. pfx = (typeof pfx != "undefined") ? pfx : "*!" + this.name + "@*.";
  2794. var idx = this.host.indexOf(".");
  2795. if (idx == -1)
  2796. return pfx + this.host;
  2797. return (pfx + this.host.substr(idx + 1, this.host.length));
  2798. }
  2799. CIRCUser.prototype.getBanMask =
  2800. function usr_banmask()
  2801. {
  2802. if (!this.host)
  2803. return this.unicodeName + "!*@*";
  2804. return "*!*@" + this.host;
  2805. }
  2806. CIRCUser.prototype.say =
  2807. function usr_say (msg)
  2808. {
  2809. this.parent.sayTo(this.encodedName, fromUnicode(msg, this));
  2810. }
  2811. CIRCUser.prototype.notice =
  2812. function usr_notice (msg)
  2813. {
  2814. this.parent.noticeTo(this.encodedName, fromUnicode(msg, this));
  2815. }
  2816. CIRCUser.prototype.act =
  2817. function usr_act (msg)
  2818. {
  2819. this.parent.actTo(this.encodedName, fromUnicode(msg, this));
  2820. }
  2821. CIRCUser.prototype.ctcp =
  2822. function usr_ctcp (code, msg, type)
  2823. {
  2824. msg = msg || "";
  2825. type = type || "PRIVMSG";
  2826. this.parent.ctcpTo(this.encodedName, fromUnicode(code, this),
  2827. fromUnicode(msg, this), type);
  2828. }
  2829. CIRCUser.prototype.whois =
  2830. function usr_whois ()
  2831. {
  2832. this.parent.whois(this.unicodeName);
  2833. }
  2834. /*
  2835. * channel user
  2836. */
  2837. function CIRCChanUser(parent, unicodeName, encodedName, modes, userInChannel)
  2838. {
  2839. // Both unicodeName and encodedName are optional, but at least one must be
  2840. // present.
  2841. if (!encodedName && !unicodeName)
  2842. throw "Hey! Come on, I need either an encoded or a Unicode name.";
  2843. else if (encodedName && !unicodeName)
  2844. unicodeName = toUnicode(encodedName, parent);
  2845. else if (!encodedName && unicodeName)
  2846. encodedName = fromUnicode(unicodeName, parent);
  2847. // We should have both unicode and encoded names by now.
  2848. var canonicalName = parent.parent.toLowerCase(encodedName);
  2849. if (canonicalName in parent.users)
  2850. {
  2851. var existingUser = parent.users[canonicalName];
  2852. if (modes)
  2853. {
  2854. // If we start with a single character mode, assume we're replacing
  2855. // the list. (i.e. the list is either all +/- modes, or all normal)
  2856. if ((modes.length >= 1) && (modes[0].search(/^[-+]/) == -1))
  2857. {
  2858. // Modes, but no +/- prefixes, so *replace* mode list.
  2859. existingUser.modes = modes;
  2860. }
  2861. else
  2862. {
  2863. // We have a +/- mode list, so carefully update the mode list.
  2864. for (var m in modes)
  2865. {
  2866. // This will remove '-' modes, and all other modes will be
  2867. // added.
  2868. var mode = modes[m][1];
  2869. if (modes[m][0] == "-")
  2870. {
  2871. if (arrayContains(existingUser.modes, mode))
  2872. {
  2873. var i = arrayIndexOf(existingUser.modes, mode);
  2874. arrayRemoveAt(existingUser.modes, i);
  2875. }
  2876. }
  2877. else
  2878. {
  2879. if (!arrayContains(existingUser.modes, mode))
  2880. existingUser.modes.push(mode);
  2881. }
  2882. }
  2883. }
  2884. }
  2885. existingUser.isFounder = (arrayContains(existingUser.modes, "q")) ?
  2886. true : false;
  2887. existingUser.isAdmin = (arrayContains(existingUser.modes, "a")) ?
  2888. true : false;
  2889. existingUser.isOp = (arrayContains(existingUser.modes, "o")) ?
  2890. true : false;
  2891. existingUser.isHalfOp = (arrayContains(existingUser.modes, "h")) ?
  2892. true : false;
  2893. existingUser.isVoice = (arrayContains(existingUser.modes, "v")) ?
  2894. true : false;
  2895. existingUser.updateSortName();
  2896. return existingUser;
  2897. }
  2898. var protoUser = new CIRCUser(parent.parent, unicodeName, encodedName);
  2899. this.__proto__ = protoUser;
  2900. this.getURL = cusr_geturl;
  2901. this.setOp = cusr_setop;
  2902. this.setHalfOp = cusr_sethalfop;
  2903. this.setVoice = cusr_setvoice;
  2904. this.setBan = cusr_setban;
  2905. this.kick = cusr_kick;
  2906. this.kickBan = cusr_kban;
  2907. this.say = cusr_say;
  2908. this.notice = cusr_notice;
  2909. this.act = cusr_act;
  2910. this.whois = cusr_whois;
  2911. this.updateSortName = cusr_updatesortname;
  2912. this.parent = parent;
  2913. this.TYPE = "IRCChanUser";
  2914. this.modes = new Array();
  2915. if (typeof modes != "undefined")
  2916. this.modes = modes;
  2917. this.isFounder = (arrayContains(this.modes, "q")) ? true : false;
  2918. this.isAdmin = (arrayContains(this.modes, "a")) ? true : false;
  2919. this.isOp = (arrayContains(this.modes, "o")) ? true : false;
  2920. this.isHalfOp = (arrayContains(this.modes, "h")) ? true : false;
  2921. this.isVoice = (arrayContains(this.modes, "v")) ? true : false;
  2922. this.updateSortName();
  2923. if (userInChannel)
  2924. parent.users[this.canonicalName] = this;
  2925. return this;
  2926. }
  2927. function cusr_updatesortname()
  2928. {
  2929. // Check for the highest mode the user has (for sorting the userlist)
  2930. const userModes = this.parent.parent.userModes;
  2931. var modeLevel = 0;
  2932. var mode;
  2933. for (var i = 0; i < this.modes.length; i++)
  2934. {
  2935. for (var j = 0; j < userModes.length; j++)
  2936. {
  2937. if (userModes[j].mode == this.modes[i])
  2938. {
  2939. if (userModes.length - j > modeLevel)
  2940. {
  2941. modeLevel = userModes.length - j;
  2942. mode = userModes[j];
  2943. }
  2944. break;
  2945. }
  2946. }
  2947. }
  2948. // Counts numerically down from 9.
  2949. this.sortName = (9 - modeLevel) + "-" + this.unicodeName;
  2950. }
  2951. function cusr_geturl()
  2952. {
  2953. // Don't ask.
  2954. return this.parent.parent.parent.getURL(this.encodedName, ["isnick"]);
  2955. }
  2956. function cusr_setop(f)
  2957. {
  2958. var server = this.parent.parent;
  2959. var me = server.me;
  2960. var modifier = (f) ? " +o " : " -o ";
  2961. server.sendData("MODE " + this.parent.encodedName + modifier + this.encodedName + "\n");
  2962. return true;
  2963. }
  2964. function cusr_sethalfop (f)
  2965. {
  2966. var server = this.parent.parent;
  2967. var me = server.me;
  2968. var modifier = (f) ? " +h " : " -h ";
  2969. server.sendData("MODE " + this.parent.encodedName + modifier + this.encodedName + "\n");
  2970. return true;
  2971. }
  2972. function cusr_setvoice (f)
  2973. {
  2974. var server = this.parent.parent;
  2975. var me = server.me;
  2976. var modifier = (f) ? " +v " : " -v ";
  2977. server.sendData("MODE " + this.parent.encodedName + modifier + this.encodedName + "\n");
  2978. return true;
  2979. }
  2980. function cusr_kick (reason)
  2981. {
  2982. var server = this.parent.parent;
  2983. var me = server.me;
  2984. reason = typeof reason == "string" ? reason : "";
  2985. server.sendData("KICK " + this.parent.encodedName + " " + this.encodedName + " :" +
  2986. fromUnicode(reason, this) + "\n");
  2987. return true;
  2988. }
  2989. function cusr_setban (f)
  2990. {
  2991. var server = this.parent.parent;
  2992. var me = server.me;
  2993. if (!this.host)
  2994. return false;
  2995. var modifier = (f) ? " +b " : " -b ";
  2996. modifier += fromUnicode(this.getBanMask(), server) + " ";
  2997. server.sendData("MODE " + this.parent.encodedName + modifier + "\n");
  2998. return true;
  2999. }
  3000. function cusr_kban (reason)
  3001. {
  3002. var server = this.parent.parent;
  3003. var me = server.me;
  3004. if (!this.host)
  3005. return false;
  3006. reason = (typeof reason != "undefined") ? reason : this.encodedName;
  3007. var modifier = " -o+b " + this.encodedName + " " +
  3008. fromUnicode(this.getBanMask(), server) + " ";
  3009. server.sendData("MODE " + this.parent.encodedName + modifier + "\n" +
  3010. "KICK " + this.parent.encodedName + " " +
  3011. this.encodedName + " :" + reason + "\n");
  3012. return true;
  3013. }
  3014. function cusr_say (msg)
  3015. {
  3016. this.__proto__.say (msg);
  3017. }
  3018. function cusr_notice (msg)
  3019. {
  3020. this.__proto__.notice (msg);
  3021. }
  3022. function cusr_act (msg)
  3023. {
  3024. this.__proto__.act (msg);
  3025. }
  3026. function cusr_whois ()
  3027. {
  3028. this.__proto__.whois ();
  3029. }
  3030. // IRC URL parsing and generating
  3031. function parseIRCURL(url)
  3032. {
  3033. var specifiedHost = "";
  3034. var rv = new Object();
  3035. rv.spec = url;
  3036. rv.scheme = url.split(":")[0];
  3037. rv.host = null;
  3038. rv.target = "";
  3039. rv.port = (rv.scheme == "ircs" ? 9999 : 6667);
  3040. rv.msg = "";
  3041. rv.pass = null;
  3042. rv.key = null;
  3043. rv.charset = null;
  3044. rv.needpass = false;
  3045. rv.needkey = false;
  3046. rv.isnick = false;
  3047. rv.isserver = false;
  3048. if (url.search(/^(ircs?:\/?\/?)$/i) != -1)
  3049. return rv;
  3050. /* split url into <host>/<everything-else> pieces */
  3051. var ary = url.match(/^ircs?:\/\/([^\/\s]+)?(\/[^\s]*)?$/i);
  3052. if (!ary || !ary[1])
  3053. {
  3054. dd("parseIRCURL: initial split failed");
  3055. return null;
  3056. }
  3057. var host = ary[1];
  3058. var rest = arrayHasElementAt(ary, 2) ? ary[2] : "";
  3059. /* split <host> into server (or network) / port */
  3060. ary = host.match(/^([^\:]+)(\:\d+)?$/);
  3061. if (!ary)
  3062. {
  3063. dd("parseIRCURL: host/port split failed");
  3064. return null;
  3065. }
  3066. specifiedHost = rv.host = ary[1].toLowerCase();
  3067. if (arrayHasElementAt(ary, 2))
  3068. {
  3069. rv.isserver = true;
  3070. rv.port = parseInt(ary[2].substr(1));
  3071. }
  3072. else
  3073. {
  3074. if (specifiedHost.indexOf(".") != -1)
  3075. rv.isserver = true;
  3076. }
  3077. if (rest)
  3078. {
  3079. ary = rest.match(/^\/([^\?\s\/,]*)?\/?(,[^\?]*)?(\?.*)?$/);
  3080. if (!ary)
  3081. {
  3082. dd("parseIRCURL: rest split failed ``" + rest + "''");
  3083. return null;
  3084. }
  3085. rv.target = arrayHasElementAt(ary, 1) ? ecmaUnescape(ary[1]) : "";
  3086. if (rv.target.search(/[\x07,\s]/) != -1)
  3087. {
  3088. dd("parseIRCURL: invalid characters in channel name");
  3089. return null;
  3090. }
  3091. var params = arrayHasElementAt(ary, 2) ? ary[2].toLowerCase() : "";
  3092. var query = arrayHasElementAt(ary, 3) ? ary[3] : "";
  3093. if (params)
  3094. {
  3095. params = params.split(",");
  3096. while (params.length)
  3097. {
  3098. var param = params.pop();
  3099. // split doesn't take out empty bits:
  3100. if (param == "")
  3101. continue;
  3102. switch (param)
  3103. {
  3104. case "isnick":
  3105. rv.isnick = true;
  3106. if (!rv.target)
  3107. {
  3108. dd("parseIRCURL: isnick w/o target");
  3109. /* isnick w/o a target is bogus */
  3110. return null;
  3111. }
  3112. break;
  3113. case "isserver":
  3114. rv.isserver = true;
  3115. if (!specifiedHost)
  3116. {
  3117. dd("parseIRCURL: isserver w/o host");
  3118. /* isserver w/o a host is bogus */
  3119. return null;
  3120. }
  3121. break;
  3122. case "needpass":
  3123. case "needkey":
  3124. rv[param] = true;
  3125. break;
  3126. default:
  3127. /* If we didn't understand it, ignore but warn: */
  3128. dd("parseIRCURL: Unrecognized param '" + param +
  3129. "' in URL!");
  3130. }
  3131. }
  3132. }
  3133. if (query)
  3134. {
  3135. ary = query.substr(1).split("&");
  3136. while (ary.length)
  3137. {
  3138. var arg = ary.pop().split("=");
  3139. /*
  3140. * we don't want to accept *any* query, or folks could
  3141. * say things like "target=foo", and overwrite what we've
  3142. * already parsed, so we only use query args we know about.
  3143. */
  3144. switch (arg[0].toLowerCase())
  3145. {
  3146. case "msg":
  3147. rv.msg = ecmaUnescape(arg[1]).replace("\n", "\\n");
  3148. break;
  3149. case "pass":
  3150. rv.needpass = true;
  3151. rv.pass = ecmaUnescape(arg[1]).replace("\n", "\\n");
  3152. break;
  3153. case "key":
  3154. rv.needkey = true;
  3155. rv.key = ecmaUnescape(arg[1]).replace("\n", "\\n");
  3156. break;
  3157. case "charset":
  3158. rv.charset = ecmaUnescape(arg[1]).replace("\n", "\\n");
  3159. break;
  3160. }
  3161. }
  3162. }
  3163. }
  3164. return rv;
  3165. }
  3166. function constructIRCURL(obj)
  3167. {
  3168. function parseQuery(obj)
  3169. {
  3170. var rv = new Array();
  3171. if ("msg" in obj)
  3172. rv.push("msg=" + ecmaEscape(obj.msg.replace("\\n", "\n")));
  3173. if ("pass" in obj)
  3174. rv.push("pass=" + ecmaEscape(obj.pass.replace("\\n", "\n")));
  3175. if ("key" in obj)
  3176. rv.push("key=" + ecmaEscape(obj.key.replace("\\n", "\n")));
  3177. if ("charset" in obj)
  3178. rv.push("charset=" + ecmaEscape(obj.charset.replace("\\n", "\n")));
  3179. return rv.length ? "?" + rv.join("&") : "";
  3180. };
  3181. function parseFlags(obj)
  3182. {
  3183. var rv = new Array();
  3184. var haveTarget = ("target" in obj) && obj.target;
  3185. if (("needpass" in obj) && obj.needpass)
  3186. rv.push(",needpass");
  3187. if (("needkey" in obj) && obj.needkey && haveTarget)
  3188. rv.push(",needkey");
  3189. if (("isnick" in obj) && obj.isnick && haveTarget)
  3190. rv.push(",isnick");
  3191. return rv.join("");
  3192. };
  3193. var flags = "";
  3194. var scheme = ("scheme" in obj) ? obj.scheme : "irc";
  3195. if (!("host" in obj) || !obj.host)
  3196. return scheme + "://";
  3197. var url = scheme + "://" + obj.host;
  3198. // Add port if non-standard:
  3199. if (("port" in obj) && (((scheme == "ircs") && (obj.port != 9999)) ||
  3200. ((scheme == "irc") && (obj.port != 6667))))
  3201. {
  3202. url += ":" + obj.port;
  3203. }
  3204. // Need to add ",isserver" if there's no port and no dots in the hostname:
  3205. else if (("isserver" in obj) && obj.isserver &&
  3206. (obj.host.indexOf(".") == -1))
  3207. {
  3208. flags += ",isserver";
  3209. }
  3210. url += "/";
  3211. if (("target" in obj) && obj.target)
  3212. {
  3213. if (obj.target.search(/[\x07,\s]/) != -1)
  3214. {
  3215. dd("parseIRCObject: invalid characters in channel/nick name");
  3216. return null;
  3217. }
  3218. url += ecmaEscape(obj.target).replace(/\//g, "%2f");
  3219. }
  3220. return url + flags + parseFlags(obj) + parseQuery(obj);
  3221. }
  3222. /* Canonicalizing an IRC URL removes all items which aren't necessary to
  3223. * identify the target. For example, an IRC URL with ?pass=password and one
  3224. * without (but otherwise identical) are refering to the same target, so
  3225. * ?pass= is removed.
  3226. */
  3227. function makeCanonicalIRCURL(url)
  3228. {
  3229. var canonicalProps = { scheme: true, host: true, port: true,
  3230. target: true, isserver: true, isnick: true };
  3231. var urlObject = parseIRCURL(url);
  3232. if (!urlObject)
  3233. return ""; // Input wasn't a valid IRC URL.
  3234. for (var prop in urlObject)
  3235. {
  3236. if (!(prop in canonicalProps))
  3237. delete urlObject[prop];
  3238. }
  3239. return constructIRCURL(urlObject);
  3240. }