/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is JSIRC Library. * * The Initial Developer of the Original Code is * New Dimensions Consulting, Inc. * Portions created by the Initial Developer are Copyright (C) 1999 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Robert Ginda, rginda@ndcico.com, original author * James Ross, silver@warwickcompsoc.co.uk * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* 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; if (jsenv.HAS_THREAD_MANAGER) { th = getService("@mozilla.org/thread-manager;1").currentThread; } else { const EQS = getService("@mozilla.org/event-queue-service;1", "nsIEventQueueService"); th = EQS.getSpecialEventQueue(EQS.CURRENT_THREAD_EVENT_QUEUE); } 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; } var 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 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.canonicalName; this.remoteIP = remoteIP; this.key = escape(this.canonicalName) + ":" + 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.state = DCC_STATE_INIT; this.dir = DCC_DIR_UNKNOWN; 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.user.remoteIP + ":" + this.port; } // 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(); if (this.connection.connect(this.remoteIP, this.port)) { this.state.socketConnected(); if (jsenv.HAS_NSPR_EVENTQ) this.connection.startAsyncRead(this); else this.eventPump.addEvent(new CEvent("dcc-chat", "poll", this, "onPoll")); } else { this.state.failed(); } return (this.state == DCC_STATE_ACCEPTED); } // 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! if (jsenv.HAS_NSPR_EVENTQ) this.connection.startAsyncRead(this); else this.eventPump.addEvent(new CEvent("dcc-chat", "poll", this, "onPoll")); } CIRCDCCChat.prototype.onPoll = function dchat_poll (e) { var line = ""; var ev; try { line = this.connection.readData(this.READ_TIMEOUT); } catch (ex) { if (typeof (ex) != "undefined") { this.connection.disconnect(); ev = new CEvent("dcc-chat", "close", this, "onClose"); ev.reason = "read-error"; ev.exception = ex; this.eventPump.addEvent(ev); return false; } else line = ""; } this.eventPump.addEvent(new CEvent("dcc-chat", "poll", this, "onPoll")); if (line == "") return false; ev = new CEvent("dcc-chat", "data-available", this, "onDataAvailable"); ev.line = line; this.eventPump.routeEvent(ev); return true; } 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 (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(\S+) ?(.*)\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.state = DCC_STATE_INIT; this.dir = DCC_DIR_UNKNOWN; 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.user.remoteIP + ":" + this.port + "/" + encodeURIComponent(this.filename); } 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.connection = new CBSConnection(true); this.position = 0; if (this.connection.connect(this.remoteIP, this.port)) { this.state.socketConnected(); if (jsenv.HAS_NSPR_EVENTQ) this.connection.startAsyncRead(this); else this.eventPump.addEvent(new CEvent("dcc-file", "poll", this, "onPoll")); } else { this.state.failed(); this.dispose(); } return (this.state == DCC_STATE_ACCEPTED); } // 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! if (jsenv.HAS_NSPR_EVENTQ) this.connection.startAsyncRead(this); else this.eventPump.addEvent(new CEvent("dcc-file", "poll", this, "onPoll")); } CIRCDCCFileTransfer.prototype.onPoll = function dfile_poll (e) { var data = ""; var ev; try { data = this.connection.readData (this.READ_TIMEOUT); } catch (ex) { if (typeof (ex) != "undefined") { this.connection.disconnect(); ev = new CEvent("dcc-file", "close", this, "onClose"); ev.reason = "read-error"; ev.exception = ex; this.eventPump.addEvent(ev); return false; } else data = ""; } this.eventPump.addEvent(new CEvent("dcc-file", "poll", this, "onPoll")); if (data == "") return false; ev = new CEvent("dcc-file", "data-available", this, "onDataAvailable"); ev.data = data; this.eventPump.routeEvent(ev); return true; } 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); }