diff --git a/README.md b/README.md index 5d7e42b..54fa1ee 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,9 @@ Description node-imap is an IMAP module for [node.js](http://nodejs.org/) that provides an asynchronous interface for communicating with an IMAP mail server. +This module does not perform any magic such as auto-decoding of messages/attachments or parsing of email addresses (node-imap leaves all mail header values as-is). +If you are in need of this kind of extra functionality, check out andris9's [mime.js](http://github.com/andris9/mailparser/blob/master/mime.js) (requires [node-iconv](http://github.com/bnoordhuis/node-iconv)) set of functions, part of his [mailparser](http://github.com/andris9/mailparser) module. + Requirements ============ @@ -23,7 +26,7 @@ This example fetches the 'date', 'from', 'to', 'subject' message headers and the host: 'imap.gmail.com', port: 993, secure: true - }); + }); function die(err) { console.log('Uh oh: ' + err); @@ -189,8 +192,9 @@ ImapConnection Functions * **host** - A String representing the hostname or IP address of the IMAP server. **Default:** "localhost" * **port** - An Integer representing the port of the IMAP server. **Default:** 143 * **secure** - A Boolean indicating the connection should use SSL/TLS. **Default:** false + * **connTimeout** - An Integer indicating the number of milliseconds to wait for a connection to be established. **Default:** 10000 -* **connect**() - _(void)_ - Attempts to connect and log into the IMAP server. +* **connect**(Function) - _(void)_ - Attempts to connect and log into the IMAP server. The Function parameter is the callback with one parameter: the error (null if none). * **logout**(Function) - _(void)_ - Closes the connection to the server. The Function parameter is the callback. @@ -252,7 +256,6 @@ TODO A bunch of things not yet implemented in no particular order: -* Connection timeout * Support AUTH=CRAM-MD5/AUTH=CRAM_MD5 authentication * OR searching ability with () grouping * HEADER.FIELDS.NOT capability during FETCH using "!" prefix diff --git a/imap.js b/imap.js index b496c11..5295d3f 100644 --- a/imap.js +++ b/imap.js @@ -1,13 +1,18 @@ var sys = require('sys'), net = require('net'), EventEmitter = require('events').EventEmitter; -var empty = function() {}, CRLF = "\r\n", debug=empty/*sys.debug*/, STATES = { NOCONNECT: 0, NOAUTH: 1, AUTH: 2, BOXSELECTING: 3, BOXSELECTED: 4 }; +var emptyFn = function() {}, CRLF = "\r\n", debug=emptyFn/*sys.debug*/, STATES = { NOCONNECT: 0, NOAUTH: 1, AUTH: 2, BOXSELECTING: 3, BOXSELECTED: 4 }; function ImapConnection (options) { + if (!(this instanceof ImapConnection)) + return new ImapConnection(options); + EventEmitter.call(this); + this._options = { username: '', password: '', host: 'localhost', port: 143, - secure: false + secure: false, + connTimeout: 10000 // connection timeout in msecs }; this._state = { status: STATES.NOCONNECT, @@ -25,6 +30,7 @@ function ImapConnection (options) { box: { _uidnext: 0, _uidvalidity: 0, _flags: [], permFlags: [], name: null, messages: { total: 0, new: 0 }} }; this._capabilities = []; + this._tmrConn = null; this._options = extend(true, this._options, options); }; @@ -32,34 +38,49 @@ sys.inherits(ImapConnection, EventEmitter); exports.ImapConnection = ImapConnection; ImapConnection.prototype.connect = function(loginCb) { - var self = this; - var fnInit = function() { - // First get pre-auth capabilities, including server-supported auth mechanisms - self._send('CAPABILITY', function() { - // Next attempt to login - self._login(function(err) { - if (err) { - loginCb(err); - return; - } - // Lastly, get the mailbox hierarchy delimiter/separator used by the server - self._send('LIST "" ""', loginCb); - }); - }); - }; + var self = this, + skipSetup = (this._state.conn !== null), + fnInit = function() { + // First get pre-auth capabilities, including server-supported auth mechanisms + self._send('CAPABILITY', function() { + // Next attempt to login + self._login(function(err) { + if (err) { + loginCb(err); + return; + } + // Lastly, get the mailbox hierarchy delimiter/separator used by the server + self._send('LIST "" ""', loginCb); + }); + }); + }; + loginCb = loginCb || emptyFn; this._reset(); - this._state.conn = net.createConnection(this._options.port, this._options.host); + if (!this._state.conn) + this._state.conn = net.createConnection(this._options.port, this._options.host); + else + this._state.conn.connect(this._options.port, this._options.host); if (this._options.secure) { this._state.conn.setSecure(); - this._state.conn.on('secure', function() { - debug('Secure connection made.'); - }); - } + if (this._state.conn.listeners('secure').length === 0) { + this._state.conn.on('secure', function() { + debug('Secure connection made.'); + }); + } + } else + this._state.conn.secure = false; + + this._tmrConn = setTimeout(this._fnTmrConn, this._options.connTimeout, loginCb); + + if (skipSetup) + return; + this._state.conn.setKeepAlive(true); this._state.conn.setEncoding('utf8'); this._state.conn.on('connect', function() { + clearTimeout(self._tmrConn); debug('Connected to host.'); self._state.conn.write(''); self._state.status = STATES.NOAUTH; @@ -182,7 +203,6 @@ ImapConnection.prototype.connect = function(loginCb) { } } } else if (data[0].indexOf('A') === 0) { // Tagged server response - //var id = data[0].substr(1); clearTimeout(self._state.tmrKeepalive); self._state.tmrKeepalive = setTimeout(self._idleCheck.bind(self), self._state.tmoKeepalive); @@ -223,15 +243,15 @@ ImapConnection.prototype.connect = function(loginCb) { // unknown response } }); - this._state.conn.on('error', function(err) { - debug('Encountered error: ' + err); - }); this._state.conn.on('end', function() { self._reset(); debug('FIN packet received. Disconnecting...'); self.emit('end'); }); this._state.conn.on('error', function(err) { + clearTimeout(self._tmrConn); + if (self._state.status === STATES.NOCONNECT) + loginCb(new Error('Unable to connect. Reason: ' + err)); self.emit('error', err); debug('Error occurred: ' + err); }); @@ -447,6 +467,11 @@ ImapConnection.prototype.delFlags = function(uid, flags, cb) { /****** Private Functions ******/ +ImapConnection.prototype._fnTmrConn = function(loginCb) { + loginCb(new Error('Connection timed out')); + this._state.conn.destroy(); +} + ImapConnection.prototype._storeFlag = function(uid, flags, isAdding, cb) { if (this._state.status !== STATES.BOXSELECTED) throw new Error('No mailbox is currently selected'); @@ -494,6 +519,7 @@ ImapConnection.prototype._login = function(cb) { }; ImapConnection.prototype._reset = function() { clearTimeout(this._state.tmrKeepalive); + clearTimeout(this._tmrConn); this._state.status = STATES.NOCONNECT; this._state.numCapRecvs = 0; this._state.requests = [];