mirror of
https://github.com/rn10950/RetroZilla.git
synced 2024-11-14 19:50:12 +01:00
1299 lines
34 KiB
JavaScript
1299 lines
34 KiB
JavaScript
/* -*- 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);
|
|
}
|