/** * Module dependencies. */ var FTP = require('ftp'); var path = require('path'); var NotFoundError = require('./notfound'); var NotModifiedError = require('./notmodified'); var debug = require('debug')('get-uri:ftp'); /** * Module exports. */ module.exports = get; /** * Returns a Readable stream from an "ftp:" URI. * * @api protected */ function get (parsed, opts, fn) { var cache = opts.cache; var client = new FTP(); var filepath = parsed.pathname; var lastModified; client.once('error', onerror); client.once('ready', onready); client.once('greeting', ongreeting); function onready () { // first we have to figure out the Last Modified date. // try the MDTM command first, which is an optional extension command. client.lastMod(filepath, onlastmod); } function ongreeting (greeting) { debug('FTP greeting: %o', greeting); } function onerror (err) { client.end(); fn(err); } function onfile (err, stream) { if (err) return onerror(err); stream.once('end', onend); stream.lastModified = lastModified; fn(null, stream); } function onend () { // close the FTP client socket connection client.end(); } function getFile () { client.get(filepath, onfile); } function onlastmod (err, lastmod) { // handle the "file not found" error code if (err) { if (550 == err.code) { onerror(new NotFoundError()); } // any other error then we'll try the LIST command instead } if (lastmod) { setLastMod(lastmod); } else { // try to get the last modified date via the LIST command (uses // more bandwidth, but is more compatible with older FTP servers var dir = path.dirname(filepath); client.list(dir, onlist); } } function setLastMod (lastmod) { lastModified = lastmod; if (cache && isNotModified()) { // file is the same as in the "cache", return a not modified error onerror(new NotModifiedError()); } else { // XXX: a small timeout seemed necessary otherwise FTP servers // were returning empty sockets for the file occasionally setTimeout(client.get.bind(client, filepath, onfile), 10); } } function onlist (err, list) { if (err) return onerror(err); var name = path.basename(filepath); // attempt to find the "entry" with a matching "name" var entry; for (var i = 0; i < list.length; i++) { entry = list[i]; debug('file %o: %o', i, entry.name); if (entry.name == name) { break; } entry = null; } if (entry) { setLastMod(entry.date); } else { onerror(new NotFoundError()); } } // called when `lastModified` is set, and a "cache" stream was provided function isNotModified () { return +cache.lastModified == +lastModified; } opts.host = parsed.hostname || parsed.host || 'localhost'; opts.port = parseInt(parsed.port, 10) || 21; if (debug.enabled) opts.debug = debug; // TODO: add auth client.connect(opts); }