summaryrefslogtreecommitdiffstats
path: root/comm/suite/chatzilla/js/lib/dcc.js
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/suite/chatzilla/js/lib/dcc.js
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/suite/chatzilla/js/lib/dcc.js')
-rw-r--r--comm/suite/chatzilla/js/lib/dcc.js1198
1 files changed, 1198 insertions, 0 deletions
diff --git a/comm/suite/chatzilla/js/lib/dcc.js b/comm/suite/chatzilla/js/lib/dcc.js
new file mode 100644
index 0000000000..d12b8f1a52
--- /dev/null
+++ b/comm/suite/chatzilla/js/lib/dcc.js
@@ -0,0 +1,1198 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* We pick a random start ID, from 0 to DCC_ID_MAX inclusive, then go through
+ * the IDs one at a time, in sequence. We wrap when we get to DCC_ID_MAX. No
+ * uniqueness checking is done, but it takes DCC_ID_MAX connections before we
+ * hit the start ID again. 65,536 IDs ought to be enough for now. :)
+ */
+const DCC_ID_MAX = 0xFFFF;
+
+function CIRCDCC(parent)
+{
+ this.parent = parent;
+
+ this.users = new Object();
+ this.chats = new Array();
+ this.files = new Array();
+ this.last = null;
+ this.lastTime = null;
+
+ this.sendChunk = 4096;
+ this.maxUnAcked = 32; // 4096 * 32 == 128KiB 'in transit'.
+
+ this.requestTimeout = 3 * 60 * 1000; // 3 minutes.
+
+ // Can't do anything 'til this is set!
+ this.localIPlist = new Array();
+ this.localIP = null;
+ this._lastPort = null;
+
+ try {
+ var dnsComp = Components.classes["@mozilla.org/network/dns-service;1"];
+ this._dnsSvc = dnsComp.getService(Components.interfaces.nsIDNSService);
+
+ // Get local hostname.
+ if ("myHostName" in this._dnsSvc) {
+ // Using newer (1.7a+) version with DNS re-write.
+ this.addHost(this._dnsSvc.myHostName);
+ }
+ if ("myIPAddress" in this._dnsSvc) {
+ // Older Mozilla, have to use this method.
+ this.addIP(this._dnsSvc.myIPAddress);
+ }
+ this.addHost("localhost");
+ } catch(ex) {
+ // what to do?
+ dd("Error getting local IPs: " + ex);
+ }
+
+ this._lastID = Math.round(Math.random() * DCC_ID_MAX);
+
+ return this;
+}
+
+CIRCDCC.prototype.TYPE = "IRCDCC";
+CIRCDCC.prototype.listenPorts = [];
+
+CIRCDCC.prototype.addUser =
+function dcc_adduser(user, remoteIP)
+{
+ // user == CIRCUser object.
+ // remoteIP == remoteIP as specified in CTCP DCC message.
+ return new CIRCDCCUser(this, user, remoteIP);
+}
+
+CIRCDCC.prototype.addChat =
+function dcc_addchat(user, port)
+{
+ // user == CIRCDCCUser object.
+ // port == port as specified in CTCP DCC message.
+ return new CIRCDCCChat(this, user, port);
+}
+
+CIRCDCC.prototype.addFileTransfer =
+function dcc_addfile(user, port, file, size)
+{
+ return new CIRCDCCFileTransfer(this, user, port, file, size);
+}
+
+CIRCDCC.prototype.addHost =
+function dcc_addhost(host, auth)
+{
+ var me = this;
+ var listener = {
+ onLookupComplete: function _onLookupComplete(request, record, status) {
+ // record == null if it failed. We can't do anything with a failure.
+ if (record)
+ {
+ while (record.hasMore())
+ me.addIP(record.getNextAddrAsString(), auth);
+ }
+ }
+ };
+
+ try {
+ var th = getService("@mozilla.org/thread-manager;1").currentThread;
+ var dnsRecord = this._dnsSvc.asyncResolve(host, false, listener, th);
+ } catch (ex) {
+ dd("Error resolving host to IP: " + ex);
+ }
+}
+
+CIRCDCC.prototype.addIP =
+function dcc_addip(ip, auth)
+{
+ if (auth)
+ this.localIPlist.unshift(ip);
+ else
+ this.localIPlist.push(ip);
+
+ if (this.localIPlist.length > 0)
+ this.localIP = this.localIPlist[0];
+}
+
+CIRCDCC.prototype.getMatches =
+function dcc_getmatches(nickname, filename, types, dirs, states)
+{
+ function matchNames(name, otherName)
+ {
+ return ((name.match(new RegExp(otherName, "i"))) ||
+ (name.toLowerCase().indexOf(otherName.toLowerCase()) != -1));
+ };
+
+ var k;
+ var list = new Array();
+ if (!types)
+ types = ["chat", "file"];
+
+ var n = nickname;
+ var f = filename;
+
+ if (arrayIndexOf(types, "chat") >= 0)
+ {
+ for (k = 0; k < this.chats.length; k++)
+ {
+ if ((!nickname || matchNames(this.chats[k].user.unicodeName, n)) &&
+ (!dirs || arrayIndexOf(dirs, this.chats[k].state.dir) >= 0) &&
+ (!states || arrayIndexOf(states, this.chats[k].state.state) >= 0))
+ {
+ list.push(this.chats[k]);
+ }
+ }
+ }
+ if (arrayIndexOf(types, "file") >= 0)
+ {
+ for (k = 0; k < this.files.length; k++)
+ {
+ if ((!nickname || matchNames(this.files[k].user.unicodeName, n)) &&
+ (!filename || matchNames(this.files[k].filename, f)) &&
+ (!dirs || arrayIndexOf(dirs, this.files[k].state.dir) >= 0) &&
+ (!states || arrayIndexOf(states, this.files[k].state.state) >= 0))
+ {
+ list.push(this.files[k]);
+ }
+ }
+ }
+
+ return list;
+}
+
+CIRCDCC.prototype.getNextPort =
+function dcc_getnextport()
+{
+ var portList = this.listenPorts;
+
+ var newPort = this._lastPort;
+
+ for (var i = 0; i < portList.length; i++)
+ {
+ var m = portList[i].match(/^(\d+)(?:-(\d+))?$/);
+ if (m)
+ {
+ if (!newPort)
+ {
+ // We dodn't have any previous port, so just take the first we
+ // find.
+ return this._lastPort = Number(m[1]);
+ }
+ else if (arrayHasElementAt(m, 2))
+ {
+ // Port range. Anything before range, or in [exl. last value]
+ // is ok. Make sure first value is lowest value returned.
+ if (newPort < m[2])
+ return this._lastPort = Math.max(newPort + 1, Number(m[1]));
+ }
+ else
+ {
+ // Single port.
+ if (newPort < m[1])
+ return this._lastPort = Number(m[1]);
+ }
+ }
+ }
+
+ // No ports found, and no last port --> use OS.
+ if (newPort == null)
+ return -1;
+
+ // Didn't find anything... d'oh. Need to start from the begining again.
+ this._lastPort = null;
+ return this.getNextPort();
+}
+
+CIRCDCC.prototype.getNextID =
+function dcc_getnextid()
+{
+ this._lastID++;
+ if (this._lastID > DCC_ID_MAX)
+ this._lastID = 0;
+
+ // Format to DCC_ID_MAX's number of digits.
+ var id = this._lastID.toString(16);
+ while (id.length < DCC_ID_MAX.toString(16).length)
+ id = "0" + id;
+ return id;
+}
+
+CIRCDCC.prototype.findByID =
+function dcc_findbyid(id)
+{
+ if (typeof id != "string")
+ return null;
+
+ var i;
+ for (i = 0; i < this.chats.length; i++)
+ {
+ if (this.chats[i].id == id)
+ return this.chats[i];
+ }
+ for (i = 0; i < this.files.length; i++)
+ {
+ if (this.files[i].id == id)
+ return this.files[i];
+ }
+ return null;
+}
+
+
+// JavaScript won't let you delete things declared with "var", workaround:
+window.val = -1;
+
+const DCC_STATE_FAILED = val++; // try connect (accept), but it failed.
+const DCC_STATE_INIT = val++; // not "doing" anything
+const DCC_STATE_REQUESTED = val++; // waiting
+const DCC_STATE_ACCEPTED = val++; // accepted.
+const DCC_STATE_DECLINED = val++; // declined.
+const DCC_STATE_CONNECTED = val++; // all going ok.
+const DCC_STATE_DONE = val++; // finished ok.
+const DCC_STATE_ABORTED = val++; // send wasn't accepted in time.
+
+delete window.val;
+
+const DCC_DIR_UNKNOWN = 0;
+const DCC_DIR_SENDING = 1;
+const DCC_DIR_GETTING = 2;
+
+
+function CIRCDCCUser(parent, user, remoteIP)
+{
+ // user == CIRCUser object.
+ // remoteIP == remoteIP as specified in CTCP DCC message.
+
+ if ("dccUser" in user)
+ {
+ if (remoteIP)
+ user.dccUser.remoteIP = remoteIP;
+
+ return user.dccUser;
+ }
+
+ this.parent = parent;
+ this.netUser = user;
+ this.id = parent.getNextID();
+ this.unicodeName = user.unicodeName;
+ this.viewName = user.unicodeName;
+ this.canonicalName = user.collectionKey.substr(1);
+ this.remoteIP = remoteIP;
+
+ this.key = escape(user.collectionKey) + ":" + remoteIP;
+ user.dccUser = this;
+ this.parent.users[this.key] = this;
+
+ if ("onInit" in this)
+ this.onInit();
+
+ return this;
+}
+
+CIRCDCCUser.prototype.TYPE = "IRCDCCUser";
+
+
+// Keeps track of the state of a DCC connection.
+function CIRCDCCState(parent, owner, eventType)
+{
+ // parent == central CIRCDCC object.
+ // owner == DCC Chat or File object.
+ // eventType == "dcc-chat" or "dcc-file".
+
+ this.parent = parent;
+ this.owner = owner;
+ this.eventType = eventType;
+
+ this.eventPump = owner.eventPump;
+
+ this.state = DCC_STATE_INIT;
+ this.dir = DCC_DIR_UNKNOWN;
+
+ return this;
+}
+
+CIRCDCCState.prototype.TYPE = "IRCDCCState";
+
+CIRCDCCState.prototype.sendRequest =
+function dccstate_sendRequest()
+{
+ if (!this.parent.localIP || (this.state != DCC_STATE_INIT))
+ throw "Must have a local IP and be in INIT state.";
+
+ this.state = DCC_STATE_REQUESTED;
+ this.dir = DCC_DIR_SENDING;
+ this.requested = new Date();
+
+ this.requestTimeout = setTimeout(function (o){ o.abort(); },
+ this.parent.requestTimeout, this.owner);
+
+ this.eventPump.addEvent(new CEvent(this.eventType, "request",
+ this.owner, "onRequest"));
+}
+
+CIRCDCCState.prototype.getRequest =
+function dccstate_getRequest()
+{
+ if (this.state != DCC_STATE_INIT)
+ throw "Must be in INIT state.";
+
+ this.state = DCC_STATE_REQUESTED;
+ this.dir = DCC_DIR_GETTING;
+ this.requested = new Date();
+
+ this.parent.last = this.owner;
+ this.parent.lastTime = new Date();
+
+ this.requestTimeout = setTimeout(function (o){ o.abort(); },
+ this.parent.requestTimeout, this.owner);
+
+ this.eventPump.addEvent(new CEvent(this.eventType, "request",
+ this.owner, "onRequest"));
+}
+
+CIRCDCCState.prototype.sendAccept =
+function dccstate_sendAccept()
+{
+ if ((this.state != DCC_STATE_REQUESTED) || (this.dir != DCC_DIR_GETTING))
+ throw "Must be in REQUESTED state and direction GET.";
+
+ // Clear out "last" incoming request if that's us.
+ if (this.parent.last == this.owner)
+ {
+ this.parent.last = null;
+ this.parent.lastTime = null;
+ }
+
+ clearTimeout(this.requestTimeout);
+ delete this.requestTimeout;
+
+ this.state = DCC_STATE_ACCEPTED;
+ this.accepted = new Date();
+
+ this.eventPump.addEvent(new CEvent(this.eventType, "accept",
+ this.owner, "onAccept"));
+}
+
+CIRCDCCState.prototype.getAccept =
+function dccstate_getAccept()
+{
+ if ((this.state != DCC_STATE_REQUESTED) || (this.dir != DCC_DIR_SENDING))
+ throw "Must be in REQUESTED state and direction SEND.";
+
+ clearTimeout(this.requestTimeout);
+ delete this.requestTimeout;
+
+ this.state = DCC_STATE_ACCEPTED;
+ this.accepted = new Date();
+
+ this.eventPump.addEvent(new CEvent(this.eventType, "accept",
+ this.owner, "onAccept"));
+}
+
+CIRCDCCState.prototype.sendDecline =
+function dccstate_sendDecline()
+{
+ if ((this.state != DCC_STATE_REQUESTED) || (this.dir != DCC_DIR_GETTING))
+ throw "Must be in REQUESTED state and direction GET.";
+
+ // Clear out "last" incoming request if that's us.
+ if (this.parent.last == this.owner)
+ {
+ this.parent.last = null;
+ this.parent.lastTime = null;
+ }
+
+ clearTimeout(this.requestTimeout);
+ delete this.requestTimeout;
+
+ this.state = DCC_STATE_DECLINED;
+ this.declined = new Date();
+
+ this.eventPump.addEvent(new CEvent(this.eventType, "decline",
+ this.owner, "onDecline"));
+}
+
+CIRCDCCState.prototype.getDecline =
+function dccstate_getDecline()
+{
+ if ((this.state != DCC_STATE_REQUESTED) || (this.dir != DCC_DIR_SENDING))
+ throw "Must be in REQUESTED state and direction SEND.";
+
+ clearTimeout(this.requestTimeout);
+ delete this.requestTimeout;
+
+ this.state = DCC_STATE_DECLINED;
+ this.declined = new Date();
+
+ this.eventPump.addEvent(new CEvent(this.eventType, "decline",
+ this.owner, "onDecline"));
+}
+
+// The sockets connected.
+CIRCDCCState.prototype.socketConnected =
+function dccstate_socketConnected()
+{
+ if (this.state != DCC_STATE_ACCEPTED)
+ throw "Not in ACCEPTED state.";
+
+ this.state = DCC_STATE_CONNECTED;
+
+ this.eventPump.addEvent(new CEvent(this.eventType, "connect",
+ this.owner, "onConnect"));
+}
+
+// Someone disconnected something.
+CIRCDCCState.prototype.socketDisconnected =
+function dccstate_socketDisconnected()
+{
+ if (this.state != DCC_STATE_CONNECTED)
+ throw "Not CONNECTED!";
+
+ this.state = DCC_STATE_DONE;
+
+ this.eventPump.addEvent(new CEvent(this.eventType, "disconnect",
+ this.owner, "onDisconnect"));
+}
+
+CIRCDCCState.prototype.sendAbort =
+function dccstate_sendAbort()
+{
+ if ((this.state != DCC_STATE_REQUESTED) &&
+ (this.state != DCC_STATE_ACCEPTED) &&
+ (this.state != DCC_STATE_CONNECTED))
+ {
+ throw "Can't abort at this point.";
+ }
+
+ this.state = DCC_STATE_ABORTED;
+
+ this.eventPump.addEvent(new CEvent(this.eventType, "abort",
+ this.owner, "onAbort"));
+}
+
+CIRCDCCState.prototype.getAbort =
+function dccstate_getAbort()
+{
+ if ((this.state != DCC_STATE_REQUESTED) &&
+ (this.state != DCC_STATE_ACCEPTED) &&
+ (this.state != DCC_STATE_CONNECTED))
+ {
+ throw "Can't abort at this point.";
+ }
+
+ this.state = DCC_STATE_ABORTED;
+
+ this.eventPump.addEvent(new CEvent(this.eventType, "abort",
+ this.owner, "onAbort"));
+}
+
+CIRCDCCState.prototype.failed =
+function dccstate_failed()
+{
+ if ((this.state != DCC_STATE_REQUESTED) &&
+ (this.state != DCC_STATE_ACCEPTED) &&
+ (this.state != DCC_STATE_CONNECTED))
+ {
+ throw "Can't fail at this point.";
+ }
+
+ this.state = DCC_STATE_FAILED;
+
+ this.eventPump.addEvent(new CEvent(this.eventType, "fail",
+ this.owner, "onFail"));
+}
+
+
+
+
+function CIRCDCCChat(parent, user, port)
+{
+ // user == CIRCDCCUser object.
+ // port == port as specified in CTCP DCC message.
+
+ this.READ_TIMEOUT = 50;
+
+ // Link up all our data.
+ this.parent = parent;
+ this.id = parent.getNextID();
+ this.eventPump = parent.parent.eventPump;
+ this.user = user;
+ this.localIP = this.parent.localIP;
+ this.remoteIP = user.remoteIP;
+ this.port = port;
+ this.unicodeName = user.unicodeName;
+ this.viewName = "DCC: " + user.unicodeName;
+
+ // Set up the initial state.
+ this.requested = null;
+ this.connection = null;
+ this.savedLine = "";
+ this.state = new CIRCDCCState(parent, this, "dcc-chat");
+ this.parent.chats.push(this);
+
+ // Give ourselves a "me" object for the purposes of displaying stuff.
+ this.me = this.parent.addUser(this.user.netUser.parent.me, "0.0.0.0");
+
+ if ("onInit" in this)
+ this.onInit();
+
+ return this;
+}
+
+CIRCDCCChat.prototype.TYPE = "IRCDCCChat";
+
+CIRCDCCChat.prototype.getURL =
+function dchat_geturl()
+{
+ return "x-irc-dcc-chat:" + this.id;
+}
+
+CIRCDCCChat.prototype.isActive =
+function dchat_isactive()
+{
+ return (this.state.state == DCC_STATE_REQUESTED) ||
+ (this.state.state == DCC_STATE_ACCEPTED) ||
+ (this.state.state == DCC_STATE_CONNECTED);
+}
+
+// Call to make this end request DCC Chat with targeted user.
+CIRCDCCChat.prototype.request =
+function dchat_request()
+{
+ this.state.sendRequest();
+
+ this.localIP = this.parent.localIP;
+
+ this.connection = new CBSConnection();
+ if (!this.connection.listen(this.port, this))
+ {
+ this.state.failed();
+ return false;
+ }
+
+ this.port = this.connection.port;
+
+ // Send the CTCP DCC request via the net user (CIRCUser object).
+ var ipNumber;
+ var ipParts = this.localIP.match(/(\d+)\.(\d+)\.(\d+)\.(\d+)/);
+ if (ipParts)
+ ipNumber = Number(ipParts[1]) * 256 * 256 * 256 +
+ Number(ipParts[2]) * 256 * 256 +
+ Number(ipParts[3]) * 256 +
+ Number(ipParts[4]);
+ else
+ return false;
+ // What should we do here? Panic?
+
+ this.user.netUser.ctcp("DCC", "CHAT chat " + ipNumber + " " + this.port);
+
+ return true;
+}
+
+// Call to make this end accept DCC Chat with target user.
+CIRCDCCChat.prototype.accept =
+function dchat_accept()
+{
+ this.state.sendAccept();
+
+ this.connection = new CBSConnection();
+ this.connection.connect(this.remoteIP, this.port, null, this);
+
+ return true;
+}
+
+// This may be called synchronously or asynchronously by CBSConnection.connect.
+CIRCDCCChat.prototype.onSocketConnection =
+function dchat_onsocketconnection(host, port, config, exception)
+{
+ if (!exception)
+ {
+ this.state.socketConnected();
+
+ this.connection.startAsyncRead(this);
+ }
+ else
+ {
+ this.state.failed();
+ }
+}
+
+// Call to make this end decline DCC Chat with target user.
+CIRCDCCChat.prototype.decline =
+function dchat_decline()
+{
+ this.state.sendDecline();
+
+ // Tell the other end, if they care, that we refused.
+ this.user.netUser.ctcp("DCC", "REJECT CHAT chat");
+
+ return true;
+}
+
+// Call to close the connection.
+CIRCDCCChat.prototype.disconnect =
+function dchat_disconnect()
+{
+ this.connection.disconnect();
+
+ return true;
+}
+
+// Aborts the connection.
+CIRCDCCChat.prototype.abort =
+function dchat_abort()
+{
+ if (this.state.state == DCC_STATE_CONNECTED)
+ {
+ this.disconnect();
+ return;
+ }
+
+ this.state.sendAbort();
+
+ if (this.connection)
+ this.connection.close();
+}
+
+// Event to handle a request from the target user.
+// CIRCUser points the event here.
+CIRCDCCChat.prototype.onGotRequest =
+function dchat_onGotRequest(e)
+{
+ this.state.getRequest();
+
+ // Pass over to the base user.
+ e.destObject = this.user.netUser;
+}
+
+// Event to handle a client connecting to the listening socket.
+// CBSConnection points the event here.
+CIRCDCCChat.prototype.onSocketAccepted =
+function dchat_onSocketAccepted(socket, transport)
+{
+ this.state.getAccept();
+
+ this.connection.accept(transport, null);
+
+ this.state.socketConnected();
+
+ this.remoteIP = transport.host;
+
+ // Start the reading!
+ this.connection.startAsyncRead(this);
+}
+
+CIRCDCCChat.prototype.onStreamDataAvailable =
+function dchat_sda(request, inStream, sourceOffset, count)
+{
+ var ev = new CEvent("dcc-chat", "data-available", this, "onDataAvailable");
+ ev.line = this.connection.readData(0, count);
+ this.eventPump.routeEvent(ev);
+}
+
+CIRCDCCChat.prototype.onStreamClose =
+function dchat_sockdiscon(status)
+{
+ this.state.socketDisconnected();
+
+ //var ev = new CEvent("dcc-chat", "disconnect", this, "onDisconnect");
+ //ev.server = this;
+ //ev.disconnectStatus = status;
+ //this.eventPump.addEvent(ev);
+}
+
+CIRCDCCChat.prototype.onDataAvailable =
+function dchat_dataavailable(e)
+{
+ var line = e.line;
+
+ var incomplete = (line[line.length] != '\n');
+ var lines = line.split("\n");
+
+ if (this.savedLine)
+ {
+ lines[0] = this.savedLine + lines[0];
+ this.savedLine = "";
+ }
+
+ if (incomplete)
+ this.savedLine = lines.pop();
+
+ for (var i in lines)
+ {
+ var ev = new CEvent("dcc-chat", "rawdata", this, "onRawData");
+ ev.data = lines[i];
+ ev.replyTo = this;
+ this.eventPump.addEvent (ev);
+ }
+
+ return true;
+}
+
+// Raw data from DCC Chat stream.
+CIRCDCCChat.prototype.onRawData =
+function dchat_rawdata(e)
+{
+ e.code = "PRIVMSG";
+ e.line = e.data;
+ e.user = this.user;
+ e.type = "parseddata";
+ e.destObject = this;
+ e.destMethod = "onParsedData";
+
+ return true;
+}
+
+CIRCDCCChat.prototype.onParsedData =
+function dchat_onParsedData(e)
+{
+ e.type = e.code.toLowerCase();
+ if (!e.code[0])
+ {
+ dd (dumpObjectTree (e));
+ return false;
+ }
+
+ if (e.line.search(/\x01.*\x01/i) != -1) {
+ e.type = "ctcp";
+ e.destMethod = "onCTCP";
+ e.set = "dcc-chat";
+ e.destObject = this;
+ }
+ else
+ {
+ e.type = "privmsg";
+ e.destMethod = "onPrivmsg";
+ e.set = "dcc-chat";
+ e.destObject = this;
+ }
+
+ // Allow DCC Chat to handle it before "falling back" to DCC User.
+ if (typeof this[e.destMethod] == "function")
+ e.destObject = this;
+ else if (typeof this.user[e.destMethod] == "function")
+ e.destObject = this.user;
+ else if (typeof this["onUnknown"] == "function")
+ e.destMethod = "onUnknown";
+ else if (typeof this.parent[e.destMethod] == "function")
+ {
+ e.set = "dcc";
+ e.destObject = this.parent;
+ }
+ else
+ {
+ e.set = "dcc";
+ e.destObject = this.parent;
+ e.destMethod = "onUnknown";
+ }
+
+ return true;
+}
+
+CIRCDCCChat.prototype.onCTCP =
+function serv_ctcp(e)
+{
+ // The \x0D? is a BIG HACK to make this work with X-Chat.
+ var ary = e.line.match(/^\x01([^ ]+) ?(.*)\x01\x0D?$/i);
+ if (ary == null)
+ return false;
+
+ e.CTCPData = ary[2] ? ary[2] : "";
+ e.CTCPCode = ary[1].toLowerCase();
+ if (e.CTCPCode.search(/^reply/i) == 0)
+ {
+ dd("dropping spoofed reply.");
+ return false;
+ }
+
+ e.CTCPCode = toUnicode(e.CTCPCode, e.replyTo);
+ e.CTCPData = toUnicode(e.CTCPData, e.replyTo);
+
+ e.type = "ctcp-" + e.CTCPCode;
+ e.destMethod = "onCTCP" + ary[1][0].toUpperCase() +
+ ary[1].substr(1, ary[1].length).toLowerCase();
+
+ if (typeof this[e.destMethod] != "function")
+ {
+ e.destObject = e.user;
+ e.set = "dcc-user";
+ if (typeof e.user[e.destMethod] != "function")
+ {
+ e.type = "unk-ctcp";
+ e.destMethod = "onUnknownCTCP";
+ }
+ }
+ else
+ {
+ e.destObject = this;
+ }
+ return true;
+}
+
+CIRCDCCChat.prototype.ctcp =
+function dchat_ctcp(code, msg)
+{
+ msg = msg || "";
+
+ this.connection.sendData("\x01" + fromUnicode(code, this) + " " +
+ fromUnicode(msg, this) + "\x01\n");
+}
+
+CIRCDCCChat.prototype.say =
+function dchat_say (msg)
+{
+ this.connection.sendData(fromUnicode(msg, this) + "\n");
+}
+
+CIRCDCCChat.prototype.act =
+function dchat_act(msg)
+{
+ this.ctcp("ACTION", msg);
+}
+
+
+function CIRCDCCFileTransfer(parent, user, port, file, size)
+{
+ // user == CIRCDCCUser object.
+ // port == port as specified in CTCP DCC message.
+ // file == name of file being sent/got.
+ // size == size of said file.
+
+ this.READ_TIMEOUT = 50;
+
+ // Link up all our data.
+ this.parent = parent;
+ this.id = parent.getNextID();
+ this.eventPump = parent.parent.eventPump;
+ this.user = user;
+ this.localIP = this.parent.localIP;
+ this.remoteIP = user.remoteIP;
+ this.port = port;
+ this.filename = file;
+ this.size = size;
+ this.unicodeName = user.unicodeName;
+ this.viewName = "File: " + this.filename;
+
+ // Set up the initial state.
+ this.requested = null;
+ this.connection = null;
+ this.state = new CIRCDCCState(parent, this, "dcc-file");
+ this.parent.files.push(this);
+
+ // Give ourselves a "me" object for the purposes of displaying stuff.
+ this.me = this.parent.addUser(this.user.netUser.parent.me, "0.0.0.0");
+
+ if ("onInit" in this)
+ this.onInit();
+
+ return this;
+}
+
+CIRCDCCFileTransfer.prototype.TYPE = "IRCDCCFileTransfer";
+
+CIRCDCCFileTransfer.prototype.getURL =
+function dfile_geturl()
+{
+ return "x-irc-dcc-file:" + this.id;
+}
+
+CIRCDCCFileTransfer.prototype.isActive =
+function dfile_isactive()
+{
+ return (this.state.state == DCC_STATE_REQUESTED) ||
+ (this.state.state == DCC_STATE_ACCEPTED) ||
+ (this.state.state == DCC_STATE_CONNECTED);
+}
+
+CIRCDCCFileTransfer.prototype.dispose =
+function dfile_dispose()
+{
+ if (this.connection)
+ {
+ // close is for the server socket, disconnect for the client socket.
+ this.connection.close();
+ this.connection.disconnect();
+ }
+
+ if (this.localFile)
+ this.localFile.close();
+
+ this.connection = null;
+ this.localFile = null;
+ this.filestream = null;
+}
+
+// Call to make this end offer DCC File to targeted user.
+CIRCDCCFileTransfer.prototype.request =
+function dfile_request(localFile)
+{
+ this.state.sendRequest();
+
+ this.localFile = new LocalFile(localFile, "<");
+ this.filename = localFile.leafName;
+ this.size = localFile.fileSize;
+
+ // Update view name.
+ this.viewName = "File: " + this.filename;
+
+ // Double-quote file names with spaces.
+ // FIXME: Do we need any better checking?
+ if (this.filename.match(/ /))
+ this.filename = '"' + this.filename + '"';
+
+ this.localIP = this.parent.localIP;
+
+ this.connection = new CBSConnection(true);
+ if (!this.connection.listen(this.port, this))
+ {
+ this.state.failed();
+ this.dispose();
+ return false;
+ }
+
+ this.port = this.connection.port;
+
+ // Send the CTCP DCC request via the net user (CIRCUser object).
+ var ipNumber;
+ var ipParts = this.localIP.match(/(\d+)\.(\d+)\.(\d+)\.(\d+)/);
+ if (ipParts)
+ ipNumber = Number(ipParts[1]) * 256 * 256 * 256 +
+ Number(ipParts[2]) * 256 * 256 +
+ Number(ipParts[3]) * 256 +
+ Number(ipParts[4]);
+ else
+ return false;
+ // What should we do here? Panic?
+
+ this.user.netUser.ctcp("DCC", "SEND " + this.filename + " " +
+ ipNumber + " " + this.port + " " + this.size);
+
+ return true;
+}
+
+// Call to make this end accept DCC File from target user.
+CIRCDCCFileTransfer.prototype.accept =
+function dfile_accept(localFile)
+{
+ const nsIBinaryOutputStream = Components.interfaces.nsIBinaryOutputStream;
+
+ this.state.sendAccept();
+
+ this.localFile = new LocalFile(localFile, ">");
+ this.localPath = localFile.path;
+
+ this.filestream = Components.classes["@mozilla.org/binaryoutputstream;1"];
+ this.filestream = this.filestream.createInstance(nsIBinaryOutputStream);
+ this.filestream.setOutputStream(this.localFile.outputStream);
+
+ this.position = 0;
+ this.connection = new CBSConnection(true);
+ this.connection.connect(this.remoteIP, this.port, null, this);
+
+ return true;
+}
+
+// This may be called synchronously or asynchronously by CBSConnection.connect.
+CIRCDCCFileTransfer.prototype.onSocketConnection =
+function dfile_onsocketconnection(host, port, config, exception)
+{
+ if (!exception)
+ {
+ this.state.socketConnected();
+
+ this.connection.startAsyncRead(this);
+ }
+ else
+ {
+ this.state.failed();
+ this.dispose();
+ }
+}
+
+// Call to make this end decline DCC File from target user.
+CIRCDCCFileTransfer.prototype.decline =
+function dfile_decline()
+{
+ this.state.sendDecline();
+
+ // Tell the other end, if they care, that we refused.
+ this.user.netUser.ctcp("DCC", "REJECT FILE " + this.filename);
+
+ return true;
+}
+
+// Call to close the connection.
+CIRCDCCFileTransfer.prototype.disconnect =
+function dfile_disconnect()
+{
+ this.dispose();
+
+ return true;
+}
+
+// Aborts the connection.
+CIRCDCCFileTransfer.prototype.abort =
+function dfile_abort()
+{
+ if (this.state.state == DCC_STATE_CONNECTED)
+ {
+ this.disconnect();
+ return;
+ }
+
+ this.state.sendAbort();
+ this.dispose();
+}
+
+// Event to handle a request from the target user.
+// CIRCUser points the event here.
+CIRCDCCFileTransfer.prototype.onGotRequest =
+function dfile_onGotRequest(e)
+{
+ this.state.getRequest();
+
+ // Pass over to the base user.
+ e.destObject = this.user.netUser;
+}
+
+// Event to handle a client connecting to the listening socket.
+// CBSConnection points the event here.
+CIRCDCCFileTransfer.prototype.onSocketAccepted =
+function dfile_onSocketAccepted(socket, transport)
+{
+ this.state.getAccept();
+
+ this.connection.accept(transport, null);
+
+ this.state.socketConnected();
+
+ this.position = 0;
+ this.ackPosition = 0;
+ this.remoteIP = transport.host;
+
+ this.eventPump.addEvent(new CEvent("dcc-file", "connect",
+ this, "onConnect"));
+
+ try {
+ this.filestream = Components.classes["@mozilla.org/binaryinputstream;1"];
+ this.filestream = this.filestream.createInstance(nsIBinaryInputStream);
+ this.filestream.setInputStream(this.localFile.baseInputStream);
+
+ // Start the reading!
+ var d;
+ if (this.parent.sendChunk > this.size)
+ d = this.filestream.readBytes(this.size);
+ else
+ d = this.filestream.readBytes(this.parent.sendChunk);
+ this.position += d.length;
+ this.connection.sendData(d);
+ }
+ catch(ex)
+ {
+ dd(ex);
+ }
+
+ // Start the reading!
+ this.connection.startAsyncRead(this);
+}
+
+CIRCDCCFileTransfer.prototype.onStreamDataAvailable =
+function dfile_sda(request, inStream, sourceOffset, count)
+{
+ var ev = new CEvent("dcc-file", "data-available", this, "onDataAvailable");
+ ev.data = this.connection.readData(0, count);
+ this.eventPump.routeEvent(ev);
+}
+
+CIRCDCCFileTransfer.prototype.onStreamClose =
+function dfile_sockdiscon(status)
+{
+ if (this.position != this.size)
+ this.state.failed();
+ else
+ this.state.socketDisconnected();
+ this.dispose();
+}
+
+CIRCDCCFileTransfer.prototype.onDataAvailable =
+function dfile_dataavailable(e)
+{
+ e.type = "rawdata";
+ e.destMethod = "onRawData";
+
+ try
+ {
+ if (this.state.dir == DCC_DIR_SENDING)
+ {
+ while (e.data.length >= 4)
+ {
+ var word = e.data.substr(0, 4);
+ e.data = e.data.substr(4, e.data.length - 4);
+ var pos = word.charCodeAt(0) * 0x01000000
+ + word.charCodeAt(1) * 0x00010000
+ + word.charCodeAt(2) * 0x00000100
+ + word.charCodeAt(3) * 0x00000001;
+ this.ackPosition = pos;
+ }
+
+ while (this.position <= this.ackPosition + this.parent.maxUnAcked)
+ {
+ var d;
+ if (this.position + this.parent.sendChunk > this.size)
+ d = this.filestream.readBytes(this.size - this.position);
+ else
+ d = this.filestream.readBytes(this.parent.sendChunk);
+
+ this.position += d.length;
+ this.connection.sendData(d);
+
+ if (this.position >= this.size)
+ {
+ this.dispose();
+ break;
+ }
+ }
+ }
+ else if (this.state.dir == DCC_DIR_GETTING)
+ {
+ this.filestream.writeBytes(e.data, e.data.length);
+
+ // Send back ack data.
+ this.position += e.data.length;
+ var bytes = new Array();
+ for (var i = 0; i < 4; i++)
+ bytes.push(Math.floor(this.position / Math.pow(256, i)) & 255);
+
+ this.connection.sendData(String.fromCharCode(bytes[3], bytes[2],
+ bytes[1], bytes[0]));
+
+ if (this.size && (this.position >= this.size))
+ this.disconnect();
+ }
+ this.eventPump.addEvent(new CEvent("dcc-file", "progress",
+ this, "onProgress"));
+ }
+ catch(ex)
+ {
+ this.disconnect();
+ }
+
+ return true;
+}
+
+CIRCDCCFileTransfer.prototype.sendData =
+function dfile_say(data)
+{
+ this.connection.sendData(data);
+}
+
+CIRCDCCFileTransfer.prototype.size = 0;
+CIRCDCCFileTransfer.prototype.position = 0;
+CIRCDCCFileTransfer.prototype.__defineGetter__("progress",
+ function dfile_get_progress()
+ {
+ if (this.size > 0)
+ return Math.floor(100 * this.position / this.size);
+ return 0;
+ });
+