PageRenderTime 91ms CodeModel.GetById 33ms RepoModel.GetById 5ms app.codeStats 0ms

/src/content/js/connection/baseProtocol.js

https://gitlab.com/web-optimize/fireftp
JavaScript | 1154 lines | 910 code | 213 blank | 31 comment | 292 complexity | 1b9bb84cd9efecdc8b09ad8b0afa2408 MD5 | raw file
  1. // if you're actually interested in reusing this class for your app
  2. // it'd be a good idea to get in contact with me, Mime Cuvalo: mimecuvalo@gmail.com
  3. function baseProtocol() {
  4. this.eventQueue = []; // commands to be sent
  5. this.trashQueue = []; // once commands are read, throw them away here b/c we might have to recycle these if there is an error
  6. this.listData = []; // holds data directory data from the LIST command
  7. }
  8. baseProtocol.prototype = {
  9. // read-write variables
  10. protocol : "",
  11. observer : null,
  12. host : "",
  13. port : -1,
  14. security : "",
  15. login : "",
  16. password : "",
  17. initialPath : "", // path we go to first onload
  18. encoding : "UTF-8",
  19. type : '', // what type of FTP connection is this? '' = master connection, 'fxp' = FXP/server-to-server, 'transfer' = transfer-only/slave connection
  20. connNo : 1, // connection #
  21. timezone : 0, // timezone offset
  22. hiddenMode : false, // show hidden files if true
  23. keepAliveMode : true, // keep the connection alive with NOOP's
  24. networkTimeout : 30, // how many seconds b/f we consider the connection to be stale and dead
  25. proxyHost : "",
  26. proxyPort : 0,
  27. proxyType : "",
  28. reconnectAttempts : 40, // how many times we should try reconnecting
  29. reconnectInterval : 10, // number of seconds in b/w reconnect attempts
  30. reconnectMode : true, // true if we want to attempt reconnecting
  31. sessionsMode : true, // true if we're caching directory data
  32. timestampsMode : false, // true if we try to keep timestamps in sync
  33. useCompression : true, // true if we try to do compression
  34. integrityMode : true, // true if we try to do integrity checks
  35. errorConnectStr : "Unable to make a connection. Please try again.", // set to error msg that you'd like to show for a connection error
  36. 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
  37. passNotShown : "(password not shown)", // set to text you'd like to show in place of password
  38. l10nMonths : new Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"), // used in display localized months
  39. // read-only variables
  40. transportService : Components.classes["@mozilla.org/network/socket-transport-service;1"].getService(Components.interfaces.nsISocketTransportService),
  41. proxyService : Components.classes["@mozilla.org/network/protocol-proxy-service;1"].getService (Components.interfaces.nsIProtocolProxyService),
  42. cacheService : Components.classes["@mozilla.org/network/cache-service;1"].getService (Components.interfaces.nsICacheService),
  43. toUTF8 : Components.classes["@mozilla.org/intl/utf8converterservice;1"].getService (Components.interfaces.nsIUTF8ConverterService),
  44. fromUTF8 : Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].getService (Components.interfaces.nsIScriptableUnicodeConverter),
  45. isAttemptingConnect : false,
  46. isConnected : false, // are we connected?
  47. isReady : false, // are we busy writing/reading the control socket?
  48. isReconnecting : false, // are we attempting a reconnect?
  49. legitClose : true, // are we the ones initiating the close or is it a network error
  50. reconnectsLeft : 0, // how many times more to try reconnecting
  51. networkTimeoutID : 0, // a counter increasing with each read and write
  52. transferID : 0, // a counter increasing with each transfer
  53. queueID : 0, // another counter increasing with each transfer
  54. controlTransport : null,
  55. controlInstream : null,
  56. controlOutstream : null,
  57. dataSocket : null, // only used with (*ahem* lame *ahem*) protocols like FTP that need a second socket
  58. doingCmdBatch : false,
  59. connectedHost : "", // name of the host we connect to plus username
  60. localRefreshLater : '',
  61. remoteRefreshLater : '',
  62. waitToRefresh : false,
  63. currentWorkingDir : "", // directory that we're currently, uh, working with
  64. version : "", // version of this class - used to avoid collisions in cache
  65. remoteMonths : "Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec", // used in parsing months from list data
  66. // functions that should be implemented by derived classes
  67. connect : function(reconnect) { alert('NOT_IMPLEMENTED'); },
  68. cleanup : function(isAbort) { alert('NOT_IMPLEMENTED'); }, // cleanup internal variables
  69. keepAlive : function() { alert('NOT_IMPLEMENTED'); },
  70. isWriteUnnecessary : function(cmd, parameter) { alert('NOT_IMPLEMENTED'); },
  71. processWriteCommand : function(cmd, parameter) { alert('NOT_IMPLEMENTED'); },
  72. isValidTimeoutCommand : function(cmd) { alert('NOT_IMPLEMENTED'); },
  73. getCommandInfo : function(cmd) { alert('NOT_IMPLEMENTED'); },
  74. readControl : function(buffer) { alert('NOT_IMPLEMENTED'); },
  75. changeWorkingDirectory : function(path, callback) { alert('NOT_IMPLEMENTED'); },
  76. makeDirectory : function(path, callback, recursive, errorCallback) { alert('NOT_IMPLEMENTED'); },
  77. makeBlankFile : function(path, callback, errorCallback) { alert('NOT_IMPLEMENTED'); },
  78. remove : function(isDirectory, path, callback) { alert('NOT_IMPLEMENTED'); },
  79. rename : function(oldName, newName, callback, isDir, errorCallback) { alert('NOT_IMPLEMENTED'); },
  80. changePermissions : function(permissions, path, callback) { alert('NOT_IMPLEMENTED'); },
  81. custom : function(cmd) { alert('NOT_IMPLEMENTED'); },
  82. list : function(path, callback, skipCache, recursive, fxp, eventualGoalPath) { alert('NOT_IMPLEMENTED'); },
  83. download : function(remotePath, localPath, remoteSize, resume, localSize, isSymlink, callback, remoteFile) { alert('NOT_IMPLEMENTED'); },
  84. upload : function(localPath, remotePath, resume, localSize, remoteSize, callback, disableTimestampSync, errorCallback, file) { alert('NOT_IMPLEMENTED'); },
  85. checkDataTimeout : function(isDownload, id, bytes) { alert('NOT_IMPLEMENTED'); },
  86. isListing : function() { alert('NOT_IMPLEMENTED'); },
  87. recoverFromDisaster : function() { alert('NOT_IMPLEMENTED'); },
  88. // optional functions to override
  89. resetReconnectState : function() { }, // called when starting to reconnect, should reset certain internal variables to have clean slate for fresh connection
  90. sendQuitCommand : function(legitClose) { }, // called when shutting down the connection
  91. sendAbortCommand : function() { }, // called when aborting
  92. doExec : function(cmd, options) { }, // execute write command
  93. // private functions that should not be overridden
  94. // if a function has a _ prefix, it should be called by corresponding functions (without _ prefix) in derived classes
  95. setupConnect : function(reconnect) {
  96. if (!reconnect) { // this is not a reconnection attempt
  97. this.isReconnecting = false;
  98. this.reconnectsLeft = parseInt(this.reconnectAttempts);
  99. if (!this.reconnectsLeft || this.reconnectsLeft < 1) {
  100. this.reconnectsLeft = 1;
  101. }
  102. }
  103. if (!this.eventQueue.length || this.eventQueue[0].cmd != "welcome") {
  104. this.unshiftEventQueue("welcome", "", ""); // wait for welcome message first
  105. }
  106. ++this.networkTimeoutID; // just in case we have timeouts from previous connection
  107. ++this.transferID;
  108. this.isAttemptingConnect = true;
  109. },
  110. onConnected : function() {
  111. this.isConnected = true; // good to go
  112. this.isAttemptingConnect = false;
  113. this.observer.onConnected();
  114. this.isReconnecting = false;
  115. this.reconnectsLeft = parseInt(this.reconnectAttempts); // setup reconnection settings
  116. if (!this.reconnectsLeft || this.reconnectsLeft < 1) {
  117. this.reconnectsLeft = 1;
  118. }
  119. },
  120. disconnect : function() { // user has requested an explicit disconnect
  121. this.legitClose = true; // this close() is ok, don't try to reconnect
  122. this.isConnected = false;
  123. this.cleanup();
  124. if (!(this.eventQueue.length && this.eventQueue[0].cmd == "welcome") || this.protocol == "ssh2") {
  125. this.sendQuitCommand(true);
  126. }
  127. if (this.dataSocket) {
  128. this.dataSocket.kill();
  129. this.dataSocket = null;
  130. }
  131. },
  132. onDisconnect : function() { // called when disconnected
  133. if ((!this.isConnected && !this.legitClose) || this.isAttemptingConnect) { // no route to host
  134. this.observer.onAppendLog(this.errorConnectStr, 'error', "error");
  135. }
  136. this.isConnected = false;
  137. this.isAttemptingConnect = false;
  138. if (this.dataSocket) { // kill ftp data socket
  139. this.dataSocket.kill();
  140. this.dataSocket = null;
  141. }
  142. this.kill();
  143. this.observer.onDisconnected(!this.legitClose && this.reconnectMode && this.reconnectsLeft > 0);
  144. this.observer.onIsReadyChange(true);
  145. if (!this.legitClose && this.reconnectMode) { // try reconnecting
  146. this.resetReconnectState();
  147. if (this.reconnectsLeft < 1) {
  148. this.isReconnecting = false;
  149. if (this.eventQueue.length && this.eventQueue[0].cmd == "welcome") {
  150. this.eventQueue.shift();
  151. }
  152. } else {
  153. this.isReconnecting = true;
  154. this.observer.onReconnecting();
  155. var self = this;
  156. var func = function() { self.reconnect(); };
  157. setTimeout(func, this.reconnectInterval * 1000);
  158. }
  159. } else {
  160. this.legitClose = true;
  161. this.cleanup();
  162. }
  163. },
  164. kill : function() {
  165. try {
  166. this.controlInstream.close();
  167. } catch(ex) {
  168. this.observer.onDebug(ex);
  169. }
  170. try {
  171. this.controlOutstream.close();
  172. } catch(ex) {
  173. this.observer.onDebug(ex);
  174. }
  175. },
  176. _cleanup : function(isAbort) {
  177. this.eventQueue = new Array();
  178. this.trashQueue = new Array();
  179. this.currentWorkingDir = "";
  180. this.localRefreshLater = "";
  181. this.remoteRefreshLater = "";
  182. this.waitToRefresh = false;
  183. this.isReady = false;
  184. ++this.networkTimeoutID;
  185. ++this.transferID;
  186. this.observer.onClearQueue();
  187. },
  188. reconnect : function() { // ahhhh! our precious connection has been lost,
  189. if (!this.isReconnecting) { // must...get it...back...our...precious
  190. return;
  191. }
  192. --this.reconnectsLeft;
  193. this.connect(true);
  194. },
  195. abort : function(forceKill) {
  196. this.isReconnecting = false;
  197. if (this.dataSocket) {
  198. this.dataSocket.progressEventSink.bytesTotal = 0; // stop uploads
  199. this.dataSocket.dataListener.bytesTotal = 0; // stop downloads
  200. }
  201. this.cleanup(true);
  202. if (!this.isConnected) {
  203. return;
  204. }
  205. if (forceKill) {
  206. this.sendAbortCommand();
  207. }
  208. //XXX this.writeControl("ABOR"); // ABOR does not seem to stop the connection in most cases
  209. if (this.dataSocket) { // so this is a more direct approach
  210. this.dataSocket.kill();
  211. this.dataSocket = null;
  212. } else {
  213. this.isReady = true;
  214. }
  215. this.addEventQueue("aborted");
  216. this.observer.onAbort();
  217. },
  218. cancel : function(forceKill) { // cancel current transfer
  219. if (this.dataSocket) {
  220. this.dataSocket.progressEventSink.bytesTotal = 0; // stop uploads
  221. this.dataSocket.dataListener.bytesTotal = 0; // stop downloads
  222. }
  223. this.trashQueue = new Array();
  224. if (forceKill) {
  225. this.sendAbortCommand();
  226. }
  227. //XXX this.writeControl("ABOR"); // ABOR does not seem to stop the connection in most cases
  228. var dId;
  229. if (this.dataSocket && this.isConnected) { // so this is a more direct approach
  230. this.dataSocket.kill();
  231. dId = this.dataSocket.id;
  232. this.dataSocket = null;
  233. }
  234. if (this.transferProgress) {
  235. dId = this.transferProgress.id;
  236. this.transferProgress.id = '';
  237. this.transferProgress.bytesTotal = 0;
  238. }
  239. for (var x = 0; x < this.eventQueue.length; ++x) {
  240. if (this.eventQueue[x].cmd == "transferEnd" && dId == this.eventQueue[x].options.id) {
  241. this.eventQueue.splice(0, x + 1);
  242. this.observer.onRemoveQueue(dId);
  243. break;
  244. }
  245. }
  246. if (this.isConnected && this.protocol != "ssh2") {
  247. this.unshiftEventQueue("aborted");
  248. }
  249. },
  250. loginAccepted : function() {
  251. if (this.legitClose) {
  252. this.observer.onWelcomed();
  253. }
  254. var newConnectedHost = this.login + "@" + this.host;
  255. this.observer.onLoginAccepted(newConnectedHost != this.connectedHost);
  256. if (newConnectedHost != this.connectedHost) {
  257. this.legitClose = true;
  258. }
  259. this.connectedHost = newConnectedHost; // switching to a different host or different login
  260. if (!this.legitClose) {
  261. this.recoverFromDisaster(); // recover from previous disaster
  262. return false;
  263. }
  264. this.legitClose = false;
  265. return true; // proceed normally
  266. },
  267. loginDenied : function(buffer) {
  268. if (this.type == 'transfer') {
  269. this.observer.onLoginDenied();
  270. }
  271. this.cleanup(); // login failed, cleanup variables
  272. if (this.type != 'transfer' && this.type != 'bad') {
  273. this.observer.onError(buffer);
  274. }
  275. this.isConnected = false;
  276. this.kill();
  277. if (this.type == 'transfer') {
  278. this.type = 'bad';
  279. }
  280. if (this.type != 'transfer' && this.type != 'bad') {
  281. var self = this;
  282. var func = function() { self.observer.onLoginDenied(); };
  283. setTimeout(func, 0);
  284. }
  285. },
  286. checkTimeout : function(id, cmd) {
  287. if (this.isConnected && this.networkTimeoutID == id && this.eventQueue.length && this.eventQueue[0].cmd.indexOf(cmd) != -1) {
  288. this.resetConnection();
  289. }
  290. },
  291. resetConnection : function() {
  292. this.legitClose = false; // still stuck on a command so, try to restart the connection the hard way
  293. this.sendQuitCommand();
  294. if (this.dataSocket) {
  295. this.dataSocket.kill();
  296. this.dataSocket = null;
  297. }
  298. this.kill();
  299. },
  300. addEventQueue : function(cmd, parameter, callback, options, exec) { // this just creates a new queue item
  301. this.eventQueue.push( { cmd: cmd, parameter: parameter || '', callback: callback || null, options: options || {}, exec: exec || null });
  302. },
  303. unshiftEventQueue : function(cmd, parameter, callback, options, exec) { // ditto
  304. this.eventQueue.unshift({ cmd: cmd, parameter: parameter || '', callback: callback || null, options: options || {}, exec: exec || null });
  305. },
  306. beginCmdBatch : function() {
  307. this.doingCmdBatch = true;
  308. },
  309. writeControlWrapper : function() {
  310. if (!this.doingCmdBatch) {
  311. this.writeControl();
  312. }
  313. },
  314. endCmdBatch : function() {
  315. this.doingCmdBatch = false;
  316. this.writeControl();
  317. },
  318. refresh : function() {
  319. var stillWorking = false;
  320. for (var y = 0; y < gMaxCon; ++y) {
  321. if (gConnections[y].eventQueue.length) {
  322. stillWorking = true;
  323. }
  324. }
  325. if (this.waitToRefresh || stillWorking) {
  326. var self = this;
  327. var func = function() { self.refresh(); };
  328. setTimeout(func, 1000);
  329. return;
  330. } else if (this.eventQueue.length) {
  331. return;
  332. }
  333. if (this.localRefreshLater) {
  334. var dir = new String(this.localRefreshLater);
  335. this.localRefreshLater = "";
  336. this.observer.onShouldRefresh(true, false, dir);
  337. }
  338. if (this.remoteRefreshLater) {
  339. var dir = new String(this.remoteRefreshLater);
  340. this.remoteRefreshLater = "";
  341. this.observer.onShouldRefresh(false, true, dir);
  342. }
  343. },
  344. writeControl : function(cmd) {
  345. try {
  346. if (!this.isReady || (!cmd && !this.eventQueue.length)) {
  347. return;
  348. }
  349. var parameter;
  350. var callback;
  351. var options;
  352. var exec;
  353. if (!cmd) {
  354. cmd = this.eventQueue[0].cmd;
  355. parameter = this.eventQueue[0].parameter;
  356. callback = this.eventQueue[0].callback;
  357. options = this.eventQueue[0].options;
  358. exec = this.eventQueue[0].exec;
  359. }
  360. if (cmd == "custom") {
  361. cmd = parameter;
  362. parameter = "";
  363. }
  364. var redudantCommand = this.isWriteUnnecessary(cmd, parameter);
  365. while (cmd == "aborted" || cmd == "goodbye" || cmd == "transferBegin" || cmd == "transferEnd" || redudantCommand) {
  366. if (redudantCommand) {
  367. this.trashQueue.push(this.eventQueue[0]);
  368. }
  369. if (cmd == "transferEnd") {
  370. this.observer.onRemoveQueue(options.id);
  371. }
  372. this.eventQueue.shift();
  373. if (this.eventQueue.length) {
  374. cmd = this.eventQueue[0].cmd;
  375. parameter = this.eventQueue[0].parameter;
  376. callback = this.eventQueue[0].callback;
  377. options = this.eventQueue[0].options;
  378. exec = this.eventQueue[0].exec;
  379. } else {
  380. return;
  381. }
  382. redudantCommand = this.isWriteUnnecessary(cmd, parameter);
  383. }
  384. this.isReady = false;
  385. this.observer.onIsReadyChange(false);
  386. var processedCommands = this.processWriteCommand(cmd, parameter);
  387. cmd = processedCommands.cmd;
  388. parameter = processedCommands.parameter;
  389. var outputData = cmd + (parameter ? (' ' + parameter) : '') + "\r\n"; // le original bug fix! - thanks to devin
  390. if (exec) {
  391. var success = this.doExec(exec, options);
  392. if (!success) {
  393. return;
  394. }
  395. } else {
  396. try {
  397. outputData = this.fromUTF8.ConvertFromUnicode(outputData) + this.fromUTF8.Finish();
  398. } catch (ex) {
  399. this.observer.onDebug(ex);
  400. }
  401. this.controlOutstream.write(outputData, outputData.length); // write!
  402. }
  403. var self = this;
  404. if (this.isValidTimeoutCommand(cmd)) {
  405. ++this.networkTimeoutID; // this checks for timeout
  406. var currentTimeout = this.networkTimeoutID;
  407. var func = function() { self.checkTimeout(currentTimeout, cmd); };
  408. setTimeout(func, this.networkTimeout * 1000);
  409. }
  410. var commandInfo = this.getCommandInfo(cmd);
  411. if (commandInfo.isTransferCmd) {
  412. ++this.transferID;
  413. var currentId = this.transferID;
  414. var func = function() { self.checkDataTimeout(commandInfo.isDownload, currentId, 0); };
  415. setTimeout(func, this.networkTimeout * 1000);
  416. }
  417. outputData = cmd + (parameter ? (' ' + parameter) : ''); // write it out to the log
  418. if (commandInfo.skipLog) {
  419. // do nothing
  420. } else if (!commandInfo.isPrivate) {
  421. this.observer.onAppendLog("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + outputData, 'output', "info");
  422. } else {
  423. this.observer.onAppendLog("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" + cmd + " " + this.passNotShown, 'output', "info");
  424. }
  425. } catch(ex) {
  426. this.observer.onDebug(ex);
  427. this.observer.onError(this.errorConnectStr);
  428. }
  429. },
  430. nextCommand : function() {
  431. this.isReady = true;
  432. this.observer.onIsReadyChange(true);
  433. if (this.eventQueue.length && this.eventQueue[0].cmd != "welcome") { // start the next command
  434. this.writeControl();
  435. } else {
  436. this.refresh();
  437. }
  438. },
  439. handleCWDResponse : function(success, cmd, parameter, callback, options, exec, buffer) {
  440. if (success) {
  441. this.currentWorkingDir = parameter;
  442. this.observer.onChangeDir(parameter, options.dontUpdateView); // else navigate to the directory
  443. if (callback) {
  444. callback(true);
  445. }
  446. } else { // if it's not a directory
  447. if (options.isUploading) {
  448. while (this.eventQueue.length) {
  449. if (this.eventQueue[0].cmd == "transferEnd") {
  450. this.observer.onRemoveQueue(this.eventQueue[0].options.id);
  451. this.observer.onTransferFail(this.eventQueue[0].options, buffer);
  452. this.eventQueue.shift();
  453. break;
  454. }
  455. this.eventQueue.shift();
  456. }
  457. }
  458. if (callback) {
  459. callback(false);
  460. } else if (this.type == 'transfer') {
  461. if (buffer) {
  462. this.observer.onDebug(buffer);
  463. }
  464. this.unshiftEventQueue(cmd, parameter, callback, options, exec);
  465. this.makeDirectory(parameter, "", true);
  466. } else {
  467. this.observer.onDirNotFound(buffer);
  468. this.observer.onError(buffer);
  469. }
  470. }
  471. },
  472. parseListData : function(data, path, skipEncoding) {
  473. /* Unix style: drwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog
  474. * Alternate Unix style: drwxr-xr-x 1 user01 ftp 512 Jan 29 1997 prog
  475. * Alternate Unix style: drwxr-xr-x 1 1 1 512 Jan 29 23:32 prog
  476. * SunOS style: drwxr-xr-x+ 1 1 1 512 Jan 29 23:32 prog
  477. * A symbolic link in Unix style: lrwxr-xr-x 1 user01 ftp 512 Jan 29 23:32 prog -> prog2000
  478. * AIX style: drwxr-xr-x 1 user01 ftp 512 05 Nov 2003 prog
  479. * Novell style: drwxr-xr-x 1 user01 512 Jan 29 23:32 prog
  480. * Weird style: drwxr-xr-x 1 user5424867 Jan 29 23:32 prog, where 5424867 is the size
  481. * Weird style 2: drwxr-xr-x 1 user01 anon5424867 Jan 11 12:48 prog, where 5424867 is the size
  482. * MS-DOS style: 01-29-97 11:32PM <DIR> prog
  483. * OS/2 style: 0 DIR 01-29-97 23:32 PROG
  484. * OS/2 style: 2243 RA 04-05-103 00:22 PJL
  485. * OS/2 style: 60 11-18-104 06:54 chkdsk.log
  486. *
  487. * MLSD style: type=file;size=6106;modify=20070223082414;UNIX.mode=0644;UNIX.uid=32257;UNIX.gid=32259;unique=808g154c727; prog
  488. * type=dir;sizd=4096;modify=20070218021044;UNIX.mode=0755;UNIX.uid=32257;UNIX.gid=32259;unique=808g1550003; prog
  489. * type=file;size=4096;modify=20070218021044;UNIX.mode=07755;UNIX.uid=32257;UNIX.gid=32259;unique=808g1550003; prog
  490. * type=OS.unix=slink:/blah;size=4096;modify=20070218021044;UNIX.mode=0755;UNIX.uid=32257;UNIX.gid=32259;unique=808g1550003; prog
  491. */
  492. if (!skipEncoding) {
  493. try {
  494. data = this.toUTF8.convertStringToUTF8(data, this.encoding, 1);
  495. } catch (ex) {
  496. this.observer.onDebug(ex);
  497. }
  498. }
  499. this.observer.onDebug(data.replace(/</g, '&lt;').replace(/>/g, '&gt;'), "DEBUG");
  500. var items = data.replace(/\r\n/g, "\n").split("\n");
  501. items = items.filter(this.removeBlanks);
  502. var curDate = new Date();
  503. if (items.length) { // some ftp servers send 'count <number>' or 'total <number>' first
  504. var firstLine = items[0].toLowerCase();
  505. if (firstLine.indexOf("count") == 0 || firstLine.indexOf("total") == 0 || firstLine.indexOf("listing directory") == 0 || (!this.featMLSD && items[0].split(" ").filter(this.removeBlanks).length == 2)) {
  506. items.shift(); // could be in german or croatian or what have you
  507. }
  508. }
  509. for (var x = 0; x < items.length; ++x) {
  510. try {
  511. if (!items[x]) { // some servers put in blank lines b/w entries, aw, for cryin' out loud
  512. items.splice(x, 1);
  513. --x;
  514. continue;
  515. }
  516. items[x] = items[x].replace(/^\s+/, ""); // @*$% - some servers put blanks in front, do trimming on front
  517. var temp = items[x]; // account for collisions: drwxr-xr-x1017 user01
  518. if (!this.featMLSD) {
  519. if (!parseInt(items[x].charAt(0)) && items[x].charAt(0) != '0' && items[x].charAt(10) == '+') { // drwxr-xr-x+ - get rid of the plus sign
  520. items[x] = this.setCharAt(items[x], 10, ' ');
  521. }
  522. if (!parseInt(items[x].charAt(0)) && items[x].charAt(0) != '0' && items[x].charAt(10) != ' ') { // this is mimicked below if weird style
  523. items[x] = items[x].substring(0, 10) + ' ' + items[x].substring(10, items[x].length);
  524. }
  525. items[x] = items[x].split(" ").filter(this.removeBlanks);
  526. }
  527. if (this.featMLSD) { // MLSD-standard style
  528. var newItem = { permissions : "----------",
  529. hardLink : "",
  530. user : "",
  531. group : "",
  532. fileSize : "0",
  533. date : "",
  534. leafName : "",
  535. isDir : false,
  536. isDirectory : function() { return this.isDir },
  537. isSymlink : function() { return this.symlink != "" },
  538. symlink : "",
  539. path : "" };
  540. var pathname = items[x].split("; ");
  541. newItem.leafName = '';
  542. for (var y = 1; y < pathname.length; ++y) {
  543. newItem.leafName += (y == 1 ? '' : '; ') + pathname[y];
  544. }
  545. // some servers place full path for the filename...arrrgh /users/www/blah has to be just 'blah'
  546. newItem.leafName = newItem.leafName.substring(newItem.leafName.lastIndexOf("/") + 1);
  547. newItem.path = this.constructPath(path, newItem.leafName);
  548. items[x] = pathname[0];
  549. items[x] = items[x].split(";");
  550. var skip = false;
  551. for (var y = 0; y < items[x].length; ++y) {
  552. if (!items[x][y]) {
  553. continue;
  554. }
  555. var fact = items[x][y].split('=');
  556. if (fact.length < 2 || !fact[0] || !fact[1]) {
  557. continue;
  558. }
  559. var factName = fact[0].toLowerCase();
  560. var factVal = fact[1];
  561. switch (factName) {
  562. case "type":
  563. if (factVal == "pdir" || factVal == "cdir") {
  564. skip = true;
  565. } else if (factVal == "dir") {
  566. newItem.isDir = true;
  567. newItem.permissions = this.setCharAt(newItem.permissions, 0, 'd');
  568. } else if (items[x][y].substring(5).indexOf("OS.unix=slink:") == 0) {
  569. newItem.symlink = items[x][y].substring(19);
  570. newItem.permissions = this.setCharAt(newItem.permissions, 0, 'l');
  571. } else if (factVal != "file") {
  572. skip = true;
  573. }
  574. break;
  575. case "size":
  576. case "sizd":
  577. newItem.fileSize = factVal;
  578. break;
  579. case "modify":
  580. var dateString = factVal.substr(0, 4) + " " + factVal.substr(4, 2) + " " + factVal.substr(6, 2) + " "
  581. + factVal.substr(8, 2) + ":" + factVal.substr(10, 2) + ":" + factVal.substr(12, 2) + " GMT";
  582. var zeDate = new Date(dateString);
  583. zeDate.setMinutes(zeDate.getMinutes() + this.timezone);
  584. var timeOrYear = new Date() - zeDate > 15600000000 ? zeDate.getFullYear() // roughly 6 months
  585. : this.zeroPadTime(zeDate.getHours()) + ":" + this.zeroPadTime(zeDate.getMinutes());
  586. newItem.date = this.l10nMonths[zeDate.getMonth()] + ' ' + zeDate.getDate() + ' ' + timeOrYear;
  587. newItem.lastModifiedTime = zeDate.getTime();
  588. break;
  589. case "unix.mode":
  590. var offset = factVal.length == 5 ? 1 : 0;
  591. var sticky = this.zeroPad(parseInt(factVal[0 + offset]).toString(2));
  592. var owner = this.zeroPad(parseInt(factVal[1 + offset]).toString(2));
  593. var group = this.zeroPad(parseInt(factVal[2 + offset]).toString(2));
  594. var pub = this.zeroPad(parseInt(factVal[3 + offset]).toString(2));
  595. newItem.permissions = this.setCharAt(newItem.permissions, 1, owner[0] == '1' ? 'r' : '-');
  596. newItem.permissions = this.setCharAt(newItem.permissions, 2, owner[1] == '1' ? 'w' : '-');
  597. newItem.permissions = this.setCharAt(newItem.permissions, 3, sticky[0] == '1' ? (owner[2] == '1' ? 's' : 'S')
  598. : (owner[2] == '1' ? 'x' : '-'));
  599. newItem.permissions = this.setCharAt(newItem.permissions, 4, group[0] == '1' ? 'r' : '-');
  600. newItem.permissions = this.setCharAt(newItem.permissions, 5, group[1] == '1' ? 'w' : '-');
  601. newItem.permissions = this.setCharAt(newItem.permissions, 6, sticky[1] == '1' ? (group[2] == '1' ? 's' : 'S')
  602. : (group[2] == '1' ? 'x' : '-'));
  603. newItem.permissions = this.setCharAt(newItem.permissions, 7, pub[0] == '1' ? 'r' : '-');
  604. newItem.permissions = this.setCharAt(newItem.permissions, 8, pub[1] == '1' ? 'w' : '-');
  605. newItem.permissions = this.setCharAt(newItem.permissions, 9, sticky[2] == '1' ? (pub[2] == '1' ? 't' : 'T')
  606. : (pub[2] == '1' ? 'x' : '-'));
  607. break;
  608. case "unix.uid":
  609. newItem.user = factVal;
  610. break;
  611. case "unix.gid":
  612. newItem.group = factVal;
  613. break;
  614. default:
  615. break;
  616. }
  617. if (skip) {
  618. break;
  619. }
  620. }
  621. if (skip) {
  622. items.splice(x, 1);
  623. --x;
  624. continue;
  625. }
  626. items[x] = newItem;
  627. } else if (!parseInt(items[x][0].charAt(0)) && items[x][0].charAt(0) != '0') { // unix style - so much simpler with you guys
  628. var offset = 0;
  629. if (items[x][3].search(this.remoteMonths) != -1 && items[x][5].search(this.remoteMonths) == -1) {
  630. var weird = temp; // added to support weird servers
  631. if (weird.charAt(10) != ' ') { // same as above code
  632. weird = weird.substring(0, 10) + ' ' + weird.substring(10, weird.length);
  633. }
  634. var weirdIndex = 0;
  635. for (var y = 0; y < items[x][2].length; ++y) {
  636. if (parseInt(items[x][2].charAt(y))) {
  637. weirdIndex = weird.indexOf(items[x][2]) + y;
  638. break;
  639. }
  640. }
  641. weird = weird.substring(0, weirdIndex) + ' ' + weird.substring(weirdIndex, weird.length);
  642. items[x] = weird.split(" ").filter(this.removeBlanks);
  643. }
  644. if (items[x][4].search(this.remoteMonths) != -1 && !parseInt(items[x][3].charAt(0))) {
  645. var weird = temp; // added to support 'weird 2' servers, oy vey
  646. if (weird.charAt(10) != ' ') { // same as above code
  647. weird = weird.substring(0, 10) + ' ' + weird.substring(10, weird.length);
  648. }
  649. var weirdIndex = 0;
  650. for (var y = 0; y < items[x][3].length; ++y) {
  651. if (parseInt(items[x][3].charAt(y))) {
  652. weirdIndex = weird.indexOf(items[x][3]) + y;
  653. break;
  654. }
  655. }
  656. weird = weird.substring(0, weirdIndex) + ' ' + weird.substring(weirdIndex, weird.length);
  657. items[x] = weird.split(" ").filter(this.removeBlanks);
  658. }
  659. if (items[x][4].search(this.remoteMonths) != -1) { // added to support novell servers
  660. offset = 1;
  661. }
  662. var index = 0;
  663. for (var y = 0; y < 7 - offset; ++y) {
  664. index = temp.indexOf(items[x][y], index) + items[x][y].length + 1;
  665. }
  666. var name = temp.substring(temp.indexOf(items[x][7 - offset], index) + items[x][7 - offset].length + 1, temp.length);
  667. name = name.substring(name.search(/[^\s]/));
  668. var symlink = "";
  669. if (items[x][0].charAt(0) == 'l') {
  670. symlink = name;
  671. name = name.substring(0, name.indexOf("->") - 1);
  672. symlink = symlink.substring(symlink.indexOf("->") + 3);
  673. }
  674. name = (name.lastIndexOf('/') == -1 ? name : name.substring(name.lastIndexOf('/') + 1));
  675. var remotepath = this.constructPath(path, name);
  676. var month;
  677. var rawDate = items[x][6 - offset];
  678. if (items[x][6].search(this.remoteMonths) != -1) { // added to support aix servers
  679. month = this.remoteMonths.search(items[x][6 - offset]) / 4;
  680. rawDate = items[x][5 - offset];
  681. } else {
  682. month = this.remoteMonths.search(items[x][5 - offset]) / 4;
  683. }
  684. var timeOrYear;
  685. var curDate = new Date();
  686. var currentYr = curDate.getMonth() < month ? curDate.getFullYear() - 1 : curDate.getFullYear();
  687. var rawYear = items[x][7 - offset].indexOf(':') != -1 ? currentYr : parseInt(items[x][7 - offset]);
  688. var rawTime = items[x][7 - offset].indexOf(':') != -1 ? items[x][7 - offset] : "00:00";
  689. rawTime = rawTime.split(":");
  690. for (var y = 0; y < rawTime.length; ++y) {
  691. rawTime[y] = parseInt(rawTime[y], 10);
  692. }
  693. var parsedDate = new Date(rawYear, month, rawDate, rawTime[0], rawTime[1]); // month-day-year format
  694. parsedDate.setMinutes(parsedDate.getMinutes() + this.timezone);
  695. if (new Date() - parsedDate > 15600000000) { // roughly 6 months
  696. timeOrYear = parsedDate.getFullYear();
  697. } else {
  698. timeOrYear = this.zeroPadTime(parsedDate.getHours()) + ":" + this.zeroPadTime(parsedDate.getMinutes());
  699. }
  700. month = this.l10nMonths[parsedDate.getMonth()];
  701. items[x] = { permissions : items[x][0],
  702. hardLink : items[x][1],
  703. user : items[x][2],
  704. group : (offset ? "" : items[x][3]),
  705. fileSize : items[x][4 - offset],
  706. date : month + ' ' + parsedDate.getDate() + ' ' + timeOrYear,
  707. leafName : name,
  708. isDir : items[x][0].charAt(0) == 'd',
  709. isDirectory : function() { return this.isDir },
  710. isSymlink : function() { return this.symlink != "" },
  711. symlink : symlink,
  712. path : remotepath };
  713. } else if (items[x][0].indexOf('-') == -1) { // os/2 style
  714. var offset = 0;
  715. if (items[x][2].indexOf(':') != -1) { // if "DIR" and "A" are missing
  716. offset = 1;
  717. }
  718. var rawDate = items[x][2 - offset].split("-");
  719. var rawTime = items[x][3 - offset];
  720. var timeOrYear = rawTime;
  721. rawTime = rawTime.split(":");
  722. for (var y = 0; y < rawDate.length; ++y) {
  723. rawDate[y] = parseInt(rawDate[y], 10); // leading zeros are treated as octal so pass 10 as base argument
  724. }
  725. for (var y = 0; y < rawTime.length; ++y) {
  726. rawTime[y] = parseInt(rawTime[y], 10);
  727. }
  728. rawDate[2] = rawDate[2] + 1900; // ah, that's better
  729. var parsedDate = new Date(rawDate[2], rawDate[0] - 1, rawDate[1], rawTime[0], rawTime[1]); // month-day-year format
  730. parsedDate.setMinutes(parsedDate.getMinutes() + this.timezone);
  731. if (new Date() - parsedDate > 15600000000) { // roughly 6 months
  732. timeOrYear = parsedDate.getFullYear();
  733. } else {
  734. timeOrYear = this.zeroPadTime(parsedDate.getHours()) + ":" + this.zeroPadTime(parsedDate.getMinutes());
  735. }
  736. var month = this.l10nMonths[parsedDate.getMonth()];
  737. var name = temp.substring(temp.indexOf(items[x][3 - offset]) + items[x][3 - offset].length + 1, temp.length);
  738. name = name.substring(name.search(/[^\s]/));
  739. name = (name.lastIndexOf('/') == -1 ? name : name.substring(name.lastIndexOf('/') + 1));
  740. items[x] = { permissions : items[x][1] == "DIR" ? "d---------" : "----------",
  741. hardLink : "",
  742. user : "",
  743. group : "",
  744. fileSize : items[x][0],
  745. date : month + ' ' + parsedDate.getDate() + ' ' + timeOrYear,
  746. leafName : name,
  747. isDir : items[x][1] == "DIR",
  748. isDirectory : function() { return this.isDir },
  749. isSymlink : function() { return this.symlink != "" },
  750. symlink : "",
  751. path : this.constructPath(path, name) };
  752. } else { // ms-dos style
  753. var rawDate = items[x][0].split("-");
  754. var amPm = items[x][1].substring(5, 7); // grab PM or AM
  755. var rawTime = items[x][1].substring(0, 5); // get rid of PM, AM
  756. var timeOrYear = rawTime;
  757. rawTime = rawTime.split(":");
  758. for (var y = 0; y < rawDate.length; ++y) {
  759. rawDate[y] = parseInt(rawDate[y], 10);
  760. }
  761. for (var y = 0; y < rawTime.length; ++y) {
  762. rawTime[y] = parseInt(rawTime[y], 10);
  763. }
  764. rawTime[0] = rawTime[0] == 12 && amPm == "AM" ? 0 : (rawTime[0] < 12 && amPm == "PM" ? rawTime[0] + 12 : rawTime[0]);
  765. if (rawDate[2] < 70) { // assuming you didn't have some files left over from 1904
  766. rawDate[2] = rawDate[2] + 2000; // ah, that's better
  767. } else {
  768. rawDate[2] = rawDate[2] + 1900;
  769. }
  770. var parsedDate = new Date(rawDate[2], rawDate[0] - 1, rawDate[1], rawTime[0], rawTime[1]); // month-day-year format
  771. parsedDate.setMinutes(parsedDate.getMinutes() + this.timezone);
  772. if (new Date() - parsedDate > 15600000000) { // roughly 6 months
  773. timeOrYear = parsedDate.getFullYear();
  774. } else {
  775. timeOrYear = this.zeroPadTime(parsedDate.getHours()) + ":" + this.zeroPadTime(parsedDate.getMinutes());
  776. }
  777. var month = this.l10nMonths[parsedDate.getMonth()];
  778. var name = temp.substring(temp.indexOf(items[x][2], temp.indexOf(items[x][1]) + items[x][1].length + 1)
  779. + items[x][2].length + 1, temp.length);
  780. name = name.substring(name.search(/[^\s]/));
  781. name = (name.lastIndexOf('/') == -1 ? name : name.substring(name.lastIndexOf('/') + 1));
  782. items[x] = { permissions : items[x][2] == "<DIR>" ? "d---------" : "----------",
  783. hardLink : "",
  784. user : "",
  785. group : "",
  786. fileSize : items[x][2] == "<DIR>" ? '0' : items[x][2],
  787. date : month + ' ' + parsedDate.getDate() + ' ' + timeOrYear,
  788. leafName : name,
  789. isDir : items[x][2] == "<DIR>",
  790. isDirectory : function() { return this.isDir },
  791. isSymlink : function() { return this.symlink != "" },
  792. symlink : "",
  793. path : this.constructPath(path, name) };
  794. }
  795. if (!items[x].lastModifiedTime) {
  796. var dateTemp = items[x].date; // this helps with sorting by date
  797. var dateMonth = dateTemp.substring(0, 3);
  798. var dateIndex = this.l10nMonths.indexOf(dateMonth);
  799. dateTemp = this.remoteMonths.substr(dateIndex * 4, 3) + dateTemp.substring(3);
  800. if (items[x].date.indexOf(':') != -1) {
  801. dateTemp = dateTemp + ' ' + (curDate.getFullYear() - (curDate.getMonth() < dateIndex ? 1 : 0));
  802. }
  803. items[x].lastModifiedTime = Date.parse(dateTemp);
  804. }
  805. items[x].fileSize = parseInt(items[x].fileSize);
  806. items[x].parent = { path: items[x].path.substring(0, items[x].path.lastIndexOf('/') ? items[x].path.lastIndexOf('/') : 1) };
  807. } catch (ex) {
  808. this.observer.onError(ex + items[x].toSource());
  809. items.splice(x, 1);
  810. --x;
  811. }
  812. }
  813. var directories = new Array(); // sort directories to the top
  814. var files = new Array();
  815. for (var x = 0; x < items.length; ++x) {
  816. if (!this.hiddenMode && items[x].leafName.charAt(0) == ".") { // don't show hidden files
  817. continue;
  818. }
  819. items[x].isHidden = items[x].leafName.charAt(0) == ".";
  820. items[x].leafName = items[x].leafName.replace(/[\\\/]/g, ''); // scrub out / or \, a security vulnerability if file tries to do ..\..\blah.txt
  821. items[x].path = this.constructPath(path, items[x].leafName); // thanks to Tan Chew Keong for the heads-up
  822. if (items[x].leafName == "." || items[x].leafName == "..") { // get rid of "." or "..", this can screw up things on recursive deletions
  823. continue;
  824. }
  825. if (items[x].isDirectory()) {
  826. directories.push(items[x]);
  827. } else {
  828. files.push(items[x]);
  829. }
  830. }
  831. items = directories.concat(files);
  832. if (this.sessionsMode) {
  833. try { // put in cache
  834. var cacheSession = this.cacheService.createSession("fireftp", 1, true);
  835. var cacheDesc = cacheSession.openCacheEntry(this.protocol + "://" + this.version + this.connectedHost + path,
  836. Components.interfaces.nsICache.ACCESS_WRITE, false);
  837. var cacheOut = cacheDesc.openOutputStream(0);
  838. var cacheData = unescape(encodeURIComponent(JSON.stringify(items)));
  839. cacheOut.write(cacheData, cacheData.length);
  840. cacheOut.close();
  841. cacheDesc.close();
  842. } catch (ex) {
  843. this.observer.onDebug(ex);
  844. }
  845. }
  846. return items;
  847. },
  848. cacheHit : function(path, callback) {
  849. try { // check the cache first
  850. var cacheSession = this.cacheService.createSession("fireftp", 1, true);
  851. var cacheDesc = cacheSession.openCacheEntry(this.protocol + "://" + this.version + this.connectedHost + path,
  852. Components.interfaces.nsICache.ACCESS_READ, false);
  853. if (cacheDesc.dataSize) {
  854. var cacheIn = cacheDesc.openInputStream(0);
  855. var cacheInstream = Components.classes["@mozilla.org/binaryinputstream;1"].createInstance(Components.interfaces.nsIBinaryInputStream);
  856. cacheInstream.setInputStream(cacheIn);
  857. this.listData = cacheInstream.readBytes(cacheInstream.available());
  858. this.listData = JSON.parse(decodeURIComponent(escape(this.listData)));
  859. for (var x = 0; x < this.listData.length; ++x) { // these functions get lost when encoding in JSON
  860. this.listData[x].isDirectory = function() { return this.isDir };
  861. this.listData[x].isSymlink = function() { return this.symlink != "" };
  862. }
  863. cacheInstream.close();
  864. cacheDesc.close();
  865. this.observer.onDebug(this.listData.toSource().replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/, {/g, ',\n{')
  866. .replace(/, isDirectory:\(function \(\) {return this.isDir;}\), isSymlink:\(function \(\) {return this.symlink != "";}\)/g, ''),
  867. "DEBUG-CACHE");
  868. if (callback) {
  869. callback(); // send off list data to whoever wanted it
  870. }
  871. return true;
  872. }
  873. cacheDesc.close();
  874. } catch (ex) { }
  875. return false;
  876. },
  877. removeCacheEntry : function(path) {
  878. try {
  879. var cacheSession = this.cacheService.createSession("fireftp", 1, true);
  880. var cacheDesc = cacheSession.openCacheEntry(this.protocol + "://" + this.version + this.connectedHost + path,
  881. Components.interfaces.nsICache.ACCESS_WRITE, false);
  882. cacheDesc.doom();
  883. cacheDesc.close();
  884. } catch (ex) {
  885. this.observer.onDebug(ex);
  886. }
  887. },
  888. constructPath : function(parent, leafName) {
  889. return parent + (parent.charAt(parent.length - 1) != '/' ? '/' : '') + leafName;
  890. },
  891. removeBlanks : function(element, index, array) {
  892. return element;
  893. },
  894. zeroPad : function(str) {
  895. return str.length == 3 ? str : (str.length == 2 ? '0' + str : '00' + str);
  896. },
  897. zeroPadTime : function(num) {
  898. num = num.toString();
  899. return num.length == 2 ? num : '0' + num;
  900. },
  901. setCharAt : function(str, index, ch) { // how annoying
  902. return str.substr(0, index) + ch + str.substr(index + 1);
  903. },
  904. setEncoding : function(encoding) {
  905. try {
  906. this.fromUTF8.charset = encoding;
  907. this.encoding = encoding;
  908. } catch (ex) {
  909. this.fromUTF8.charset = "UTF-8";
  910. this.encoding = "UTF-8";
  911. }
  912. },
  913. binaryToHex : function(input) { // borrowed from nsUpdateService.js
  914. var result = "";
  915. for (var i = 0; i < input.length; ++i) {
  916. var hex = input.charCodeAt(i).toString(16);
  917. if (hex.length == 1) {
  918. hex = "0" + hex;
  919. }
  920. result += hex;
  921. }
  922. return result;
  923. }
  924. };
  925. function setProtocol(protocol) {
  926. var protocolMap = { 'ftp' : { 'transport': ftpMozilla, 'observer': ftpObserver },
  927. 'ssh2' : { 'transport': ssh2Mozilla, 'observer': ssh2Observer } };
  928. gConnections = new Array();
  929. for (var x = 0; x < gMaxCon; ++x) {
  930. gConnections.push(new protocolMap[protocol].transport(x ? new transferObserver(x + 1) : new protocolMap[protocol].observer()));
  931. gConnections[x].type = x ? 'transfer' : '';
  932. gConnections[x].connNo = x + 1;
  933. gConnections[x].errorConnectStr = gStrbundle.getString("errorConn");
  934. gConnections[x].errorXCheckFail = gStrbundle.getString("errorXCheckFail");
  935. gConnections[x].passNotShown = gStrbundle.getString("passNotShown");
  936. gConnections[x].l10nMonths = gStrbundle.getString("months").split("|");
  937. gConnections[x].version = gVersion;
  938. }
  939. gConnection = gConnections[0];
  940. readPreferences();
  941. }