/* ***** 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 FireFTP. * * Contributor(s): * Mime Cuvalo * * Alternatively, the contents of this file may be used under the terms of * either of 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 ***** */ Components.utils.import("resource://app/modules/urlHelper.jsm"); Components.utils.import("resource://gre/modules/Services.jsm"); var EXPORTED_SYMBOLS = ["ftpMozilla", "ftpDataSocketMozilla"]; function setTimeout(func, delay) { var timer = Components.classes["@mozilla.org/timer;1"] .createInstance(Components.interfaces.nsITimer); timer.initWithCallback(func, delay, Components.interfaces.nsITimer.TYPE_ONE_SHOT); } function ftpMozilla(observer) { this.transportService = Components.classes["@mozilla.org/network/socket-transport-service;1"].getService(Components.interfaces.nsISocketTransportService); this.proxyService = Components.classes["@mozilla.org/network/protocol-proxy-service;1"].getService (Components.interfaces.nsIProtocolProxyService); this.cacheService = Components.classes["@mozilla.org/network/cache-service;1"].getService (Components.interfaces.nsICacheService); this.toUTF8 = Components.classes["@mozilla.org/intl/utf8converterservice;1"].getService (Components.interfaces.nsIUTF8ConverterService); this.fromUTF8 = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].getService (Components.interfaces.nsIScriptableUnicodeConverter); this.observer = observer; this.eventQueue = new Array(); // commands to be sent this.trashQueue = new Array(); // once commands are read, throw them away here b/c we might have to recycle these if there is an error this.listData = new Array(); // holds data directory data from the LIST command var self = this; var func = function() { self.keepAlive(); }; setTimeout(func, 60000); } ftpMozilla.prototype = { // begin: variables you can set host : "", port : 21, security : "", login : "", password : "", passiveMode : true, initialPath : "", // path we go to first onload encoding : "UTF-8", type : '', // what type of FTP connection is this? '' = standard, 'fxp' = FXP, 'transfer' = just for transfers connNo : 1, // connection # fxpHost : null, // the host of an FXP connection timezone : 0, // timezone offset privateKey : "", // private key for sftp connections asciiFiles : new Array(), // set to the list of extensions we treat as ASCII files when transfering fileMode : 0, // 0 == auto, 1 == binary, 2 == ASCII hiddenMode : false, // show hidden files if true ipType : "IPv4", // right now, either IPv4 or IPv6 keepAliveMode : true, // keep the connection alive with NOOP's networkTimeout : 30, // how many seconds b/f we consider the connection to be stale and dead proxyHost : "", proxyPort : 0, proxyType : "", activePortMode : false, // in active mode, if you want to specify a range of ports activeLow : 1, // low port activeHigh : 65535, // high port reconnectAttempts : 40, // how many times we should try reconnecting reconnectInterval : 10, // number of seconds in b/w reconnect attempts reconnectMode : true, // true if we want to attempt reconnecting sessionsMode : true, // true if we're caching directory data timestampsMode : false, // true if we try to keep timestamps in sync useCompression : true, // true if we try to do compression integrityMode : true, // true if we try to do integrity checks errorConnectStr : "Unable to make a connection. Please try again.", // set to error msg that you'd like to show for a connection error errorXCheckFail : "The transfer of this file was unsuccessful and resulted in a corrupted file. It is recommended to restart this transfer.", // an integrity check failure passNotShown : "(password not shown)", // set to text you'd like to show in place of password l10nMonths : new Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"), // used in display localized months // end: variables you can set // variables used internally isConnected : false, // are we connected? isReady : false, // are we busy writing/reading the control socket? isReconnecting : false, // are we attempting a reconnect? legitClose : true, // are we the ones initiating the close or is it a network error reconnectsLeft : 0, // how many times more to try reconnecting networkTimeoutID : 0, // a counter increasing with each read and write transferID : 0, // a counter increasing with each transfer queueID : 0, // another counter increasing with each transfer controlTransport : null, controlInstream : null, controlOutstream : null, pipeTransport : null, // SFTP stuff ipcBuffer : null, isKilling : false, readPoller : 0, doingCmdBatch : false, dataSocket : null, activeCurrentPort : -1, // if user specified a range of ports, this is the current port we're using featMLSD : false, // is the MLSD command available? featMDTM : false, // is the MDTM command available? featXMD5 : false, // is the XMD5 command available? featXSHA1 : false, // is the XSHA1 command available? featXCheck : null, // are the XMD5 or XSHA1 commands available; if so, which one to use? featModeZ : false, // is the MODE Z command available? welcomeMessage : "", // hello world fullBuffer : "", // full response of control socket connectedHost : "", // name of the host we connect to plus username localRefreshLater : '', remoteRefreshLater : '', waitToRefresh : false, transferMode : "", // either "A" or "I" securityMode : "", // either "P" or "C" or "" compressMode : "S", // either "S" or "Z" currentWorkingDir : "", // directory that we're currently, uh, working with version : "1.0.7", // version of this class - used to avoid collisions in cache remoteMonths : "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec", // used in parsing months from list data setSecurity : function(type) { ftpMozilla.prototype.security = type; if (type == "sftp") { for (func in this.sftp) { eval("ftpMozilla.prototype." + func + " = ftpMozilla.prototype.sftp." + func); } } else { for (func in this.ftp) { eval("ftpMozilla.prototype." + func + " = ftpMozilla.prototype.ftp." + func); } } }, onDisconnect : function(sslException) { if (!this.isConnected) { // no route to host if (!sslException && this.observer) { this.observer.onAppendLog(this.errorConnectStr, 'error', "error"); } } this.isConnected = false; if (this.dataSocket) { this.dataSocket.kill(); this.dataSocket = null; } if (this.observer) { this.observer.onDisconnected(!this.legitClose && this.reconnectMode && this.reconnectsLeft > 0); this.observer.onIsReadyChange(true); } if (!this.legitClose && this.reconnectMode) { // try reconnecting this.transferMode = ""; this.securityMode = ""; this.compressMode = "S"; if (this.reconnectsLeft < 1) { this.isReconnecting = false; if (this.eventQueue.length && this.eventQueue[0].cmd == "welcome") { this.eventQueue.shift(); } } else { this.isReconnecting = true; if (this.observer) { this.observer.onReconnecting(); } var self = this; var func = function() { self.reconnect(); }; setTimeout(func, this.reconnectInterval * 1000); } } else { this.legitClose = true; this.cleanup(); } if (this.security == "sftp") { this.kill(); } }, disconnect : function() { this.legitClose = true; // this close() is ok, don't try to reconnect this.cleanup(); if (!(!this.isConnected && this.eventQueue.length && this.eventQueue[0].cmd == "welcome")) { try { this.controlOutstream.write((this.security == "sftp" ? "quit" : "QUIT") + "\r\n", 6); if (this.observer) { this.observer.onAppendLog("" + (this.security == "sftp" ? "quit" : "QUIT"), 'output', "info"); } } catch(ex) { } } if (this.dataSocket) { this.dataSocket.kill(); this.dataSocket = null; } this.kill(); if (this.security == "sftp") { this.onDisconnect(); } }, reconnect : function() { // ahhhh! our precious connection has been lost, if (!this.isReconnecting) { // must...get it...back...our...precious return; } --this.reconnectsLeft; this.connect(true); }, abort : function(forceKill) { this.isReconnecting = false; if (this.dataSocket) { this.dataSocket.progressEventSink.bytesTotal = 0; // stop uploads this.dataSocket.dataListener.bytesTotal = 0; // stop downloads } this.cleanup(true); if (!this.isConnected) { return; } if (forceKill && this.security != "sftp") { try { this.controlOutstream.write("ABOR\r\n", 6); } catch(ex) { } } //XXX this.writeControl("ABOR"); // ABOR does not seem to stop the connection in most cases if (this.dataSocket) { // so this is a more direct approach this.dataSocket.kill(); this.dataSocket = null; } else { this.isReady = true; } this.addEventQueue("aborted"); if (this.observer) { this.observer.onAbort(); } }, cancel : function(forceKill) { // cancel current transfer if (this.security == "sftp") { // can't currently do this with sftp return; } if (this.dataSocket) { this.dataSocket.progressEventSink.bytesTotal = 0; // stop uploads this.dataSocket.dataListener.bytesTotal = 0; // stop downloads } this.trashQueue = new Array(); if (forceKill && this.security != "sftp") { try { if (this.isConnected) { this.controlOutstream.write("ABOR\r\n", 6); } } catch(ex) { } } //XXX this.writeControl("ABOR"); // ABOR does not seem to stop the connection in most cases var dId; if (this.dataSocket && this.isConnected) { // so this is a more direct approach this.dataSocket.kill(); dId = this.dataSocket.id; this.dataSocket = null; } for (var x = 0; x < this.eventQueue.length; ++x) { if (this.eventQueue[x].cmd == "transferEnd" && dId == this.eventQueue[x].callback.id) { this.eventQueue.splice(0, x + 1); break; } } if (this.isConnected) { this.unshiftEventQueue("aborted"); } }, checkTimeout : function(id, cmd) { if (this.isConnected && this.networkTimeoutID == id && this.eventQueue.length && this.eventQueue[0].cmd.indexOf(cmd) != -1) { this.resetConnection(); } }, resetConnection : function() { this.legitClose = false; // still stuck on a command so, try to restart the connection the hard way try { this.controlOutstream.write((this.security == "sftp" ? "quit" : "QUIT") + "\r\n", 6); if (this.observer) { this.observer.onAppendLog("" + (this.security == "sftp" ? "quit" : "QUIT"), 'output', "info"); } } catch(ex) { } if (this.dataSocket) { this.dataSocket.kill(); this.dataSocket = null; } this.kill(); if (this.security == "sftp") { this.isConnected = false; } }, cleanup : function(isAbort) { this.eventQueue = new Array(); this.trashQueue = new Array(); this.transferMode = ""; this.securityMode = ""; this.compressMode = "S"; this.currentWorkingDir = ""; this.localRefreshLater = ""; this.remoteRefreshLater = ""; this.waitToRefresh = false; this.fxpHost = null; this.isReady = false; if (!isAbort) { this.featMLSD = false; this.featMDTM = false; this.featXMD5 = false; this.featXSHA1 = false; this.featXCheck = null; this.featModeZ = false; } ++this.networkTimeoutID; ++this.transferID; }, kill : function() { try { this.controlInstream.close(); } catch(ex) { if (this.observer && this.security != "sftp") { this.observer.onDebug(ex); } } try { this.controlOutstream.close(); } catch(ex) { if (this.observer) { this.observer.onDebug(ex); } } if (this.security == "sftp") { this.sftpKill(); } }, sftpKill : function() { this.isKilling = true; this.eventQueue = []; clearInterval(this.readPoller); try { this.pipeTransport.cancel(-1); } catch(ex) { } try { this.ipcBuffer.shutdown(); } catch(ex) { } try { this.pipeTransport.closeStdin(); } catch(ex) { } try { if (this.getPlatform() == "windows") { var killPath; var firefoxInstallPath = Components.classes["@mozilla.org/file/directory_service;1"].createInstance(Components.interfaces.nsIProperties) .get("CurProcD", Components.interfaces.nsILocalFile); killPath = firefoxInstallPath.path.substring(0, 2) + "/windows/system32/taskkill.exe /IM psftp.exe"; this.ipcService.exec(killPath); } else { var killPath = "/usr/bin/killall -9 psftp"; this.ipcService.exec(killPath); } } catch (ex) { } try { this.pipeTransport.join(); } catch(ex) { } try { this.pipeTransport.terminate(); } catch(ex) { } this.isKilling = false; }, addEventQueue : function(cmd, parameter, callback, callback2) { // this just creates a new queue item this.eventQueue.push( { cmd: cmd, parameter: parameter || '', callback: callback || '', callback2: callback2 || '' }); }, unshiftEventQueue : function(cmd, parameter, callback, callback2) { // ditto this.eventQueue.unshift({ cmd: cmd, parameter: parameter || '', callback: callback || '', callback2: callback2 || '' }); }, beginCmdBatch : function() { this.doingCmdBatch = true; }, writeControlWrapper : function() { if (!this.doingCmdBatch) { this.writeControl(); } }, endCmdBatch : function() { this.doingCmdBatch = false; this.writeControl(); }, writeControl : function(cmd) { try { if (!this.isReady || (!cmd && !this.eventQueue.length)) { return; } var parameter; var callback; var callback2; if (!cmd) { cmd = this.eventQueue[0].cmd; parameter = this.eventQueue[0].parameter; callback = this.eventQueue[0].callback; callback2 = this.eventQueue[0].callback2; } if (cmd == "sftpcache") { cmd = parameter; parameter = ""; callback = "sftpcache"; } if (cmd == "custom") { cmd = parameter; parameter = ""; } while (cmd == "aborted" || cmd == "goodbye" // these are sort of dummy values || cmd == "transferBegin" || cmd == "transferEnd" || (cmd == "TYPE" && this.transferMode == parameter) // or if we ignore TYPE if it's unnecessary || (cmd == "PROT" && this.securityMode == parameter) // or if we ignore PROT if it's unnecessary || (cmd == "MODE" && this.compressMode == parameter) // or if we ignore MODE if it's unnecessary || (cmd == "CWD" && this.currentWorkingDir == parameter // or if we ignore CWD if it's unnecessary && this.type != 'transfer') || (cmd == "cd" && this.currentWorkingDir == parameter)) { if ((cmd == "TYPE" && this.transferMode == parameter) || (cmd == "PROT" && this.securityMode == parameter) || (cmd == "MODE" && this.compressMode == parameter) || (cmd == "CWD" && this.currentWorkingDir == parameter) || (cmd == "cd" && this.currentWorkingDir == parameter)) { this.trashQueue.push(this.eventQueue[0]); } this.eventQueue.shift(); if (this.eventQueue.length) { cmd = this.eventQueue[0].cmd; parameter = this.eventQueue[0].parameter; callback = this.eventQueue[0].callback; callback2 = this.eventQueue[0].callback2; } else { return; } } this.isReady = false; if (this.observer) { this.observer.onIsReadyChange(false); } if (!this.passiveMode && cmd == "PASV") { // active mode cmd = this.ipType == "IPv4" ? "PORT" : "EPRT"; var security = this.security && this.securityMode == "P"; var proxy = { proxyType: this.proxyType, proxyHost: this.proxyHost, proxyPort: this.proxyPort }; var currentPort = this.activeCurrentPort == -1 ? this.activeLow : this.activeCurrentPort + 2; if (currentPort < this.activeLow || currentPort > this.activeHigh) { currentPort = this.activeLow; } this.activeCurrentPort = currentPort; var qId; for (var x = 0; x < this.eventQueue.length; ++x) { if (this.eventQueue[x].cmd == "transferEnd") { qId = this.eventQueue[x].callback.id; break; } } this.dataSocket = new ftpDataSocketMozilla(this.host, this.port, security, proxy, "", this.activePortMode ? currentPort : -1, this.compressMode == "Z", qId, this.observer, this.getCert(), this.fileMode == 2); var activeInfo = {}; activeInfo.cmd = this.eventQueue[1].cmd; activeInfo.ipType = this.ipType; if (this.eventQueue[1].cmd == "RETR") { activeInfo.localPath = this.eventQueue[1].callback; activeInfo.totalBytes = callback; } else if (this.eventQueue[1].cmd == "REST") { activeInfo.localPath = this.eventQueue[2].callback; activeInfo.totalBytes = callback; activeInfo.partialBytes = this.eventQueue[1].parameter; } else if (this.eventQueue[1].cmd == "STOR") { activeInfo.localPath = this.eventQueue[1].callback; } else if (this.eventQueue[1].cmd == "APPE") { activeInfo.localPath = this.eventQueue[1].callback.localPath; activeInfo.partialBytes = this.eventQueue[1].callback.remoteSize; } parameter = this.dataSocket.createServerSocket(activeInfo); } if (cmd == "PASV" && this.passiveMode && this.ipType != "IPv4") { cmd = "EPSV"; } if (cmd == "LIST") { // don't include path in list command - breaks too many things parameter = this.hiddenMode && !this.featMLSD ? "-al" : ""; if (this.featMLSD) { cmd = "MLSD"; } } if (cmd == "ls") { parameter = ""; } if (this.security == "sftp" && parameter && cmd != "chmod" && cmd != "mv" && cmd != "get" && cmd != "reget" && cmd != "put" && cmd != "reput") { parameter = '"' + this.escapeSftp(parameter) + '"'; } var outputData = cmd + (parameter ? (' ' + parameter) : '') + "\r\n"; // le original bug fix! - thanks to devin try { outputData = this.fromUTF8.ConvertFromUnicode(outputData) + this.fromUTF8.Finish(); } catch (ex) { if (this.observer) { this.observer.onDebug(ex); } } this.controlOutstream.write(outputData, outputData.length); // write! if (cmd != "get" && cmd != "reget" && cmd != "put" && cmd != "reput") { ++this.networkTimeoutID; // this checks for timeout var self = this; var currentTimeout = this.networkTimeoutID; var func = function() { self.checkTimeout(currentTimeout, cmd); }; setTimeout(func, this.networkTimeout * 1000); } if ((cmd == "RETR" || cmd == "STOR" || cmd == "APPE") && callback2 != 'fxp') { ++this.transferID; var currentId = this.transferID; var func = function() { self.checkDataTimeout(cmd == "RETR", currentId, 0); }; setTimeout(func, this.networkTimeout * 1000); } outputData = cmd + (parameter ? (' ' + parameter) : ''); // write it out to the log if (callback == "sftpcache") { callback = null; } else if (cmd != "PASS") { if (this.observer) { this.observer.onAppendLog("" + outputData, 'output', "info"); } } else { if (this.observer) { this.observer.onAppendLog("PASS " + this.passNotShown, 'output', "info"); } } } catch(ex) { if (this.observer) { this.observer.onDebug(ex); this.observer.onError(this.errorConnectStr); } } }, refresh : function() { if (this.waitToRefresh) { var self = this; var func = function() { self.refresh(); }; setTimeout(func, 1000); return; } else if (this.eventQueue.length) { return; } if (this.localRefreshLater) { var dir = new String(this.localRefreshLater); this.localRefreshLater = ""; if (this.observer) { this.observer.onShouldRefresh(true, false, dir); } } if (this.remoteRefreshLater) { var dir = new String(this.remoteRefreshLater); this.remoteRefreshLater = ""; if (this.observer) { this.observer.onShouldRefresh(false, true, dir); } } }, parseListData : function(data, path) { /* Unix style: drwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog * Alternate Unix style: drwxr-xr-x 1 user01 ftp 512 Jan 29 1997 prog * Alternate Unix style: drwxr-xr-x 1 1 1 512 Jan 29 23:32 prog * SunOS style: drwxr-xr-x+ 1 1 1 512 Jan 29 23:32 prog * A symbolic link in Unix style: lrwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog -> prog2000 * AIX style: drwxr-xr-x 1 user01 ftp 512 05 Nov 2003 prog * Novell style: drwxr-xr-x 1 user01 512 Jan 29 23:32 prog * Weird style: drwxr-xr-x 1 user5424867 Jan 29 23:32 prog, where 5424867 is the size * Weird style 2: drwxr-xr-x 1 user01 anon5424867 Jan 11 12:48 prog, where 5424867 is the size * MS-DOS style: 01-29-97 11:32PM prog * OS/2 style: 0 DIR 01-29-97 23:32 PROG * OS/2 style: 2243 RA 04-05-103 00:22 PJL * OS/2 style: 60 11-18-104 06:54 chkdsk.log * * MLSD style: type=file;size=6106;modify=20070223082414;UNIX.mode=0644;UNIX.uid=32257;UNIX.gid=32259;unique=808g154c727; prog * type=dir;sizd=4096;modify=20070218021044;UNIX.mode=0755;UNIX.uid=32257;UNIX.gid=32259;unique=808g1550003; prog * type=file;size=4096;modify=20070218021044;UNIX.mode=07755;UNIX.uid=32257;UNIX.gid=32259;unique=808g1550003; prog * type=OS.unix=slink:/blah;size=4096;modify=20070218021044;UNIX.mode=0755;UNIX.uid=32257;UNIX.gid=32259;unique=808g1550003; prog */ try { data = this.toUTF8.convertStringToUTF8(data, this.encoding, 1); } catch (ex) { if (this.observer) { this.observer.onDebug(ex); } } if (this.observer) { this.observer.onDebug(data.replace(//g, '>'), "DEBUG"); } var items = data.indexOf("\r\n") != -1 ? data.split("\r\n") : data.split("\n"); items = items.filter(this.removeBlanks); var curDate = new Date(); if (items.length) { // some ftp servers send 'count ' or 'total ' first if (items[0].indexOf("count") == 0 || items[0].indexOf("total") == 0 || items[0].indexOf("Listing directory") == 0 || (!this.featMLSD && items[0].split(" ").filter(this.removeBlanks).length == 2)) { items.shift(); // could be in german or croatian or what have you } } for (var x = 0; x < items.length; ++x) { if (!items[x]) { // some servers put in blank lines b/w entries, aw, for cryin' out loud items.splice(x, 1); --x; continue; } items[x] = items[x].replace(/^\s+/, ""); // @*$% - some servers put blanks in front, do trimming on front var temp = items[x]; // account for collisions: drwxr-xr-x1017 user01 if (!this.featMLSD) { if (!parseInt(items[x].charAt(0)) && items[x].charAt(0) != '0' && items[x].charAt(10) == '+') { // drwxr-xr-x+ - get rid of the plus sign items[x] = this.setCharAt(items[x], 10, ' '); } if (!parseInt(items[x].charAt(0)) && items[x].charAt(0) != '0' && items[x].charAt(10) != ' ') { // this is mimicked below if weird style items[x] = items[x].substring(0, 10) + ' ' + items[x].substring(10, items[x].length); } items[x] = items[x].split(" ").filter(this.removeBlanks); } if (this.featMLSD) { // MLSD-standard style var newItem = { permissions : "----------", hardLink : "", user : "", group : "", fileSize : "0", date : "", leafName : "", isDir : false, isDirectory : function() { return this.isDir }, isSymlink : function() { return this.symlink != "" }, symlink : "", path : "" }; var pathname = items[x].split("; "); newItem.leafName = ''; for (var y = 1; y < pathname.length; ++y) { newItem.leafName += (y == 1 ? '' : '; ') + pathname[y]; } newItem.path = this.constructPath(path, newItem.leafName); items[x] = pathname[0]; items[x] = items[x].split(";"); var skip = false; for (var y = 0; y < items[x].length; ++y) { if (!items[x][y]) { continue; } var fact = items[x][y].split('='); if (fact.length < 2 || !fact[0] || !fact[1]) { continue; } var factName = fact[0].toLowerCase(); var factVal = fact[1]; switch (factName) { case "type": if (factVal == "pdir" || factVal == "cdir") { skip = true; } else if (factVal == "dir") { newItem.isDir = true; newItem.permissions = this.setCharAt(newItem.permissions, 0, 'd'); } else if (items[x][y].substring(5).indexOf("OS.unix=slink:") == 0) { newItem.symlink = items[x][y].substring(19); newItem.permissions = this.setCharAt(newItem.permissions, 0, 'l'); } else if (factVal != "file") { skip = true; } break; case "size": case "sizd": newItem.fileSize = factVal; break; case "modify": var dateString = factVal.substr(0, 4) + " " + factVal.substr(4, 2) + " " + factVal.substr(6, 2) + " " + factVal.substr(8, 2) + ":" + factVal.substr(10, 2) + ":" + factVal.substr(12, 2) + " GMT"; var zeDate = new Date(dateString); zeDate.setMinutes(zeDate.getMinutes() + this.timezone); var timeOrYear = new Date() - zeDate > 15600000000 ? zeDate.getFullYear() // roughly 6 months : this.zeroPadTime(zeDate.getHours()) + ":" + this.zeroPadTime(zeDate.getMinutes()); newItem.date = this.l10nMonths[zeDate.getMonth()] + ' ' + zeDate.getDate() + ' ' + timeOrYear; newItem.lastModifiedTime = zeDate.getTime(); break; case "unix.mode": var offset = factVal.length == 5 ? 1 : 0; var sticky = this.zeroPad(parseInt(factVal[0 + offset]).toString(2)); var owner = this.zeroPad(parseInt(factVal[1 + offset]).toString(2)); var group = this.zeroPad(parseInt(factVal[2 + offset]).toString(2)); var pub = this.zeroPad(parseInt(factVal[3 + offset]).toString(2)); newItem.permissions = this.setCharAt(newItem.permissions, 1, owner[0] == '1' ? 'r' : '-'); newItem.permissions = this.setCharAt(newItem.permissions, 2, owner[1] == '1' ? 'w' : '-'); newItem.permissions = this.setCharAt(newItem.permissions, 3, sticky[0] == '1' ? (owner[2] == '1' ? 's' : 'S') : (owner[2] == '1' ? 'x' : '-')); newItem.permissions = this.setCharAt(newItem.permissions, 4, group[0] == '1' ? 'r' : '-'); newItem.permissions = this.setCharAt(newItem.permissions, 5, group[1] == '1' ? 'w' : '-'); newItem.permissions = this.setCharAt(newItem.permissions, 6, sticky[1] == '1' ? (group[2] == '1' ? 's' : 'S') : (group[2] == '1' ? 'x' : '-')); newItem.permissions = this.setCharAt(newItem.permissions, 7, pub[0] == '1' ? 'r' : '-'); newItem.permissions = this.setCharAt(newItem.permissions, 8, pub[1] == '1' ? 'w' : '-'); newItem.permissions = this.setCharAt(newItem.permissions, 9, sticky[2] == '1' ? (pub[2] == '1' ? 't' : 'T') : (pub[2] == '1' ? 'x' : '-')); break; case "unix.uid": newItem.user = factVal; break; case "unix.gid": newItem.group = factVal; break; default: break; } if (skip) { break; } } if (skip) { items.splice(x, 1); --x; continue; } items[x] = newItem; } else if (!parseInt(items[x][0].charAt(0)) && items[x][0].charAt(0) != '0') { // unix style - so much simpler with you guys var offset = 0; if (items[x][3].search(this.remoteMonths) != -1 && items[x][5].search(this.remoteMonths) == -1) { var weird = temp; // added to support weird servers if (weird.charAt(10) != ' ') { // same as above code weird = weird.substring(0, 10) + ' ' + weird.substring(10, weird.length); } var weirdIndex = 0; for (var y = 0; y < items[x][2].length; ++y) { if (parseInt(items[x][2].charAt(y))) { weirdIndex = weird.indexOf(items[x][2]) + y; break; } } weird = weird.substring(0, weirdIndex) + ' ' + weird.substring(weirdIndex, weird.length); items[x] = weird.split(" ").filter(this.removeBlanks); } if (items[x][4].search(this.remoteMonths) != -1 && !parseInt(items[x][3].charAt(0))) { var weird = temp; // added to support 'weird 2' servers, oy vey if (weird.charAt(10) != ' ') { // same as above code weird = weird.substring(0, 10) + ' ' + weird.substring(10, weird.length); } var weirdIndex = 0; for (var y = 0; y < items[x][3].length; ++y) { if (parseInt(items[x][3].charAt(y))) { weirdIndex = weird.indexOf(items[x][3]) + y; break; } } weird = weird.substring(0, weirdIndex) + ' ' + weird.substring(weirdIndex, weird.length); items[x] = weird.split(" ").filter(this.removeBlanks); } if (items[x][4].search(this.remoteMonths) != -1) { // added to support novell servers offset = 1; } var index = 0; for (var y = 0; y < 7 - offset; ++y) { index = temp.indexOf(items[x][y], index) + items[x][y].length + 1; } var name = temp.substring(temp.indexOf(items[x][7 - offset], index) + items[x][7 - offset].length + 1, temp.length); name = name.substring(name.search(/[^\s]/)); var symlink = ""; if (items[x][0].charAt(0) == 'l') { symlink = name; if (this.security != "sftp") { name = name.substring(0, name.indexOf("->") - 1); symlink = symlink.substring(symlink.indexOf("->") + 3); } } name = (name.lastIndexOf('/') == -1 ? name : name.substring(name.lastIndexOf('/') + 1)); var remotepath = this.constructPath(path, name); var month; var rawDate = items[x][6 - offset]; if (items[x][6].search(this.remoteMonths) != -1) { // added to support aix servers month = this.remoteMonths.search(items[x][6 - offset]) / 4; rawDate = items[x][5 - offset]; } else { month = this.remoteMonths.search(items[x][5 - offset]) / 4; } var timeOrYear; var curDate = new Date(); var currentYr = curDate.getMonth() < month ? curDate.getFullYear() - 1 : curDate.getFullYear(); var rawYear = items[x][7 - offset].indexOf(':') != -1 ? currentYr : parseInt(items[x][7 - offset]); var rawTime = items[x][7 - offset].indexOf(':') != -1 ? items[x][7 - offset] : "00:00"; rawTime = rawTime.split(":"); for (var y = 0; y < rawTime.length; ++y) { rawTime[y] = parseInt(rawTime[y], 10); } var parsedDate = new Date(rawYear, month, rawDate, rawTime[0], rawTime[1]); // month-day-year format parsedDate.setMinutes(parsedDate.getMinutes() + this.timezone); if (new Date() - parsedDate > 15600000000) { // roughly 6 months timeOrYear = parsedDate.getFullYear(); } else { timeOrYear = this.zeroPadTime(parsedDate.getHours()) + ":" + this.zeroPadTime(parsedDate.getMinutes()); } month = this.l10nMonths[parsedDate.getMonth()]; items[x] = { permissions : items[x][0], hardLink : items[x][1], user : items[x][2], group : (offset ? "" : items[x][3]), fileSize : items[x][4 - offset], date : month + ' ' + parsedDate.getDate() + ' ' + timeOrYear, leafName : name, isDir : items[x][0].charAt(0) == 'd', isDirectory : function() { return this.isDir }, isSymlink : function() { return this.symlink != "" }, symlink : symlink, path : remotepath }; } else if (items[x][0].indexOf('-') == -1) { // os/2 style var offset = 0; if (items[x][2].indexOf(':') != -1) { // if "DIR" and "A" are missing offset = 1; } var rawDate = items[x][2 - offset].split("-"); var rawTime = items[x][3 - offset]; var timeOrYear = rawTime; rawTime = rawTime.split(":"); for (var y = 0; y < rawDate.length; ++y) { rawDate[y] = parseInt(rawDate[y], 10); // leading zeros are treated as octal so pass 10 as base argument } for (var y = 0; y < rawTime.length; ++y) { rawTime[y] = parseInt(rawTime[y], 10); } rawDate[2] = rawDate[2] + 1900; // ah, that's better var parsedDate = new Date(rawDate[2], rawDate[0] - 1, rawDate[1], rawTime[0], rawTime[1]); // month-day-year format parsedDate.setMinutes(parsedDate.getMinutes() + this.timezone); if (new Date() - parsedDate > 15600000000) { // roughly 6 months timeOrYear = parsedDate.getFullYear(); } else { timeOrYear = this.zeroPadTime(parsedDate.getHours()) + ":" + this.zeroPadTime(parsedDate.getMinutes()); } var month = this.l10nMonths[parsedDate.getMonth()]; var name = temp.substring(temp.indexOf(items[x][3 - offset]) + items[x][3 - offset].length + 1, temp.length); name = name.substring(name.search(/[^\s]/)); name = (name.lastIndexOf('/') == -1 ? name : name.substring(name.lastIndexOf('/') + 1)); items[x] = { permissions : items[x][1] == "DIR" ? "d---------" : "----------", hardLink : "", user : "", group : "", fileSize : items[x][0], date : month + ' ' + parsedDate.getDate() + ' ' + timeOrYear, leafName : name, isDir : items[x][1] == "DIR", isDirectory : function() { return this.isDir }, isSymlink : function() { return false }, symlink : "", path : this.constructPath(path, name) }; } else { // ms-dos style var rawDate = items[x][0].split("-"); var amPm = items[x][1].substring(5, 7); // grab PM or AM var rawTime = items[x][1].substring(0, 5); // get rid of PM, AM var timeOrYear = rawTime; rawTime = rawTime.split(":"); for (var y = 0; y < rawDate.length; ++y) { rawDate[y] = parseInt(rawDate[y], 10); } for (var y = 0; y < rawTime.length; ++y) { rawTime[y] = parseInt(rawTime[y], 10); } rawTime[0] = rawTime[0] == 12 && amPm == "AM" ? 0 : (rawTime[0] < 12 && amPm == "PM" ? rawTime[0] + 12 : rawTime[0]); if (rawDate[2] < 70) { // assuming you didn't have some files left over from 1904 rawDate[2] = rawDate[2] + 2000; // ah, that's better } else { rawDate[2] = rawDate[2] + 1900; } var parsedDate = new Date(rawDate[2], rawDate[0] - 1, rawDate[1], rawTime[0], rawTime[1]); // month-day-year format parsedDate.setMinutes(parsedDate.getMinutes() + this.timezone); if (new Date() - parsedDate > 15600000000) { // roughly 6 months timeOrYear = parsedDate.getFullYear(); } else { timeOrYear = this.zeroPadTime(parsedDate.getHours()) + ":" + this.zeroPadTime(parsedDate.getMinutes()); } var month = this.l10nMonths[parsedDate.getMonth()]; var name = temp.substring(temp.indexOf(items[x][2], temp.indexOf(items[x][1]) + items[x][1].length + 1) + items[x][2].length + 1, temp.length); name = name.substring(name.search(/[^\s]/)); name = (name.lastIndexOf('/') == -1 ? name : name.substring(name.lastIndexOf('/') + 1)); items[x] = { permissions : items[x][2] == "" ? "d---------" : "----------", hardLink : "", user : "", group : "", fileSize : items[x][2] == "" ? '0' : items[x][2], date : month + ' ' + parsedDate.getDate() + ' ' + timeOrYear, leafName : name, isDir : items[x][2] == "", isDirectory : function() { return this.isDir }, isSymlink : function() { return false }, symlink : "", path : this.constructPath(path, name) }; } if (!items[x].lastModifiedTime) { var dateTemp = items[x].date; // this helps with sorting by date var dateMonth = dateTemp.substring(0, 3); var dateIndex = this.l10nMonths.indexOf(dateMonth); dateTemp = this.remoteMonths.substr(dateIndex * 4, 3) + dateTemp.substring(3); if (items[x].date.indexOf(':') != -1) { dateTemp = dateTemp + ' ' + (curDate.getFullYear() - (curDate.getMonth() < dateIndex ? 1 : 0)); } items[x].lastModifiedTime = Date.parse(dateTemp); } items[x].fileSize = parseInt(items[x].fileSize); items[x].parent = { path: items[x].path.substring(0, items[x].path.lastIndexOf('/') ? items[x].path.lastIndexOf('/') : 1) }; } var directories = new Array(); // sort directories to the top var files = new Array(); for (var x = 0; x < items.length; ++x) { if (!this.hiddenMode && items[x].leafName.charAt(0) == ".") { // don't show hidden files continue; } items[x].isHidden = items[x].leafName.charAt(0) == "."; items[x].leafName = items[x].leafName.replace(/[\\|\/]/g, ''); // scrub out / or \, a security vulnerability if file tries to do ..\..\blah.txt items[x].path = this.constructPath(path, items[x].leafName); // thanks to Tan Chew Keong for the heads-up if (items[x].leafName == "." || items[x].leafName == "..") { // get rid of "." or "..", this can screw up things on recursive deletions continue; } if (items[x].isDirectory()) { directories.push(items[x]); } else { files.push(items[x]); } } items = directories.concat(files); if (this.sessionsMode) { try { // put in cache var cacheSession = this.cacheService.createSession("fireftp", 1, true); var cacheDesc = cacheSession.openCacheEntry((this.security == "sftp" ? "s" : "") + "ftp://" + this.version + this.connectedHost + path, Components.interfaces.nsICache.ACCESS_WRITE, false); var cacheOut = cacheDesc.openOutputStream(0); var cacheData = items.toSource(); cacheOut.write(cacheData, cacheData.length); cacheOut.close(); cacheDesc.close(); } catch (ex) { if (this.observer) { this.observer.onDebug(ex); } } } return items; }, cacheHit : function(path, callback) { try { // check the cache first var cacheSession = this.cacheService.createSession("fireftp", 1, true); var cacheDesc = cacheSession.openCacheEntry((this.security == "sftp" ? "s" : "") + "ftp://" + this.version + this.connectedHost + path, Components.interfaces.nsICache.ACCESS_READ, false); if (cacheDesc.dataSize) { var cacheIn = cacheDesc.openInputStream(0); var cacheInstream = Components.classes["@mozilla.org/binaryinputstream;1"].createInstance(Components.interfaces.nsIBinaryInputStream); cacheInstream.setInputStream(cacheIn); this.listData = cacheInstream.readBytes(cacheInstream.available()); this.listData = eval(this.listData); cacheInstream.close(); cacheDesc.close(); if (this.observer) { this.observer.onDebug(this.listData.toSource().replace(//g, '>').replace(/, {/g, ',\n{') .replace(/isDirectory:\(function \(\) {return this.isDir;}\), isSymlink:\(function \(\) {return this.symlink != "";}\), /g, ''), "DEBUG-CACHE"); } if (typeof callback == "string") { eval(callback); // send off list data to whoever wanted it } else { callback(); } return true; } cacheDesc.close(); } catch (ex) { } return false; }, removeCacheEntry : function(path) { try { var cacheSession = this.cacheService.createSession("fireftp", 1, true); var cacheDesc = cacheSession.openCacheEntry((this.security == "sftp" ? "s" : "") + "ftp://" + this.version + this.connectedHost + path, Components.interfaces.nsICache.ACCESS_WRITE, false); cacheDesc.doom(); cacheDesc.close(); } catch (ex) { if (this.observer) { this.observer.onDebug(ex); } } }, detectAscii : function(path) { // detect an ascii file - returns "A" or "I" if (this.fileMode == 1) { // binary return "I"; } if (this.fileMode == 2) { // ASCII return "A"; } path = path.substring(path.lastIndexOf('.') + 1); // manually detect for (var x = 0; x < this.asciiFiles.length; ++x) { if (this.asciiFiles[x].toLowerCase() == path.toLowerCase()) { return "A"; } } return "I"; }, constructPath : function(parent, leafName) { return parent + (parent.charAt(parent.length - 1) != '/' ? '/' : '') + leafName; }, removeBlanks : function(element, index, array) { return element; }, zeroPad : function(str) { return str.length == 3 ? str : (str.length == 2 ? '0' + str : '00' + str); }, zeroPadTime : function(num) { num = num.toString(); return num.length == 2 ? num : '0' + num; }, setCharAt : function(str, index, ch) { // how annoying return str.substr(0, index) + ch + str.substr(index + 1); }, setEncoding : function(encoding) { try { this.fromUTF8.charset = encoding; this.encoding = encoding; } catch (ex) { this.fromUTF8.charset = "UTF-8"; this.encoding = "UTF-8"; } }, binaryToHex : function(input) { // borrowed from nsUpdateService.js var result = ""; for (var i = 0; i < input.length; ++i) { var hex = input.charCodeAt(i).toString(16); if (hex.length == 1) { hex = "0" + hex; } result += hex; } return result; }, escapeSftp : function(str) { // thanks to Tan Chew Keong for the heads-up return str.replace(/"/g, '""'); } }; ftpMozilla.prototype.ftp = { connect : function(reconnect) { if (!reconnect) { // this is not a reconnection attempt this.isReconnecting = false; this.reconnectsLeft = parseInt(this.reconnectAttempts); if (!this.reconnectsLeft || this.reconnectsLeft < 1) { this.reconnectsLeft = 1; } } if (!this.eventQueue.length || this.eventQueue[0].cmd != "welcome") { this.unshiftEventQueue("welcome", "", ""); // wait for welcome message first } ++this.networkTimeoutID; // just in case we have timeouts from previous connection ++this.transferID; try { // create a control socket var proxyInfo = null; var self = this; if (this.proxyType != "") { // use a proxy proxyInfo = this.proxyService.newProxyInfo(this.proxyType, this.proxyHost, this.proxyPort, 0, 30, null); } if (this.security == "ssl") { // thanks to Scott Bentley. he's a good man, Jeffrey. and thorough. this.controlTransport = this.transportService.createTransport(["ssl"], 1, this.host, parseInt(this.port), proxyInfo); } else if (!this.security) { this.controlTransport = this.transportService.createTransport(null, 0, this.host, parseInt(this.port), proxyInfo); } else { this.controlTransport = this.transportService.createTransport(["starttls"], 1, this.host, parseInt(this.port), proxyInfo); } if (this.observer && this.observer.securityCallbacks) { this.observer.securityCallbacks.connection = this; this.controlTransport.securityCallbacks = this.observer.securityCallbacks; } this.controlOutstream = this.controlTransport.openOutputStream(0, 0, 0); var controlStream = this.controlTransport.openInputStream(0, 0, 0); this.controlInstream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream); this.controlInstream.init(controlStream); var dataListener = { // async data listener for the control socket data : "", onStartRequest : function(request, context) { }, onStopRequest : function(request, context, status) { self.onDisconnect(); }, onDataAvailable : function(request, context, inputStream, offset, count) { this.data = self.controlInstream.read(count); // read data self.readControl(this.data); } }; var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"].createInstance(Components.interfaces.nsIInputStreamPump); pump.init(controlStream, -1, -1, 0, 0, false); pump.asyncRead(dataListener, null); } catch(ex) { this.onDisconnect(); } }, getCert : function() { try { if (this.security) { return this.controlTransport.securityInfo.QueryInterface(Components.interfaces.nsISSLStatusProvider) .SSLStatus.QueryInterface(Components.interfaces.nsISSLStatus) .serverCert; } } catch(ex) { if (this.observer) { this.observer.onDebug(ex); } } return null; }, checkDataTimeout : function(download, id, bytes) { if (this.isConnected && this.transferID == id && this.dataSocket) { if ((download && bytes == this.dataSocket.dataListener.bytesDownloaded) || (!download && bytes == this.dataSocket.progressEventSink.bytesUploaded)) { this.resetConnection(); return; } var self = this; var nextBytes = download ? self.dataSocket.dataListener.bytesDownloaded : self.dataSocket.progressEventSink.bytesUploaded; var func = function() { self.checkDataTimeout(download, id, nextBytes); }; setTimeout(func, this.networkTimeout * 1000); } }, keepAlive : function() { if (this.isConnected && this.keepAliveMode && this.eventQueue.length == 0) { this.addEventQueue("NOOP"); this.writeControl(); } var self = this; var func = function() { self.keepAlive(); }; setTimeout(func, 60000); }, readControl : function(buffer) { try { buffer = this.toUTF8.convertStringToUTF8(buffer, this.encoding, 1); } catch (ex) { if (this.observer) { this.observer.onDebug(ex); } } if ((buffer == "2" && !this.isConnected) || buffer == "\r\n" || buffer == "\n") { return; } var lastLineOfBuffer = buffer.indexOf("\r\n") != -1 ? buffer.split("\r\n") : buffer.split("\n"); lastLineOfBuffer = lastLineOfBuffer.filter(this.removeBlanks); if (buffer != "2") { // "2"s are self-generated fake messages for (var x = 0; x < lastLineOfBuffer.length; ++x) { // add response to log var message = lastLineOfBuffer[x].charAt(lastLineOfBuffer[x].length - 1) == '\r' ? lastLineOfBuffer[x].substring(0, lastLineOfBuffer[x].length - 1) : lastLineOfBuffer[x]; var errorBlah = lastLineOfBuffer[x].charAt(0) == '4' || lastLineOfBuffer[x].charAt(0) == '5'; if (!errorBlah) { if (this.observer) { this.observer.onAppendLog(message, 'input', "info"); } } } ++this.networkTimeoutID; } lastLineOfBuffer = lastLineOfBuffer[lastLineOfBuffer.length - 1]; // we are only interested in what the last line says var returnCode; if ((lastLineOfBuffer.length > 3 && lastLineOfBuffer.charAt(3) == '-') || lastLineOfBuffer.charAt(0) == ' ') { if (this.eventQueue[0].cmd == "USER" || this.eventQueue[0].cmd == "PASS") { this.welcomeMessage += buffer; // see if the message is finished or not } this.fullBuffer += buffer; return; } else { buffer = this.fullBuffer + buffer; this.fullBuffer = ''; returnCode = parseInt(lastLineOfBuffer.charAt(0)); // looks at first number of number code } var cmd; var parameter; var callback; var callback2; if (this.eventQueue.length) { cmd = this.eventQueue[0].cmd; parameter = this.eventQueue[0].parameter; callback = this.eventQueue[0].callback; callback2 = this.eventQueue[0].callback2; if (cmd != "LIST" && cmd != "RETR" && cmd != "STOR" && cmd != "APPE" // used if we have a loss in connection && cmd != "LIST2" && cmd != "RETR2" && cmd != "STOR2" && cmd != "APPE2") { var throwAway = this.eventQueue.shift(); if (throwAway.cmd != "USER" && throwAway.cmd != "PASS" && throwAway.cmd != "PWD" && throwAway.cmd != "FEAT" && throwAway.cmd != "welcome" && throwAway.cmd != "goodbye" && throwAway.cmd != "aborted" && throwAway.cmd != "NOOP" && throwAway.cmd != "REST" && throwAway.cmd != "SIZE" && throwAway.cmd != "PBSZ" && throwAway.cmd != "AUTH" && throwAway.cmd != "PROT") { this.trashQueue.push(throwAway); } } } else { cmd = "default"; // an unexpected reply - perhaps a 421 timeout message } switch (cmd) { case "welcome": this.welcomeMessage = buffer; if (returnCode != 2) { if (this.observer) { this.observer.onConnectionRefused(); } if (this.type == 'transfer') { this.type = 'bad'; } this.cleanup(); break; } this.isConnected = true; // good to go if (this.observer) { this.observer.onConnected(); } this.isReconnecting = false; this.reconnectsLeft = parseInt(this.reconnectAttempts); // setup reconnection settings if (!this.reconnectsLeft || this.reconnectsLeft < 1) { this.reconnectsLeft = 1; } this.unshiftEventQueue( "USER", this.login, ""); if (this.security) { this.unshiftEventQueue("PBSZ", "0", ""); } if (this.security == "authtls") { this.unshiftEventQueue("AUTH", "TLS", ""); } else if (this.security == "authssl") { this.unshiftEventQueue("AUTH", "SSL", ""); } break; case "AUTH": if (returnCode != 2) { if (this.observer) { this.observer.onError(buffer); } this.isConnected = false; this.kill(); return; } else { var si = this.controlTransport.securityInfo; si.QueryInterface(Components.interfaces.nsISSLSocketControl); si.StartTLS(); } break; case "PBSZ": if (returnCode != 2) { if (this.observer) { this.observer.onError(buffer); } this.isConnected = false; this.kill(); return; } break; case "PROT": if (buffer.substring(0, 3) == "534" && parameter == "P") { if (this.observer) { this.observer.onAppendLog(buffer, 'error', "error"); } this.unshiftEventQueue("PROT", "C", ""); break; } if (returnCode != 2) { if (this.observer) { this.observer.onError(buffer); } } else { this.securityMode = parameter; } break; case "USER": case "PASS": if (returnCode == 2) { if (this.legitClose) { if (this.observer) { this.observer.onWelcomed(); } } var newConnectedHost = this.login + "@" + this.host; if (this.observer) { this.observer.onLoginAccepted(newConnectedHost != this.connectedHost); } if (newConnectedHost != this.connectedHost) { this.legitClose = true; } this.connectedHost = newConnectedHost; // switching to a different host or different login if (!this.legitClose) { this.recoverFromDisaster(); // recover from previous disaster break; } this.legitClose = false; this.unshiftEventQueue("PWD", "", ""); this.unshiftEventQueue("FEAT", "", ""); } else if (cmd == "USER" && returnCode == 3) { this.unshiftEventQueue("PASS", this.password, ""); } else { if (this.observer && this.type == 'transfer') { this.observer.onLoginDenied(); } this.cleanup(); // login failed, cleanup variables if (this.observer && this.type != 'transfer' && this.type != 'bad') { this.observer.onError(buffer); } this.isConnected = false; this.kill(); if (this.type == 'transfer') { this.type = 'bad'; } if (this.observer && this.type != 'transfer' && this.type != 'bad') { var self = this; var func = function() { self.observer.onLoginDenied(); }; setTimeout(func, 0); } return; } break; case "PASV": if (returnCode != 2) { if (this.observer) { this.observer.onError(buffer + ": " + this.constructPath(this.currentWorkingDir, this.eventQueue[(this.eventQueue[0].cmd == "REST" ? 1 : 0)].parameter)); } if (this.observer && this.eventQueue[0].cmd != "LIST") { for (var x = 0; x < this.eventQueue.length; ++x) { if (this.eventQueue[x].cmd == "transferEnd") { this.observer.onTransferFail(this.eventQueue[x].callback, buffer); break; } } } if (this.eventQueue[0].cmd == "LIST") { this.eventQueue.shift(); } else { while (this.eventQueue.length) { if (this.eventQueue[0].cmd == "transferEnd") { this.eventQueue.shift(); break; } this.eventQueue.shift(); } } break; } if (this.passiveMode) { var dataHost; var dataPort; if (callback2 == 'fxp') { callback(buffer.substring(buffer.indexOf("(") + 1, buffer.indexOf(")"))); return; } if (this.ipType == "IPv4") { buffer = buffer.substring(buffer.indexOf("(") + 1, buffer.indexOf(")")); var re = /,/g; buffer = buffer.replace(re, "."); // parsing the port to transfer to var lastDotIndex = buffer.lastIndexOf("."); dataPort = parseInt(buffer.substring(lastDotIndex + 1)); dataPort += 256 * parseInt(buffer.substring(buffer.lastIndexOf(".", lastDotIndex - 1) + 1, lastDotIndex)); dataHost = buffer.substring(0, buffer.lastIndexOf(".", lastDotIndex - 1)); } else { buffer = buffer.substring(buffer.indexOf("(|||") + 4, buffer.indexOf("|)")); dataPort = parseInt(buffer); dataHost = this.host; } var isSecure = this.security && this.securityMode == "P"; var proxy = { proxyType: this.proxyType, proxyHost: this.proxyHost, proxyPort: this.proxyPort }; var qId; for (var x = 0; x < this.eventQueue.length; ++x) { if (this.eventQueue[x].cmd == "transferEnd") { qId = this.eventQueue[x].callback.id; break; } } this.dataSocket = new ftpDataSocketMozilla(this.host, this.port, isSecure, proxy, dataHost, dataPort, this.compressMode == "Z", qId, this.observer, this.getCert(), this.fileMode == 2); if (this.eventQueue[0].cmd == "LIST") { // do what's appropriate this.dataSocket.connect(); } else if (this.eventQueue[0].cmd == "RETR") { this.dataSocket.connect(false, this.eventQueue[0].callback, callback); } else if (this.eventQueue[0].cmd == "REST") { this.dataSocket.connect(false, this.eventQueue[1].callback, callback, this.eventQueue[0].parameter); } else if (this.eventQueue[0].cmd == "STOR") { this.dataSocket.connect(true, this.eventQueue[0].callback, 0, 0); } else if (this.eventQueue[0].cmd == "APPE") { this.dataSocket.connect(true, this.eventQueue[0].callback.localPath, 0, this.eventQueue[0].callback.remoteSize); } } break; case "PORT": // only used with FXP if (returnCode != 2) { if (this.observer) { this.observer.onError(buffer + ": " + this.constructPath(this.currentWorkingDir, this.eventQueue[(this.eventQueue[0].cmd == "REST" ? 1 : 0)].parameter)); } break; } break; case "APPE": case "LIST": case "RETR": case "STOR": this.eventQueue[0].cmd = cmd + "2"; if (callback2 == 'fxp') { if (returnCode == 2) { ++this.transferID; this.eventQueue.shift(); if (this.eventQueue.length && this.eventQueue[0].cmd == "transferEnd") { this.eventQueue.shift(); } this.trashQueue = new Array(); // clear the trash array, completed an 'atomic' set of operations if (callback == 'dest' && (!this.fxpHost.eventQueue.length || (this.fxpHost.eventQueue[0].callback2 != 'fxp' && this.fxpHost.eventQueue[0].callback2 != 'fxpList'))) { this.disconnect(); } break; } if (this.fxpHost) { this.fxpHost.isReady = true; this.fxpHost.writeControlWrapper(); } return; } if (this.dataSocket.emptyFile) { // XXX empty files are (still) special cases this.dataSocket.kill(true); this.dataSocket = null; } if (returnCode == 2) { if (this.dataSocket.finished) { ++this.transferID; this.eventQueue.shift(); if (this.eventQueue.length && this.eventQueue[0].cmd == "transferEnd") { this.eventQueue.shift(); } this.trashQueue = new Array(); // clear the trash array, completed an 'atomic' set of operations if (cmd == "LIST") { this.listData = this.parseListData(this.dataSocket.listData, parameter); if (typeof callback == "string") { eval(callback); // send off list data to whoever wanted it } else { callback(); } } if (callback2) { // for transfers if (typeof callback2 == "string") { eval(callback2); } else { callback2(); } } this.dataSocket = null; break; } else { var self = this; var func = function() { self.readControl("2"); }; setTimeout(func, 500); // give data stream some time to finish up return; } } if (returnCode != 1) { if (this.observer) { this.observer.onError(buffer + ": " + this.constructPath(this.currentWorkingDir, parameter)); } this.eventQueue.shift(); while (this.eventQueue.length && (this.eventQueue[0].cmd == "MDTM" || this.eventQueue[0].cmd == "XMD5" || this.eventQueue[0].cmd == "XSHA1" || this.eventQueue[0].cmd == "transferEnd")) { if (this.eventQueue[0].cmd == "transferEnd" && this.observer) { this.observer.onTransferFail(this.eventQueue[0].callback, buffer); } this.eventQueue.shift(); } this.trashQueue = new Array(); if (this.dataSocket) { this.dataSocket.kill(); this.dataSocket = null; } break; } return; case "APPE2": case "RETR2": case "STOR2": case "LIST2": if (callback2 == 'fxp') { if (returnCode != 2) { if (this.observer) { this.observer.onError(buffer + ": " + this.constructPath(this.currentWorkingDir, parameter)); } } ++this.transferID; this.eventQueue.shift(); if (this.eventQueue.length && this.eventQueue[0].cmd == "transferEnd") { this.eventQueue.shift(); } this.trashQueue = new Array(); // clear the trash array, completed an 'atomic' set of operations if (callback == 'dest' && (!this.fxpHost.eventQueue.length || (this.fxpHost.eventQueue[0].callback2 != 'fxp' && this.fxpHost.eventQueue[0].callback2 != 'fxpList'))) { this.disconnect(); } break; } if (returnCode != 2) { if (this.observer) { this.observer.onError(buffer + ": " + this.constructPath(this.currentWorkingDir, parameter)); } this.eventQueue.shift(); while (this.eventQueue.length && (this.eventQueue[0].cmd == "MDTM" || this.eventQueue[0].cmd == "XMD5" || this.eventQueue[0].cmd == "XSHA1" || this.eventQueue[0].cmd == "transferEnd")) { if (this.eventQueue[0].cmd == "transferEnd" && this.observer) { this.observer.onTransferFail(this.eventQueue[0].callback, buffer); } this.eventQueue.shift(); } this.trashQueue = new Array(); if (this.dataSocket) { this.dataSocket.kill(); this.dataSocket = null; } break; } if (!this.dataSocket || this.dataSocket.finished) { ++this.transferID; this.eventQueue.shift(); if (this.eventQueue.length && this.eventQueue[0].cmd == "transferEnd") { this.eventQueue.shift(); } this.trashQueue = new Array(); // clear the trash array, completed an 'atomic' set of operations } if (cmd == "LIST2" && this.dataSocket.finished) { this.listData = this.parseListData(this.dataSocket.listData, parameter); this.dataSocket = null; if (typeof callback == "string") { eval(callback); // send off list data to whoever wanted it } else { callback(); } } else if ((!this.dataSocket || this.dataSocket.finished) && callback2) { // for transfers this.dataSocket = null; if (typeof callback2 == "string") { eval(callback2); } else { callback2(); } } else if (this.dataSocket && !this.dataSocket.finished) { var self = this; var func = function() { self.readControl("2"); }; setTimeout(func, 500); // give data stream some time to finish up return; } else if (this.dataSocket && this.dataSocket.finished) { this.dataSocket = null; } break; case "SIZE": if (returnCode == 2) { // used with APPE commands to see where to pick up from var size = buffer.split(" ").filter(this.removeBlanks); size = parseInt(size[1]); for (var x = 0; x < this.eventQueue.length; ++x) { if (callback == this.eventQueue[x].cmd) { if (callback == "STOR") { this.eventQueue[x].cmd = "APPE"; this.eventQueue[x].callback = { localPath: this.eventQueue[x].callback, remoteSize: size }; } else if (callback == "APPE") { this.eventQueue[x].callback = { localPath: this.eventQueue[x].callback.localPath, remoteSize: size }; } else if (callback == "PASV") { this.eventQueue[x].callback = size; } break; } } } else { // our size command didn't work out, make sure we're not doing an APPE if (callback != "PASV") { for (var x = 0; x < this.eventQueue.length; ++x) { if (this.eventQueue[x].cmd == "APPE") { this.eventQueue[x].cmd = "STOR"; this.eventQueue[x].callback = this.eventQueue[x].callback.localPath; break; } } } if (this.observer) { this.observer.onAppendLog(buffer, 'error', "error"); } } break; case "XMD5": case "XSHA1": if (returnCode == 2) { var zeHash = buffer.split(" ").filter(this.removeBlanks); zeHash = zeHash[1].replace(/\n|\r/g, "").toLowerCase(); if (typeof callback == "function") { callback(zeHash); break; } try { var file = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile); file.initWithPath(callback); var cryptoHash = cmd == "XMD5" ? Components.interfaces.nsICryptoHash.MD5 : Components.interfaces.nsICryptoHash.SHA1; var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream); fstream.init(file, 1, 0, false); var gHashComp = Components.classes["@mozilla.org/security/hash;1"].createInstance(Components.interfaces.nsICryptoHash); gHashComp.init(cryptoHash); gHashComp.updateFromStream(fstream, -1); var ourHash = this.binaryToHex(gHashComp.finish(false)).toLowerCase(); fstream.close(); if (ourHash != zeHash) { if (this.observer) { this.observer.onError("'" + callback + "' - " + this.errorXCheckFail); for (var x = 0; x < this.eventQueue.length; ++x) { if (this.eventQueue[x].cmd == "transferEnd") { this.observer.onTransferFail(this.eventQueue[x].callback, "checksum"); break; } } } } } catch (ex) { if (this.observer) { this.observer.onDebug(ex); } } } else { // our size command didn't work out, make sure we're not doing an APPE if (this.observer) { this.observer.onAppendLog(buffer, 'error', "error"); } } break; case "MDTM": if (returnCode == 2) { var zeDate = buffer.split(" ").filter(this.removeBlanks); zeDate = zeDate[1]; try { var fileHandler = UrlUtils.getFileProtocolHandler(); var file = fileHandler.getFileFromURLSpec(callback).QueryInterface(Components.interfaces.nsILocalFile); file.lastModifiedTime = Date.parse(zeDate.substr(0, 4) + " " + zeDate.substr(4, 2) + " " + zeDate.substr(6, 2) + " " + zeDate.substr(8, 2) + ":" + zeDate.substr(10, 2) + ":" + zeDate.substr(12, 2) + " GMT"); } catch (ex) { if (this.observer) { this.observer.onDebug(ex); } } } else { // our size command didn't work out, make sure we're not doing an APPE if (this.observer) { this.observer.onAppendLog(buffer, 'error', "error"); } } break; case "RNFR": case "REST": if (returnCode != 3) { if (cmd == "RNFR") { this.eventQueue = new Array(); this.trashQueue = new Array(); } if (this.observer) { this.observer.onError(buffer); // should still be able to go on without this, just not with resuming } break; } break; case "MKD": case "SITE CHMOD": case "RNTO": case "DELE": case "RMD": if (returnCode != 2) { if (this.observer) { this.observer.onError(buffer + ": " + this.constructPath(this.currentWorkingDir, parameter)); } } else { if (cmd == "RMD") { // clear out of cache if it's a remove directory this.removeCacheEntry(this.constructPath(this.currentWorkingDir, parameter)); } if (typeof callback == "string") { eval(callback); // send off list data to whoever wanted it } else { callback(); } } this.trashQueue = new Array(); break; case "CWD": if (returnCode != 2) { // if it's not a directory if (callback && typeof callback == "function") { callback(false); } else if (this.type == 'transfer') { this.observer.onDebug(buffer); this.unshiftEventQueue(cmd, parameter, callback, callback2); var self = this; var func = function() { self.nextCommand(); }; setTimeout(func, 500); // give main connection time to create the directory return; } else if (this.observer) { this.observer.onDirNotFound(buffer); if (this.observer) { this.observer.onError(buffer); } } } else { this.currentWorkingDir = parameter; if (this.observer) { // else navigate to the directory this.observer.onChangeDir(parameter, typeof callback == "boolean" ? callback : ""); } if (callback && typeof callback == "function") { callback(true); } } break; case "PWD": // gotta check for chrooted directories if (returnCode != 2) { if (this.observer) { this.observer.onError(buffer); } } else { buffer = buffer.substring(buffer.indexOf("\"") + 1, buffer.lastIndexOf("\"")); // if buffer is not '/' we're chrooted this.currentWorkingDir = buffer; if (this.observer) { this.observer.onChangeDir(buffer != '/' && this.initialPath == '' ? buffer : '', false, buffer != '/' || this.initialPath != ''); } if (this.type == 'fxp') { this.list(this.initialPath ? this.initialPath : this.currentWorkingDir); } } this.trashQueue = new Array(); break; case "FEAT": if (returnCode != 2) { if (this.observer) { this.observer.onAppendLog(buffer, 'error', "error"); } } else { // XXX glazou: following line is wrong because some servers use both \r\n and \n //buffer = buffer.indexOf("\r\n") != -1 ? buffer.split("\r\n") : buffer.split("\n"); buffer = buffer.replace(/\\r\\n/g, "\n").split("\n"); for (var x = 0; x < buffer.length; ++x) { if (buffer[x] && buffer[x][0] == ' ') { var feat = buffer[x].trim().toUpperCase(); if (feat == "MDTM") { this.featMDTM = true; } else if (feat == "MLSD") { this.featMLSD = true; } else if (feat.indexOf("MODE Z") == 0) { this.featModeZ = true; } else if (feat.indexOf("XSHA1") == 0) { this.featXCheck = "XSHA1"; this.featXSHA1 = true; } else if (feat.indexOf("XMD5") == 0 && !this.featXCheck) { this.featXCheck = "XMD5"; } if (feat.indexOf("XMD5") == 0) { this.featXMD5 = true; } } } } break; case "aborted": break; case "TYPE": if (returnCode != 2) { if (this.observer) { this.observer.onError(buffer); } } else { this.transferMode = parameter; } break; case "MODE": if (returnCode != 2) { if (this.observer) { this.observer.onError(buffer); } } else { this.compressMode = parameter; } break; case "goodbye": // you say yes, i say no, you stay stop... case "NOOP": default: if (buffer.substring(0, 3) != "421" && returnCode != 2) { if (this.observer) { this.observer.onError(buffer); } } break; } this.nextCommand(); }, nextCommand : function() { this.isReady = true; if (this.observer) { this.observer.onIsReadyChange(true); } if (this.eventQueue.length && this.eventQueue[0].cmd != "welcome") { // start the next command this.writeControl(); } else { this.refresh(); } }, changeWorkingDirectory : function(path, callback) { this.addEventQueue("CWD", path, callback); this.writeControlWrapper(); }, makeDirectory : function(path, callback) { this.addEventQueue("CWD", path.substring(0, path.lastIndexOf('/') ? path.lastIndexOf('/') : 1), true); this.addEventQueue("MKD", path.substring(path.lastIndexOf('/') + 1), callback); this.writeControlWrapper(); }, makeBlankFile : function(path, callback) { this.addEventQueue("CWD", path.substring(0, path.lastIndexOf('/') ? path.lastIndexOf('/') : 1), true); try { var count = 0; let tmpFile = Components.classes["@mozilla.org/file/directory_service;1"].createInstance(Components.interfaces.nsIProperties).get("TmpD", Components.interfaces.nsILocalFile); tmpFile.append(count + '-blankFile'); while (tmpFile.exists()) { ++count; tmpFile.leafName = count + '-blankFile'; } var foutstream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream); foutstream.init(tmpFile, 0x04 | 0x08 | 0x20, 0644, 0); foutstream.write("", 0); foutstream.close(); this.upload(tmpFile.path, path, false, 0, 0, callback, true); } catch (ex) { if (this.observer) { this.observer.onDebug(ex); } } }, remove : function(isDirectory, path, callback) { if (isDirectory) { this.unshiftEventQueue("RMD", path.substring(path.lastIndexOf('/') + 1), callback); this.unshiftEventQueue("CWD", path.substring(0, path.lastIndexOf('/') ? path.lastIndexOf('/') : 1), true); this.unshiftEventQueue("CWD", path.replace(/[^\/]+/g , ".."), true); var self = this; var listCallback = function() { self.removeRecursive(path); }; this.list(path, listCallback, true, true); } else { this.unshiftEventQueue("DELE", path.substring(path.lastIndexOf('/') + 1), callback); this.unshiftEventQueue("CWD", path.substring(0, path.lastIndexOf('/') ? path.lastIndexOf('/') : 1), true); } this.writeControlWrapper(); }, removeRecursive : function(parent) { // delete subdirectories and files var files = this.listData; for (var x = 0; x < files.length; ++x) { var remotePath = this.constructPath(parent, files[x].leafName); if (files[x].isDirectory()) { // delete a subdirectory recursively this.unshiftEventQueue("RMD", remotePath.substring(remotePath.lastIndexOf('/') + 1), ""); this.unshiftEventQueue("CWD", parent, true); this.removeRecursiveHelper(remotePath); } else { // delete a file this.unshiftEventQueue("DELE", remotePath.substring(remotePath.lastIndexOf('/') + 1), ""); this.unshiftEventQueue("CWD", parent, true); } } }, removeRecursiveHelper : function(remotePath) { var self = this; var listCallback = function() { self.removeRecursive(remotePath); }; this.list(remotePath, listCallback, true, true); }, rename : function(oldName, newName, callback, isDir) { if (isDir) { this.removeCacheEntry(oldName); } this.addEventQueue("RNFR", oldName); // rename the file this.addEventQueue("RNTO", newName, callback); this.writeControlWrapper(); }, changePermissions : function(permissions, path, callback) { this.addEventQueue("CWD", path.substring(0, path.lastIndexOf('/') ? path.lastIndexOf('/') : 1), true); this.addEventQueue("SITE CHMOD", permissions + ' ' + path.substring(path.lastIndexOf('/') + 1), callback); this.writeControlWrapper(); }, custom : function(cmd) { this.addEventQueue(cmd); this.writeControlWrapper(); }, list : function(path, callback, skipCache, recursive, fxp) { if (!skipCache && this.sessionsMode) { if (this.cacheHit(path, callback)) { return; } } var callback2 = fxp ? 'fxpList' : ''; if (recursive) { this.unshiftEventQueue( "LIST", path, callback, callback2); this.unshiftEventQueue( "PASV", "", "", callback2); this.unshiftEventQueue( "CWD", path, "", callback2); if (this.security) { this.unshiftEventQueue("PROT", "P", "", callback2); } this.unshiftEventQueue( "MODE", this.useCompression && this.featModeZ ? "Z" : "S", null, callback2); this.unshiftEventQueue( "TYPE", "A", "", callback2); } else { this.addEventQueue( "TYPE", "A", "", callback2); this.addEventQueue( "MODE", this.useCompression && this.featModeZ ? "Z" : "S", null, callback2); if (this.security) { this.addEventQueue( "PROT", "P", "", callback2); } this.addEventQueue( "CWD", path, "", callback2); this.addEventQueue( "PASV", "", "", callback2); this.addEventQueue( "LIST", path, callback, callback2); } this.writeControlWrapper(); }, download : function(remotePath, localPath, remoteSize, resume, localSize, isSymlink, callback, disableMDTM) { ++this.queueID; var id = this.connNo + "-" + this.queueID; this.addEventQueue("transferBegin", "", { id: id }); this.addEventQueue( "CWD", remotePath.substring(0, remotePath.lastIndexOf('/') ? remotePath.lastIndexOf('/') : 1), true); var leafName = remotePath.substring(remotePath.lastIndexOf('/') + 1); var ascii = this.detectAscii(remotePath); this.addEventQueue( "TYPE", ascii); this.addEventQueue( "MODE", this.useCompression && this.featModeZ ? "Z" : "S"); if (isSymlink) { this.addEventQueue("SIZE", leafName, "PASV"); // need to do a size check } if (this.security) { this.addEventQueue("PROT", "P"); } this.addEventQueue( "PASV", "", remoteSize, remoteSize); if (resume && ascii != 'A') { this.addEventQueue("REST", localSize); } this.addEventQueue( "RETR", leafName, localPath, callback); if (this.integrityMode && this.featXCheck && ascii != 'A') { this.addEventQueue(this.featXCheck, '"' + leafName + '"', localPath); } if (this.timestampsMode && this.featMDTM && !disableMDTM) { this.addEventQueue("MDTM", leafName, localPath); } this.addEventQueue("transferEnd", "", { localPath: localPath, remotePath: remotePath, size: remoteSize, transport: 'ftp', type: 'download', ascii: ascii, id: id }); this.writeControlWrapper(); }, upload : function(localPath, remotePath, resume, localSize, remoteSize, callback, disableMDTM) { ++this.queueID; var id = this.connNo + "-" + this.queueID; this.addEventQueue("transferBegin", "", { id: id }); this.addEventQueue( "CWD", remotePath.substring(0, remotePath.lastIndexOf('/') ? remotePath.lastIndexOf('/') : 1), true); var leafName = remotePath.substring(remotePath.lastIndexOf('/') + 1); var ascii = this.detectAscii(remotePath); this.addEventQueue( "TYPE", ascii); this.addEventQueue( "MODE", this.useCompression && this.featModeZ && ascii != 'A' ? "Z" : "S"); // XXX can't do compression with ascii mode in upload currently if (resume && ascii != 'A') { this.addEventQueue("SIZE", leafName, "APPE"); // need to do a size check } if (this.security) { this.addEventQueue("PROT", "P"); } this.addEventQueue( "PASV", null, null, localSize); if (resume && ascii != 'A') { this.addEventQueue("APPE", leafName, { localPath: localPath, remoteSize: remoteSize }, callback); } else { this.addEventQueue("STOR", leafName, localPath, callback); } if (this.integrityMode && this.featXCheck && ascii != 'A') { this.addEventQueue(this.featXCheck, '"' + leafName + '"', localPath); } if (this.timestampsMode && this.featMDTM && !disableMDTM) { this.addEventQueue("MDTM", leafName, localPath); } this.addEventQueue("transferEnd", "", { localPath: localPath, remotePath: remotePath, size: localSize, transport: 'ftp', type: 'upload', ascii: ascii, id: id }); this.writeControlWrapper(); return id; }, fxp : function(hostPath, destPath, resume, destSize, hostSize) { ++this.fxpHost.queueID; var id = this.fxpHost.connNo + "-" + this.fxpHost.queueID; var leafName = hostPath.substring(hostPath.lastIndexOf('/') + 1); var self = this; var func = function(hostPort) { self.fxpCallback(hostPort, destPath, resume, destSize, id); }; this.fxpHost.addEventQueue("transferBegin", "", { id: id }, 'fxp'); this.fxpHost.addEventQueue( "CWD", hostPath.substring(0, hostPath.lastIndexOf('/') ? hostPath.lastIndexOf('/') : 1), true, 'fxp'); var ascii = this.detectAscii(hostPath); this.fxpHost.addEventQueue( "TYPE", ascii, null, 'fxp'); this.fxpHost.addEventQueue( "MODE", this.useCompression && this.fxpHost.featModeZ && this.featModeZ ? "Z" : "S", null, 'fxp'); if (resume && ascii != 'A') { this.fxpHost.addEventQueue("REST", destSize, null, 'fxp'); } this.fxpHost.addEventQueue( "PASV", "", func, 'fxp'); this.fxpHost.addEventQueue( "RETR", leafName, 'host', 'fxp'); this.fxpHost.addEventQueue("transferEnd", "", { localPath: hostPath, remotePath: destPath, size: hostSize, transport: 'fxp', type: 'fxp', ascii: ascii, id: id }, 'fxp'); this.fxpHost.writeControlWrapper(); }, fxpCallback : function(hostPort, destPath, resume, destSize, id) { var leafName = destPath.substring(destPath.lastIndexOf('/') + 1); this.fxpHost.addEventQueue("transferBegin", "", { id: id }, 'fxp'); this.addEventQueue( "CWD", destPath.substring(0, destPath.lastIndexOf('/') ? destPath.lastIndexOf('/') : 1), true, 'fxp'); this.addEventQueue( "TYPE", this.detectAscii(leafName), null, 'fxp'); this.addEventQueue( "MODE", this.useCompression && this.fxpHost.featModeZ && this.featModeZ ? "Z" : "S", null, 'fxp'); if (resume) { this.addEventQueue( "REST", destSize, null, 'fxp'); } this.addEventQueue( "PORT", hostPort, null, 'fxp'); this.addEventQueue( "STOR", leafName, 'dest', 'fxp'); this.fxpHost.addEventQueue("transferEnd", "", { transport: 'fxp', type: 'fxp', id: id }, 'fxp'); this.writeControlWrapper(); }, isListing : function() { // check queue to see if we're listing for (var x = 0; x < this.eventQueue.length; ++x) { if (this.eventQueue[x].cmd.indexOf("LIST") != -1) { return true; } } return false; }, recoverFromDisaster : function() { // after connection lost, try to restart queue if (this.eventQueue.length && this.eventQueue[0].cmd == "goodbye") { this.eventQueue.shift(); } if (this.eventQueue.cmd) { this.eventQueue = new Array(this.eventQueue); } while (this.eventQueue.length && (this.eventQueue[0].callback2 == "fxp" || this.eventQueue[0].callback2 == "fxpList")) { this.eventQueue.shift(); } if (this.eventQueue.length && (this.eventQueue[0].cmd == "LIST" || this.eventQueue[0].cmd == "LIST2" || this.eventQueue[0].cmd == "RETR" || this.eventQueue[0].cmd == "RETR2" || this.eventQueue[0].cmd == "REST" || this.eventQueue[0].cmd == "APPE" || this.eventQueue[0].cmd == "STOR" || this.eventQueue[0].cmd == "STOR2" || this.eventQueue[0].cmd == "PASV" || this.eventQueue[0].cmd == "APPE2" || this.eventQueue[0].cmd == "SIZE")) { var cmd = this.eventQueue[0].cmd; var parameter = this.eventQueue[0].parameter; if (cmd == "LIST2" || cmd == "RETR2" || cmd == "STOR2" || cmd == "APPE2") { this.eventQueue[0].cmd = this.eventQueue[0].cmd.substring(0, 4); } cmd = this.eventQueue[0].cmd; if (cmd == "REST") { // set up resuming for these poor interrupted transfers try { var file = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile); file.initWithPath(this.eventQueue[1].callback); if (file.fileSize) { this.eventQueue[0].parameter = file.fileSize; } } catch (ex) { if (this.observer) { this.observer.onDebug(ex); } } } else if (cmd == "RETR") { try { var file = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile); file.initWithPath(this.eventQueue[0].callback); if (file.fileSize) { this.unshiftEventQueue("REST", file.fileSize, ""); } } catch (ex) { if (this.observer) { this.observer.onDebug(ex); } } } for (var x = this.trashQueue.length - 1; x >= 0; --x) { // take cmds out of the trash and put them back in the eventQueue if (this.trashQueue[x].cmd == "TYPE" && (cmd == "STOR" || cmd == "APPE")) { // more resuming fun - this time for the stor/appe commandds this.unshiftEventQueue("SIZE", parameter, cmd); } this.eventQueue.unshift(this.trashQueue[x]); } } else if (this.eventQueue.length && this.eventQueue[0].cmd == "RNTO" && this.trashQueue[this.trashQueue.length - 1].cmd == "RNFR") { this.unshiftEventQueue("RNFR", this.trashQueue[this.trashQueue.length - 1].parameter); } if (this.currentWorkingDir) { this.unshiftEventQueue("CWD", this.currentWorkingDir, true); this.currentWorkingDir = ""; } this.trashQueue = new Array(); } }; function ftpDataSocketMozilla(controlHost, controlPort, security, proxy, host, port, compress, id, observer, cert, asciiMode) { this.transportService = Components.classes["@mozilla.org/network/socket-transport-service;1"].getService(Components.interfaces.nsISocketTransportService); this.proxyService = Components.classes["@mozilla.org/network/protocol-proxy-service;1"].getService (Components.interfaces.nsIProtocolProxyService); this.dnsService = Components.classes["@mozilla.org/network/dns-service;1"].getService (Components.interfaces.nsIDNSService); this.eventTarget = Components.classes["@mozilla.org/thread-manager;1"].getService ().currentThread; this.security = security || false; this.host = (security ? controlHost : (host || "")); this.port = port || -1; this.proxyType = proxy ? proxy.proxyType : ""; this.proxyHost = proxy ? proxy.proxyHost : ""; this.proxyPort = proxy ? proxy.proxyPort : -1; this.useCompression = compress; this.dataListener = new dataListener(); this.progressEventSink = new progressEventSink(); this.id = id; this.observer = observer; this.asciiMode = asciiMode; if (security) { try { this.certOverride = Components.classes["@mozilla.org/security/certoverride;1"].getService(Components.interfaces.nsICertOverrideService); var hashAlg = {}; var fingerprint = {}; var overrideBits = {}; var isTemporary = {}; var ok = this.certOverride.getValidityOverride(controlHost, controlPort, hashAlg, fingerprint, overrideBits, isTemporary); this.certOverride.rememberValidityOverride(this.host, port, cert, overrideBits.value, true); } catch (ex) { if (this.observer) { this.observer.onDebug(ex); } } } } ftpDataSocketMozilla.prototype = { dataTransport : null, dataInstream : null, dataOutstream : null, fileInstream : null, serverSocket : null, listData : "", finished : true, emptyFile : false, // XXX empty files are (still) special cases connect : function(write, localPath, fileTotalBytes, filePartialBytes, activeTransport) { try { if (activeTransport) { this.dataTransport = activeTransport; } else { var proxyInfo = this.proxyType == "" ? null : this.proxyService.newProxyInfo(this.proxyType, this.proxyHost, this.proxyPort, 0, 30, null); if (this.security) { this.dataTransport = this.transportService.createTransport(["ssl"], 1, this.host, this.port, proxyInfo); } else { this.dataTransport = this.transportService.createTransport(null, 0, this.host, this.port, proxyInfo); } } this.finished = false; if (write) { // upload this.dataOutstream = this.dataTransport.openOutputStream(0, 0, -1); var file; try { file = UrlUtils.newLocalFile(localPath); this.fileInstream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(); this.fileInstream.QueryInterface(Components.interfaces.nsIFileInputStream); this.fileInstream.init(file, 0x01, 0644, 0); this.fileInstream.QueryInterface(Components.interfaces.nsISeekableStream); this.fileInstream.seek(0, filePartialBytes); // append or not to append } catch (ex) { if (this.observer) { this.observer.onDebug(ex); } if (this.observer) { this.observer.onError(gStrbundle.getFormattedString("failedUpload", [localPath])); } this.kill(); return; } var binaryOutstream = Components.classes["@mozilla.org/binaryoutputstream;1"].createInstance(Components.interfaces.nsIBinaryOutputStream); binaryOutstream.setOutputStream(this.dataOutstream); this.dataInstream = Components.classes["@mozilla.org/binaryinputstream;1"].createInstance(Components.interfaces.nsIBinaryInputStream); this.dataInstream.setInputStream(this.fileInstream); this.progressEventSink.parent = this; this.progressEventSink.localPath = localPath; this.progressEventSink.sendPrevSent = 0; this.progressEventSink.timeStart = new Date(); this.progressEventSink.bytesTotal = file.fileSize; this.progressEventSink.bytesUploaded = this.useCompression ? 0 : filePartialBytes; this.progressEventSink.bytesPartial = filePartialBytes; this.progressEventSink.dataInstream = this.dataInstream; this.progressEventSink.dataOutstream = binaryOutstream; this.progressEventSink.fileInstream = this.fileInstream; this.progressEventSink.asciiMode = this.asciiMode; this.emptyFile = !file.fileSize; this.dataTransport.setEventSink(this.progressEventSink, this.eventTarget); if (this.useCompression && file.fileSize) { // never as elegant as downloading :( this.progressEventSink.compressStream = true; var streamConverter = Components.classes["@mozilla.org/streamconv;1?from=uncompressed&to=deflate"].createInstance(Components.interfaces.nsIStreamConverter); streamConverter.asyncConvertData("uncompressed", "deflate", this.progressEventSink, null); var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"].createInstance(Components.interfaces.nsIInputStreamPump); pump.init(this.dataInstream, -1, -1, 0, 0, false); pump.asyncRead(streamConverter, null); } else { var dataBuffer = this.dataInstream.readBytes(this.dataInstream.available() < 4096 ? this.dataInstream.available() : 4096); var diff = dataBuffer.length; if (this.asciiMode) { dataBuffer = dataBuffer.replace(/(^|[^\r])\n/g, "$1\r\n"); } this.progressEventSink.bytesTotal += dataBuffer.length - diff; this.progressEventSink.dataOutstream.writeBytes(dataBuffer, dataBuffer.length); } } else { // download this.listData = ""; var dataStream = this.dataTransport.openInputStream(0, 0, 0); var streamConverter; this.dataInstream = Components.classes["@mozilla.org/binaryinputstream;1"].createInstance(Components.interfaces.nsIBinaryInputStream); if (this.useCompression) { streamConverter = Components.classes["@mozilla.org/streamconv;1?from=deflate&to=uncompressed"].createInstance(Components.interfaces.nsIStreamConverter); streamConverter.asyncConvertData("deflate", "uncompressed", this.dataListener, null); } else { this.dataInstream.setInputStream(dataStream); } this.dataListener.parent = this; this.dataListener.localPath = localPath; this.dataListener.dataInstream = this.dataInstream; this.dataListener.data = ""; this.dataListener.file = ""; this.dataListener.fileOutstream = ""; this.dataListener.binaryOutstream = ""; this.dataListener.bytesTotal = fileTotalBytes || 0; this.dataListener.bytesDownloaded = filePartialBytes || 0; this.dataListener.bytesPartial = filePartialBytes || 0; this.dataListener.timeStart = new Date(); this.dataListener.dataBuffer = ""; this.dataListener.isNotList = localPath != null; this.dataListener.useCompression = this.useCompression; this.dataListener.asciiMode = this.asciiMode; var pump = Components.classes["@mozilla.org/network/input-stream-pump;1"].createInstance(Components.interfaces.nsIInputStreamPump); pump.init(dataStream, -1, -1, 0, 0, false); pump.asyncRead(this.useCompression ? streamConverter : this.dataListener, null); } } catch(ex) { if (this.observer) { this.observer.onDebug(ex); } if (this.observer) { this.observer.onError(gStrbundle.getString("errorDataConn")); } return; } }, createServerSocket : function(activeInfo) { try { var ipAddress = this.dnsService.resolve(this.dnsService.myHostName, false).getNextAddrAsString(); var re = /\x2e/g; this.serverSocket = Components.classes["@mozilla.org/network/server-socket;1"].createInstance(Components.interfaces.nsIServerSocket); var self = this; var serverListener = { onSocketAccepted : function(serv, transport) { if (activeInfo.cmd == "LIST") { self.connect(false, null, 0, 0, transport); } else if (activeInfo.cmd == "RETR") { self.connect(false, activeInfo.localPath, activeInfo.totalBytes, 0, transport); } else if (activeInfo.cmd == "REST") { self.connect(false, activeInfo.localPath, activeInfo.totalBytes, activeInfo.partialBytes, transport); } else if (activeInfo.cmd == "STOR") { self.connect(true, activeInfo.localPath, 0, 0, transport); } else if (activeInfo.cmd == "APPE") { self.connect(true, activeInfo.localPath, 0, activeInfo.partialBytes, transport); } }, onStopListening : function(serv, status) { } }; this.serverSocket.init(this.port, false, -1); this.serverSocket.asyncListen(serverListener); if (activeInfo.ipType == "IPv4" && ipAddress.indexOf(':') == -1) { return ipAddress.replace(re, ",") + "," + parseInt(this.serverSocket.port / 256) + "," + this.serverSocket.port % 256; } else { return (ipAddress.indexOf(':') != -1 ? "|2|" : "|1|") + ipAddress + "|" + this.serverSocket.port + "|"; } } catch (ex) { if (this.observer) { this.observer.onDebug(ex); } if (this.observer) { this.observer.onError(gStrbundle.getString("errorDataConn")); } return null; } }, kill : function(override) { this.progressEventSink.bytesTotal = 0; // stop uploads this.dataListener.bytesTotal = 0; // stop downloads try { if (this.dataInstream && this.dataInstream.close) { this.dataInstream.close(); } } catch(ex) { } try { if ((!this.emptyFile || override) && this.dataOutstream && this.dataOutstream.flush) { this.dataOutstream.flush(); } if ((!this.emptyFile || override) && this.dataOutstream && this.dataOutstream.close) { this.dataOutstream.close(); } } catch(ex) { } try { if ((!this.emptyFile || override) && this.fileInstream && this.fileInstream.close) { this.fileInstream.close(); } } catch(ex) { } try { if ((!this.emptyFile || override)) { // XXX empty files are (still) special cases if (this.dataTransport && this.dataTransport.close) { this.dataTransport.close("Finished"); } } } catch(ex) { } try { if (this.dataListener.binaryOutstream && this.dataListener.binaryOutstream.close) { this.dataListener.binaryOutstream.close(); } } catch(ex) { } try { if (this.dataListener.fileOutstream && this.dataListener.fileOutstream.close) { this.dataListener.fileOutstream.close(); } } catch(ex) { } try { if (this.serverSocket && this.serverSocket.close) { this.serverSocket.close(); } } catch(ex) { } this.progressEventSink.parent = null; // stop memory leakage! this.dataListener.parent = null; // stop memory leakage! this.finished = true; if (this.security) { try { this.certOverride.clearValidityOverride(this.host, this.port); } catch (ex) { if (this.observer) { this.observer.onDebug(ex); } } } } }; function dataListener() { } dataListener.prototype = { parent : null, localPath : "", dataInstream : "", data : "", file : "", fileOutstream : "", binaryOutstream : "", bytesTotal : 0, bytesDownloaded : 0, bytesPartial : 0, timeStart : new Date(), dataBuffer : "", isNotList : false, useCompression : false, asciiMode : false, onStartRequest : function(request, context) { if (this.isNotList) { this.timeStart = new Date(); try { this.file = UrlUtils.newLocalFile(this.localPath); this.fileOutstream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream); if (this.bytesPartial) { this.fileOutstream.init(this.file, 0x04 | 0x10, 0644, 0); } else { this.fileOutstream.init(this.file, 0x04 | 0x08 | 0x20, 0644, 0); } this.binaryOutstream = Components.classes["@mozilla.org/binaryoutputstream;1"].createInstance(Components.interfaces.nsIBinaryOutputStream); this.binaryOutstream.setOutputStream(this.fileOutstream); } catch (ex) { this.failure(ex); } } }, onStopRequest : function(request, context, status) { if (!this.isNotList && this.parent) { this.parent.listData = this.data; } if (this.parent) { this.parent.kill(); } }, onDataAvailable : function(request, context, inputStream, offset, count) { if (this.useCompression) { this.dataInstream.setInputStream(inputStream); } if (this.isNotList) { try { this.dataBuffer = this.dataInstream.readBytes(count); var length = this.dataBuffer.length; if (this.asciiMode && this.getPlatform() != "windows") { this.dataBuffer = this.dataBuffer.replace(/\r\n/g, '\n'); } this.binaryOutstream.writeBytes(this.dataBuffer, this.dataBuffer.length) this.bytesDownloaded += length; } catch (ex) { this.failure(ex); } } else { this.data += this.dataInstream.readBytes(count); } }, failure : function(ex) { if (this.parent.observer) { this.parent.observer.onDebug(ex); } if (this.parent.observer) { this.parent.observer.onError(gStrbundle.getFormattedString("failedSave", [this.localPath])); } this.parent.kill(); }, getPlatform : function() { var platform = navigator.platform.toLowerCase(); if (platform.indexOf('linux') != -1) { return 'linux'; } if (platform.indexOf('mac') != -1) { return 'mac'; } return 'windows'; } }; function progressEventSink() { } progressEventSink.prototype = { parent : null, localPath : "", bytesTotal : 0, sendPrevSent : 0, bytesUploaded : 0, timeStart : new Date(), bytesPartial : 0, dataOutstream : null, fileInstream : null, compressFirst : true, compressStream : false, compressTotal : 0, compressDone : false, compressBuffer : "", asciiMode : false, onStartRequest : function(request, context) { }, onStopRequest : function(request, context, status) { this.compressDone = true; }, onDataAvailable : function(request, context, inputStream, offset, count) { try { var dataInstream = Components.classes["@mozilla.org/binaryinputstream;1"].createInstance(Components.interfaces.nsIBinaryInputStream); dataInstream.setInputStream(inputStream); this.compressTotal += count; this.compressBuffer += dataInstream.readBytes(count); if (this.compressFirst) { this.compressFirst = false; this.dataOutstream.writeBytes(this.compressBuffer, this.compressBuffer.length); this.compressBuffer = ""; } } catch (ex) { this.failure(ex); } }, onTransportStatus : function (transport, status, progress, progressMax) { this.bytesUploaded += progress - this.sendPrevSent; this.sendPrevSent = progress; if ((!this.compressStream && this.bytesUploaded == this.bytesTotal) || (this.compressStream && this.compressDone && this.bytesUploaded == this.compressTotal)) { // finished writing this.parent.kill(); // can't rely on this.fileInstream.available() - corrupts uploads return; } if (this.compressStream) { this.dataOutstream.writeBytes(this.compressBuffer, this.compressBuffer.length); this.compressBuffer = ""; } else { var dataBuffer = this.dataInstream.readBytes(this.dataInstream.available() < 4096 ? this.dataInstream.available() : 4096); var diff = dataBuffer.length; if (this.asciiMode) { dataBuffer = dataBuffer.replace(/(^|[^\r])\n/g, "$1\r\n"); } this.bytesTotal += dataBuffer.length - diff; this.dataOutstream.writeBytes(dataBuffer, dataBuffer.length); } }, failure : function(ex) { if (this.parent.observer) { this.parent.observer.onDebug(ex); } if (this.parent.observer) { this.parent.observer.onError(gStrbundle.getFormattedString("failedUpload", [this.localPath])); } this.parent.kill(); } }; ftpMozilla.prototype.sftp = { connect : function(reconnect) { if (!reconnect) { // this is not a reconnection attempt this.isReconnecting = false; this.reconnectsLeft = parseInt(this.reconnectAttempts); if (!this.reconnectsLeft || this.reconnectsLeft < 1) { this.reconnectsLeft = 1; } } if (!this.eventQueue.length || this.eventQueue[0].cmd != "welcome") { this.unshiftEventQueue("welcome", "", ""); // wait for welcome message first } ++this.networkTimeoutID; // just in case we have timeouts from previous connection ++this.transferID; try { var exec = this.getExec(); if (!exec || !exec.exists()) { this.onDisconnect(); return; } this.ipcService = Components.classes["@mozilla.org/process/ipc-service;1"].getService(Components.interfaces.nsIIPCService); this.pipeTransport = Components.classes["@mozilla.org/process/pipe-transport;1"].createInstance(Components.interfaces.nsIPipeTransport); this.ipcBuffer = Components.classes["@mozilla.org/process/ipc-buffer;1"].createInstance(Components.interfaces.nsIIPCBuffer); this.ipcBuffer.open(65536, true); var command = exec.path.replace(/\x5c/g, "/"); var args = []; if (this.password) { args.push("-pw"); args.push(this.password); } args.push("-P"); args.push(this.port); if (this.useCompression) { args.push("-C"); } if (this.privatekey) { args.push("-i"); args.push(this.privatekey.replace(/\x5c/g, "/")); } args.push((this.login ? this.login + "@" : "") + this.host); this.pipeTransport.init(command, args, args.length, [], 0, 0, "", true, true, this.ipcBuffer); this.controlOutstream = this.pipeTransport.openOutputStream(0, 0, 0); var self = this; var dataListener; var func = function() { if (!self.pipeTransport.isAttached()) { self.onDisconnect(); } if (dataListener.data) { if (dataListener.data.indexOf('\npsftp>') == -1 && dataListener.data.indexOf('\nStore key in cache') == -1 && dataListener.data.indexOf('\nUpdate cached key') == -1 && dataListener.data.indexOf('\nAccess denied') == -1 && dataListener.data.indexOf('Fatal: Network error:') == -1 && dataListener.data.indexOf('ssh_init:') != 0) { return; } var buf = dataListener.data; dataListener.data = ""; self.readControl(buf); } }; this.readPoller = setInterval(func, 100); dataListener = { data : "", onStartRequest : function(request, context) { }, onStopRequest : function(request, context, status) { }, onDataAvailable : function(request, context, inputStream, offset, count) { var controlInstream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream); controlInstream.init(inputStream); this.data += controlInstream.read(count); } }; this.pipeTransport.asyncRead(dataListener, null, 0, 0, 0); } catch(ex) { this.onDisconnect(); } }, keepAlive : function() { // do nothing }, readControl : function(buffer) { if (this.isKilling) { return; } try { buffer = this.toUTF8.convertStringToUTF8(buffer, this.encoding, 1); } catch (ex) { if (this.observer) { this.observer.onDebug(ex); } } if ((buffer == "2" && !this.isConnected) || buffer == "\r\n" || buffer == "\n") { return; } buffer = buffer.replace(/\r\npsftp> /, ''); buffer = buffer.replace(/\npsftp> /, ''); var origBuffer = buffer; buffer = buffer.indexOf("\r\n") != -1 ? buffer.split("\r\n") : buffer.split("\n"); buffer = buffer.filter(this.removeBlanks); if (origBuffer != "2" && origBuffer.indexOf('Store key in cache') == -1 // "2"s are self-generated fake messages && origBuffer.indexOf('\nUpdate cached key') == -1 && origBuffer.indexOf('Fatal: Network error:') == -1 && origBuffer.indexOf('\nAccess denied') == -1 && origBuffer.indexOf('ssh_init:') != 0) { for (var x = 0; x < buffer.length; ++x) { // add response to log var message = buffer[x].charAt(buffer[x].length - 1) == '\r' ? buffer[x].substring(0, buffer[x].length - 1) : buffer[x]; if (this.observer) { this.observer.onAppendLog(message, 'input', "info"); } if (message.indexOf('Listing directory') != -1) { break; } } ++this.networkTimeoutID; } var cmd; var parameter; var callback; var callback2; if (this.eventQueue.length) { cmd = this.eventQueue[0].cmd; parameter = this.eventQueue[0].parameter; callback = this.eventQueue[0].callback; callback2 = this.eventQueue[0].callback2; if (cmd != "ls" && cmd != "get" && cmd != "reget" && cmd != "put" && cmd != "reput") { // used if we have a loss in connection var throwAway = this.eventQueue.shift(); if (throwAway.cmd != "welcome" && throwAway.cmd != "goodbye" && throwAway.cmd != "aborted" && throwAway.cmd != "sftpcache") { this.trashQueue.push(throwAway); } } } else { cmd = "default"; // an unexpected reply - perhaps a 421 timeout message } switch (cmd) { case "welcome": case "sftpcache": if (origBuffer.indexOf('Fatal: Network error:') != -1 || origBuffer.indexOf('ssh_init:') == 0) { this.legitClose = true; this.onDisconnect(); return; } if (origBuffer.indexOf('Store key in cache') != -1 || origBuffer.indexOf('\nUpdate cached key') != -1) { if (this.observer) { var answer = this.observer.onSftpCache(origBuffer); if (answer) { this.unshiftEventQueue("sftpcache", answer, ""); } else { this.legitClose = true; this.onDisconnect(); return; } break; } } this.isConnected = true; // good to go if (this.observer) { this.observer.onConnected(); } this.isReconnecting = false; this.reconnectsLeft = parseInt(this.reconnectAttempts); // setup reconnection settings if (!this.reconnectsLeft || this.reconnectsLeft < 1) { this.reconnectsLeft = 1; } var newConnectedHost = this.login + "@" + this.host; if (this.observer) { this.observer.onLoginAccepted(newConnectedHost != this.connectedHost); } if (newConnectedHost != this.connectedHost) { this.legitClose = true; } this.connectedHost = newConnectedHost; // switching to a different host or different login if (!this.legitClose) { this.recoverFromDisaster(); // recover from previous disaster break; } this.legitClose = false; origBuffer = origBuffer.substring(origBuffer.indexOf("Remote working directory is") + 28); // if buffer is not '/' we're chrooted this.currentWorkingDir = origBuffer; if (this.observer) { this.observer.onChangeDir(origBuffer != '/' && this.initialPath == '' ? origBuffer : '', false, origBuffer != '/' || this.initialPath != ''); } this.trashQueue = new Array(); break; case "ls": case "get": case "reget": case "put": case "reput": ++this.transferID; this.eventQueue.shift(); if (this.eventQueue.length && this.eventQueue[0].cmd == "transferEnd") { this.eventQueue.shift(); } this.trashQueue = new Array(); // clear the trash array, completed an 'atomic' set of operations if (cmd == "ls") { this.listData = this.parseListData(origBuffer, parameter); if (typeof callback == "string") { eval(callback); // send off list data to whoever wanted it } else { callback(); } } if (callback2) { // for transfers if (typeof callback2 == "string") { eval(callback2); } else { callback2(); } } break; case "mkdir": case "rm": case "rmdir": if (origBuffer.indexOf(': OK') != origBuffer.length - 4) { if (this.observer) { this.observer.onError(origBuffer + ": " + this.constructPath(this.currentWorkingDir, parameter)); } } else { if (cmd == "rmdir") { // clear out of cache if it's a remove directory this.removeCacheEntry(this.constructPath(this.currentWorkingDir, parameter)); } if (typeof callback == "string") { eval(callback); // send off list data to whoever wanted it } else { callback(); } } this.trashQueue = new Array(); break; case "mv": if (origBuffer.indexOf(': no such file or directory') != -1) { if (this.observer) { this.observer.onError(origBuffer + ": " + parameter); } } else { if (typeof callback == "string") { eval(callback); // send off list data to whoever wanted it } else { callback(); } } this.trashQueue = new Array(); break; case "chmod": if (typeof callback == "string") { eval(callback); // send off list data to whoever wanted it } else { callback(); } this.trashQueue = new Array(); break; case "cd": if (origBuffer.indexOf('Remote directory is now') == -1) { // if it's not a directory if (callback && typeof callback == "function") { callback(false); } else if (this.observer) { this.observer.onDirNotFound(origBuffer); if (this.observer) { this.observer.onError(origBuffer); } } } else { this.currentWorkingDir = parameter; if (this.observer) { // else navigate to the directory this.observer.onChangeDir(parameter, typeof callback == "boolean" ? callback : ""); } if (callback && typeof callback == "function") { callback(true); } } break; case "aborted": case "custom": break; case "goodbye": // you say yes, i say no, you stay stop... default: if (origBuffer.indexOf('Access denied') != -1) { if (this.observer && this.type == 'transfer') { this.observer.onLoginDenied(); } this.cleanup(); // login failed, cleanup variables if (this.observer && this.type != 'transfer' && this.type != 'bad') { this.observer.onError(origBuffer); } this.isConnected = false; this.kill(); if (this.type == 'transfer') { this.type = 'bad'; } if (this.observer && this.type != 'transfer' && this.type != 'bad') { var self = this; var func = function() { self.observer.onLoginDenied(); }; setTimeout(func, 0); } return; } else if (this.observer) { this.observer.onError(origBuffer); } break; } this.isReady = true; if (this.observer) { this.observer.onIsReadyChange(true); } if (this.eventQueue.length && this.eventQueue[0].cmd != "welcome") { // start the next command this.writeControl(); } else { this.refresh(); } }, changeWorkingDirectory : function(path, callback) { this.addEventQueue("cd", path, callback); this.writeControlWrapper(); }, makeDirectory : function(path, callback) { this.addEventQueue("cd", path.substring(0, path.lastIndexOf('/') ? path.lastIndexOf('/') : 1), true); this.addEventQueue("mkdir", path.substring(path.lastIndexOf('/') + 1), callback); this.writeControlWrapper(); }, makeBlankFile : function(path, callback) { this.addEventQueue("cd", path.substring(0, path.lastIndexOf('/') ? path.lastIndexOf('/') : 1), true); try { var count = 0; let tmpFile = Components.classes["@mozilla.org/file/directory_service;1"].createInstance(Components.interfaces.nsIProperties).get("TmpD", Components.interfaces.nsILocalFile); tmpFile.append(count + '-blankFile'); while (tmpFile.exists()) { ++count; tmpFile.leafName = count + '-blankFile'; } var foutstream = Components.classes["@mozilla.org/network/file-output-stream;1"].createInstance(Components.interfaces.nsIFileOutputStream); foutstream.init(tmpFile, 0x04 | 0x08 | 0x20, 0644, 0); foutstream.write("", 0); foutstream.close(); this.upload(tmpFile.path, path, false, 0, 0, callback, true); } catch (ex) { if (this.observer) { this.observer.onDebug(ex); } } }, remove : function(isDirectory, path, callback) { if (isDirectory) { this.unshiftEventQueue("rmdir", path.substring(path.lastIndexOf('/') + 1), callback); this.unshiftEventQueue("cd", path.substring(0, path.lastIndexOf('/') ? path.lastIndexOf('/') : 1), true); var self = this; var listCallback = function() { self.removeRecursive(path); }; this.list(path, listCallback, true, true); } else { this.unshiftEventQueue("rm", path.substring(path.lastIndexOf('/') + 1), callback); this.unshiftEventQueue("cd", path.substring(0, path.lastIndexOf('/') ? path.lastIndexOf('/') : 1), true); } this.writeControlWrapper(); }, removeRecursive : function(parent) { // delete subdirectories and files var files = this.listData; for (var x = 0; x < files.length; ++x) { var remotePath = this.constructPath(parent, files[x].leafName); if (files[x].isDirectory()) { // delete a subdirectory recursively this.unshiftEventQueue("rmdir", remotePath.substring(remotePath.lastIndexOf('/') + 1), ""); this.unshiftEventQueue("cd", parent, true); this.removeRecursiveHelper(remotePath); } else { // delete a file this.unshiftEventQueue("rm", remotePath.substring(remotePath.lastIndexOf('/') + 1), ""); this.unshiftEventQueue("cd", parent, true); } } }, removeRecursiveHelper : function(remotePath) { var self = this; var listCallback = function() { self.removeRecursive(remotePath); }; this.list(remotePath, listCallback, true, true); }, rename : function(oldName, newName, callback, isDir) { if (isDir) { this.removeCacheEntry(oldName); } oldName = oldName.replace(/\[/g, "\\[").replace(/\]/g, "\\]"); this.addEventQueue("mv", '"' + this.escapeSftp(oldName) + '" "' + this.escapeSftp(newName) + '"', callback); // rename the file this.writeControlWrapper(); }, changePermissions : function(permissions, path, callback) { path = path.replace(/\[/g, "\\[").replace(/\]/g, "\\]"); this.addEventQueue("cd", path.substring(0, path.lastIndexOf('/') ? path.lastIndexOf('/') : 1), true); this.addEventQueue("chmod", permissions + ' "' + this.escapeSftp(path.substring(path.lastIndexOf('/') + 1)) + '"', callback); this.writeControlWrapper(); }, custom : function(cmd) { this.addEventQueue("custom", cmd); this.writeControlWrapper(); }, list : function(path, callback, skipCache, recursive, fxp) { if (!skipCache && this.sessionsMode) { if (this.cacheHit(path, callback)) { return; } } if (recursive) { this.unshiftEventQueue( "ls", path, callback, ''); this.unshiftEventQueue( "cd", path, "", ''); } else { this.addEventQueue( "cd", path, "", ''); this.addEventQueue( "ls", path, callback, ''); } this.writeControlWrapper(); }, download : function(remotePath, localPath, remoteSize, resume, localSize, isSymlink, callback) { ++this.queueID; var id = this.connNo + "-" + this.queueID; this.addEventQueue("transferBegin", "", { id: id }); this.addEventQueue( "cd", remotePath.substring(0, remotePath.lastIndexOf('/') ? remotePath.lastIndexOf('/') : 1), true); var leafName = remotePath.substring(remotePath.lastIndexOf('/') + 1); this.addEventQueue(resume ? "reget" : "get", '"' + this.escapeSftp(leafName) + '" "' + this.escapeSftp(localPath.replace(/\x5c/g, "/")) + '"', localPath, callback); this.addEventQueue("transferEnd", "", { localPath: localPath, remotePath: remotePath, size: remoteSize, transport: 'sftp', type: 'download', ascii: "I", id: id }); this.writeControlWrapper(); }, upload : function(localPath, remotePath, resume, localSize, remoteSize, callback, disableMDTM) { ++this.queueID; var id = this.connNo + "-" + this.queueID; this.addEventQueue("transferBegin", "", { id: id }); this.addEventQueue( "cd", remotePath.substring(0, remotePath.lastIndexOf('/') ? remotePath.lastIndexOf('/') : 1), true); var leafName = remotePath.substring(remotePath.lastIndexOf('/') + 1); this.addEventQueue(resume ? "reput" : "put", '"' + this.escapeSftp(localPath.replace(/\x5c/g, "/")) + '" "' + this.escapeSftp(leafName) + '"', localPath, callback); this.addEventQueue("transferEnd", "", { localPath: localPath, remotePath: remotePath, size: localSize, transport: 'sftp', type: 'upload', ascii: "I", id: id }); this.writeControlWrapper(); return id; }, isListing : function() { // check queue to see if we're listing for (var x = 0; x < this.eventQueue.length; ++x) { if (this.eventQueue[x].cmd.indexOf("ls") != -1) { return true; } } return false; }, recoverFromDisaster : function() { // after connection lost, try to restart queue if (this.eventQueue.length && this.eventQueue[0].cmd == "goodbye") { this.eventQueue.shift(); } if (this.eventQueue.cmd) { this.eventQueue = new Array(this.eventQueue); } if (this.eventQueue.length && (this.eventQueue[0].cmd == "ls" || this.eventQueue[0].cmd == "get" || this.eventQueue[0].cmd == "reget" || this.eventQueue[0].cmd == "put" || this.eventQueue[0].cmd == "reput")) { var cmd = this.eventQueue[0].cmd; var parameter = this.eventQueue[0].parameter; cmd = this.eventQueue[0].cmd; if (cmd == "put") { // set up resuming for these poor interrupted transfers this.eventQueue[0].cmd = "reput"; } else if (cmd == "get") { this.eventQueue[0].cmd = "reget"; } } if (this.currentWorkingDir) { this.unshiftEventQueue("cd", this.currentWorkingDir, true); this.currentWorkingDir = ""; } this.trashQueue = new Array(); }, getExec : function() { if (this.getPlatform() == "windows") { var exec = Components.classes["@mozilla.org/file/directory_service;1"].createInstance(Components.interfaces.nsIProperties) .get("ProfD", Components.interfaces.nsILocalFile); exec.append("extensions"); exec.append("{a7c6cf7f-112c-4500-a7ea-39801a327e5f}"); exec.append("platform"); exec.append("WINNT_x86-msvc"); exec.append("psftp.exe"); return exec; } else if (this.getPlatform() == "linux") { var file = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile); file.initWithPath("/usr/bin/psftp"); return file; } else if (this.getPlatform() == "mac") { var file = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile); file.initWithPath("/opt/local/var/macports/software/putty"); if (!file.exists()) { return file; } var subdirs = []; var entries = file.directoryEntries; // find highest version number while (entries.hasMoreElements()) { subdirs.push(entries.getNext().QueryInterface(Components.interfaces.nsILocalFile)); } subdirs.sort(compareName); subdirs.reverse(); if (!subdirs.length) { return file; } file.append(subdirs[0].leafName); file.append("opt"); file.append("local"); file.append("bin"); file.append("psftp"); return file; } }, getPlatform : function() { var platform = navigator.platform.toLowerCase(); if (platform.indexOf('linux') != -1) { return 'linux'; } if (platform.indexOf('mac') != -1) { return 'mac'; } return 'windows'; } };