From f1c552ae91c0e5b64b87e3ec1ba8e9cd81db21de Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Sun, 29 Apr 2012 21:49:27 +0300 Subject: [PATCH 001/106] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4f25656..421b3da 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ Where Example (output message contents to console) - client.reateMessageStream(123).pipe(process.stdout, {end: false}); + client.createMessageStream(123).pipe(process.stdout, {end: false}); **NB!* If the opened mailbox is not in read-only mode, the message will be automatically marked as read (\Seen flag is set) when the message is fetched. From d5d314b7e3f1b9038083b1dbb591be8ffa9e25fa Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 30 Apr 2012 15:47:24 +0300 Subject: [PATCH 002/106] updated version --- package.json | 2 +- tools/clientplayground.js | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index b78766a..919926a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.3", + "version": "0.1.4", "author" : "Andris Reinman", "maintainers":[ { diff --git a/tools/clientplayground.js b/tools/clientplayground.js index a20c614..b54f09a 100644 --- a/tools/clientplayground.js +++ b/tools/clientplayground.js @@ -28,9 +28,13 @@ client.on("connect", function(){ console.log(message); }); - var stream = client.createMessageStream(52); - client.createMessageStream(52).pipe(process.stdout, {end: false}); - + //var stream = client.createMessageStream(52); + //client.createMessageStream(52).pipe(process.stdout, {end: false}); + + client.updateFlags(52, ["\\Answered", "\\Flagged"], "+", console.log) + client.removeFlags(52, ["\\Answered", "\\Flagged"], console.log) + client.addFlags(52, ["\\Flagged"], console.log) + }); // on new messages, print to console From 0c17935667f09598cb9fc765e87817c916a4436c Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 30 Apr 2012 15:51:04 +0300 Subject: [PATCH 003/106] updated README --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f022fb6..2591037 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ Where * **name** is the name of the mailbox (ie. "INBOX") * **options** is an optional options object * **options.readOnly** - if set to true, open the mailbox in read-only mode (downloading messages does not update seen/unseen flag) - * **callback** is a callback function to run after the mailbox has been opened. Has an error param in case the opening failed and a mailbox param with the properties of the opened mailbox. + * **callback** *(error, mailbox)* is a callback function to run after the mailbox has been opened. Has an error param in case the opening failed and a mailbox param with the properties of the opened mailbox. Example @@ -126,7 +126,7 @@ Where * **from** is the index of the first message (0 based), you can use negative numbers to count from the end (-10 indicates the 10 last messages) * **limit** defines the maximum count of messages to fetch, if not set or 0 all messages from the starting position will be included - * **callback** is the callback function to run with the message array + * **callback** *(error, messages)* is the callback function to run with the message array Example @@ -146,7 +146,7 @@ To fetch message data (flags, title, etc) for a specific message, use Where * **uid** is the UID value for the mail - * **callback** is the callback function to with the message data object (or null if the message was not found). Gets an error parameter if error occured + * **callback** *(error, message)* is the callback function to with the message data object (or null if the message was not found). Gets an error parameter if error occured Example @@ -184,7 +184,7 @@ Where * **uid** is the message identifier * **flags** is the array of flags to be added - * **callback** *(err, flags)* is the callback to run, gets message flags array as a parameter + * **callback** *(error, flags)* is the callback to run, gets message flags array as a parameter **Remove flags** @@ -194,7 +194,7 @@ Where * **uid** is the message identifier * **flags** is the array of flags to be removed - * **callback** *(err, flags)* is the callback to run, gets message flags array as a parameter + * **callback** *(error, flags)* is the callback to run, gets message flags array as a parameter Example From 2ad63c9bd25c529dd4ac2b5b68857598a4c18328 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 30 Apr 2012 15:57:21 +0300 Subject: [PATCH 004/106] updated README --- README.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/README.md b/README.md index 2591037..8f3ed11 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,55 @@ Example }); }); +Example output for a message listing + + [ + { + // if uidvalidity changes, all uid values are void! + UIDValidity: '664399135', + + // uid value of the message + UID: 52, + + // message flags (Array) + flags: [ '\\Flagged', '\\Seen' ], + + // date of the message (Date object) + date: Wed, 25 Apr 2012 12:23:05 GMT, + + title: 'This is a message, may contain unicode symbols', + + // single "from:" address + from: { + name: 'Andris Reinman', + address: 'andris.reinman@gmail.com' + }, + + // an array of "to:" addresses + to: [ + { + name: 'test nodemailer', + address: 'test.nodemailer@gmail.com' + } + ], + + // an array of "cc:" addresses + cc: [ + { + name: 'test nodemailer', + address: 'test.nodemailer@gmail.com' + } + ], + + messageId: '<04541AB5-9FBD-4255-81AA-18FE67CB97E5@gmail.com>' + }, + ... + ] + +**NB!** if some properties are not present in a message, it may be not included +in the message object - for example, if there are no "cc:" addresses listed, +there is no "cc" field in the message object + ### Fetch message details To fetch message data (flags, title, etc) for a specific message, use From 7c310bb3323cbc1bde927d14c5edb8352ca803e3 Mon Sep 17 00:00:00 2001 From: Christian Ress Date: Wed, 2 May 2012 23:45:52 +0200 Subject: [PATCH 005/106] Detecting XLIST capabilities in _handlerTaggedListRoot didn't work An indexOf check wasn't evaluated correctly. --- lib/client.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/client.js b/lib/client.js index 19db7c3..15489da 100644 --- a/lib/client.js +++ b/lib/client.js @@ -799,7 +799,7 @@ IMAPClient.prototype._handlerTaggedSelect = function(callback, status, params){ IMAPClient.prototype._handlerTaggedListRoot = function(callback, status){ var command = "LIST", mailboxes, i, len; - if(this._capabilities.indexOf("XLIST")){ + if(this._capabilities.indexOf("XLIST")>=0){ command = "XLIST"; } @@ -1542,4 +1542,4 @@ IMAPClient.prototype.close = function(){ if(socket && !socket.destroyed){ socket.destroy(); } -}; \ No newline at end of file +}; From 836911f61a4c4bd04423e1f34cddddc90cc90850 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Thu, 3 May 2012 17:22:14 +0300 Subject: [PATCH 006/106] update --- README.md | 37 ++- examples/list.js | 4 +- lib/client.js | 484 ++++++++++++++++++++++++-------------- lib/names.json | 6 + package.json | 5 +- tools/clientplayground.js | 21 +- 6 files changed, 358 insertions(+), 199 deletions(-) create mode 100644 lib/names.json diff --git a/README.md b/README.md index 8f3ed11..6381571 100644 --- a/README.md +++ b/README.md @@ -75,24 +75,37 @@ When the connection has been successfully established a 'connect' event is emitt ### List available mailboxes -To get the list of available mailboxes, use +To list the available mailboxes use - client.getMailboxList() + client.listRoot(callback) + +Where -which returns the mailbox list + * **callback** *(error, mailboxes)* returns a list of root mailbox object + +Mailbox objects have the following properties -Example + * **name** - the display name of the mailbox + * **path** - the actual name of the mailbox, use it for opening the mailbox + * **type** - the type of the mailbox (if server hints about it) + * **hasChildren** - boolean indicator, if true, has child mailboxes + * **disabled** - boolean indicator, if true, can not be selected - console.log(client.getMailboxList()); +Additionally mailboxes have the following methods -Output: + * **listChildren** *(callback)* - if the mailbox has children (*hasChildren* is true), lists the child mailboxes - { - INBOX: { name: 'INBOX', inbox: true }, - Drafts: { name: 'Drafts' }, - Sent: { name: 'Sent', disabled: true}, - Junk: { name: 'Junk' } - } +Example: + + client.listRoot(function(error, mailboxes){ + for(var i=0, len = mailboxes.length; i=0){ + this._send("ID (" + + "\"name\" "+this._escapeString(X_CLIENT_NAME) + + (packageData.version?" \"version\" "+this._escapeString(packageData.version):"") + + (X_CLIENT_URL?" \"support-url\" "+this._escapeString(X_CLIENT_URL):"") + + ")", this._handlerTaggedId.bind(this)); + }else{ + this._postReady(); + } +} + +/** + * Handle mailbox listing with LSUB + * + * @param {String} status If "OK" then the command succeeded + */ +IMAPClient.prototype._handlerTaggedLsub = function(xinfo, callback, status){ + if(status != "OK"){ + if(typeof callback == "function"){ + callback(new Error("Mailbox listing failed")); + } + return; + } + + var curXinfo, curName; + + for(var i=0, len = xinfo.length; i=0){ + curName = "INBOX"; + } + + for(var j=0, jlen = this._mailboxList.length; j=0){ + this._mailboxList[j].type = "Inbox"; + }else if(curXinfo.tags.indexOf("\\All")>=0 || curXinfo.tags.indexOf("\\AllMail")>=0){ + this._mailboxList[j].type = "All Mail"; + }else if(curXinfo.tags.indexOf("\\Archive")>=0){ + this._mailboxList[j].type = "Archive"; + }else if(curXinfo.tags.indexOf("\\Drafts")>=0){ + this._mailboxList[j].type = "Drafts"; + }else if(curXinfo.tags.indexOf("\\Sent")>=0){ + this._mailboxList[j].type = "Sent"; + }else if(curXinfo.tags.indexOf("\\Junk")>=0 || curXinfo.tags.indexOf("\\Spam")>=0){ + this._mailboxList[j].type = "Junk"; + }else if(curXinfo.tags.indexOf("\\Flagged")>=0 || curXinfo.tags.indexOf("\\Starred")>=0 || curXinfo.tags.indexOf("\\Important")>=0){ + this._mailboxList[j].type = "Flagged"; + }else if(curXinfo.tags.indexOf("\\Trash")>=0){ + this._mailboxList[j].type = "Trash"; + } + + break; + } + } + } + + if(typeof callback == "function"){ + callback(null, this._mailboxList); + } +} + /** * Handle SELECT and EXAMINE commands. If succeeded, move to SELECTED state. * If callback is set runs it with selected mailbox data @@ -790,138 +895,102 @@ IMAPClient.prototype._handlerTaggedSelect = function(callback, status, params){ } }; + +// HANDLERS FOR UNTAGGED RESPONSES + /** - * Handle LIST % command. If any of the mailboxes has children, check these as well + * Handle untagged CAPABILITY response, store params to _capabilities array * - * @param {Function} callback Callback function to run on completion - * @param {String} status If "OK" then the command succeeded + * @param {Array} list Params for "* CAPABILITY" as an array */ -IMAPClient.prototype._handlerTaggedListRoot = function(callback, status){ - var command = "LIST", mailboxes, i, len; - - if(this._capabilities.indexOf("XLIST")>=0){ - command = "XLIST"; - } - - if(status == "OK"){ - // check if child boxes available - mailboxes = Object.keys(this._mailboxList); - for(i=0, len = mailboxes.length; i1){ - if(mailboxList[nameParts[0]] && mailboxList[nameParts[0]].childNodes){ - mailboxList = mailboxList[nameParts[0]].childNodes; - name = nameParts.slice(1).join(delimiter); - }else{ - return; - } - }else{ - name = fullname; - } - - mailbox = { - name: this._mailboxRoot + fullname - }; + path = (list.shift() || "").substr(this._rootPath.length), + name = delimiter?path.split(delimiter).pop():path, + mailbox = new this.Mailbox({ + client: this, + path: path, + name: this._convertFromUTF7(name), + delimiter: delimiter + }); if(tags.indexOf("\\HasChildren")>=0){ - mailbox.childNodes = {}; + mailbox.hasChildren = true; } - if(tags.indexOf("\\Inbox")>=0){ - mailbox.name = "INBOX"; - name = "INBOX"; - mailbox.inbox = true; - this._inboxName = "INBOX"; + if(name == "INBOX"){ + mailbox.type="Inbox"; } - if(tags.indexOf("\\Sent")>=0){ - mailbox.sent = true; - this._outgoingName = this._outgoingName || fullname; // prefer previous - } - - if(tags.indexOf("\\Noselect")>=0){ + if(tags.indexOf("\\Noselect")>=0 && path != "INBOX"){ mailbox.disabled = true; } - mailboxList[name] = mailbox; + this._mailboxList.push(mailbox); }; /** @@ -1004,14 +1073,19 @@ IMAPClient.prototype._postCapability = function(){ }; /** - * Run when user is successfully entered AUTH state. If NAMESPACE capability - * is detected, run it, otherwise fetch the mailbox list. + * Run when user is successfully entered AUTH state */ IMAPClient.prototype._postAuth = function(){ - if(this._capabilities.indexOf("NAMESPACE")>=0){ - this._send("NAMESPACE", this._handlerTaggedNamespace.bind(this)); + if(this._capabilities.indexOf("CONDSTORE")>=0){ + this._send("ENABLE CONDSTORE", this._handlerTaggedCondstore.bind(this)) + }else if(this._capabilities.indexOf("ID")>=0){ + this._send("ID (" + + "\"name\" "+this._escapeString(X_CLIENT_NAME) + + (packageData.version?" \"version\" "+this._escapeString(packageData.version):"") + + (X_CLIENT_URL?" \"support-url\" "+this._escapeString(X_CLIENT_URL):"") + + ")", this._handlerTaggedId.bind(this)); }else{ - this.fetchMailboxList(this._postReady.bind(this)); + this._postReady(); } }; @@ -1029,64 +1103,6 @@ IMAPClient.prototype._postReady = function(err){ } }; -/** - * Run after LIST command is completed. Sort the mailbox array and return it - * with callback - * - * @param {Function} callback Callback function to run after LIST data is gathered - */ -IMAPClient.prototype._postFetchList = function(callback){ - var keys = Object.keys(this._mailboxList), - i, len, - sortedList = {}; - - if(!this._outgoingName){ - for(i=0, len = keys.length; i=0){ + Object.defineProperty(this, "client", { + value: options.client || {}, + enumerable: false + }); + + this.name = options.name || ""; + this.path = options.path || this.name; + this.type = options.type || this.detectType(); + this.delimiter = options.delimiter || this.client._mailboxDelimiter || ""; +} + +IMAPClient.prototype.Mailbox.prototype.detectType = function(){ + + if(mailboxNames.sent.indexOf(this.name.toLowerCase())>=0){ + return "Sent"; + } + + if(mailboxNames.trash.indexOf(this.name.toLowerCase())>=0){ + return "Trash"; + } + + if(mailboxNames.junk.indexOf(this.name.toLowerCase())>=0){ + return "Junk"; + } + + if(mailboxNames.drafts.indexOf(this.name.toLowerCase())>=0){ + return "Drafts"; + } + + return "Normal"; +} + +IMAPClient.prototype.Mailbox.prototype.listChildren = function(callback){ + var path = this.client._escapeString(this.path ? this.path + this.delimiter + "%":"%"); + + if(this.client._capabilities.indexOf("SPECIAL-USE")>=0 || this.client._capabilities.indexOf("XLIST")>=0){ + this.gatherChildInfo(path, callback); + return; + } + + this.listSubscribed(path, callback); +} + +IMAPClient.prototype.Mailbox.prototype.gatherChildInfo = function(path, callback){ + var command, suffix = ""; + + if(this.client._capabilities.indexOf("SPECIAL-USE")>=0){ + command = "LIST"; + suffix = " RETURN (SPECIAL-USE)"; + }else if(this.client._capabilities.indexOf("XLIST")>=0){ command = "XLIST"; } - this._mailboxList = {}; - this._send(command+" "+this._escapeString(this._mailboxRoot)+" %", this._handlerTaggedListRoot.bind(this, callback)); -}; + this.client._send(command+" "+this.client._escapeString(this.client._rootPath) + " " + path + suffix, + (function(status){ + this.listSubscribed(path, this.client._mailboxList, callback); + }).bind(this), + (function(){ + this.client._mailboxList = []; + }).bind(this)); + +} + +IMAPClient.prototype.Mailbox.prototype.listSubscribed = function(path, xinfo, callback){ + if(!callback && typeof xinfo == "function"){ + callback = xinfo; + xinfo = undefined; + } + + xinfo = xinfo || []; + + this.client._send("LSUB "+this.client._escapeString(this.client._rootPath)+" "+path, + this.client._handlerTaggedLsub.bind(this.client, xinfo, callback), + (function(){ + this.client._mailboxList = []; + }).bind(this)); +} + +IMAPClient.prototype.Mailbox.prototype.createChild = function(name, callback){ + var path = (this.path ? this.path + this.delimiter + name:name); + this.client._send("CREATE "+this.client._escapeString(path), (function(status){ + if(typeof callback == "function"){ + if(status == "OK"){ + callback(null, new this.Mailbox({ + client: this, + path: path, + name: name, + delimiter: this.delimiter + })); + }else{ + callback(new Error("Creating mailbox failed")); + } + } + }).bind(this)); +} + +// PUBLIC API /** - * Returns the cached mailbox list + * Lists root mailboxes * - * @return {Array} Mailbox list + * @param {Function} callback Callback function to run with the mailbox list */ -IMAPClient.prototype.getMailboxList = function(){ - return this._mailboxList; -}; +IMAPClient.prototype.listRoot = function(callback){ + this._rootMailbox.listChildren(callback); +} /** * Opens a selected mailbox. This is needed before you can open any message. @@ -1255,7 +1371,9 @@ IMAPClient.prototype.openMailbox = function(mailboxName, options, callback){ name: mailboxName }; - this._send(command + " " + this._escapeString(mailboxName), this._handlerTaggedSelect.bind(this, callback)); + this._send(command + " " + this._escapeString(mailboxName)+( + this._condstoreEnabled?" (CONDSTORE)":"" + ), this._handlerTaggedSelect.bind(this, callback)); }; /** diff --git a/lib/names.json b/lib/names.json new file mode 100644 index 0000000..6fd588f --- /dev/null +++ b/lib/names.json @@ -0,0 +1,6 @@ +{ + "sent": ["aika", "bidaliak", "bidalita", "dihantar", "e rometsweng", "e tindami", "elküldött", "elküldöttek", "enviadas", "enviadas", "enviados", "enviats", "envoyés", "ethunyelweyo", "expediate", "ezipuru", "gesendete", "gestuur", "gönderilmiş öğeler", "göndərilənlər", "iberilen", "inviati", "išsiųstieji", "kuthunyelwe", "lasa", "lähetetyt", "messages envoyés", "naipadala", "nalefa", "napadala", "nosūtītās ziņas", "odeslané", "padala", "poslane", "poslano", "poslano", "poslané", "poslato", "saadetud", "sendt", "sendt", "sent", "sent items", "sent messages", "sända poster", "sänt", "terkirim", "ti fi ranṣẹ", "të dërguara", "verzonden", "vilivyotumwa", "wysłane", "đã gửi", "σταλθέντα", "жиберилген", "жіберілгендер", "изпратени", "илгээсэн", "ирсол шуд", "испратено", "надіслані", "отправленные", "пасланыя", "юборилган", "ուղարկված", "נשלחו", "פריטים שנשלחו", "المرسلة", "بھیجے گئے", "سوزمژہ", "لېګل شوی", "موارد ارسال شده", "पाठविले", "पाठविलेले", "प्रेषित", "भेजा गया", "প্রেরিত", "প্রেরিত", "প্ৰেৰিত", "ਭੇਜੇ", "મોકલેલા", "ପଠାଗଲା", "அனுப்பியவை", "పంపించబడింది", "ಕಳುಹಿಸಲಾದ", "അയച്ചു", "යැවු පණිවුඩ", "ส่งแล้ว", "გაგზავნილი", "የተላኩ", "បាន​ផ្ញើ", "寄件備份", "寄件備份", "已发信息", "送信済みメール", "발신 메시지", "보낸 편지함"], + "trash": ["articole șterse", "bin", "borttagna objekt", "deleted", "deleted items", "deleted messages", "elementi eliminati", "elementos borrados", "elementos eliminados", "gelöschte objekte", "item dipadam", "itens apagados", "itens excluídos", "mục đã xóa", "odstraněné položky", "pesan terhapus", "poistetut", "praht", "silinmiş öğeler", "slettede beskeder", "slettede elementer", "trash", "törölt elemek", "usunięte wiadomości", "verwijderde items", "vymazané správy", "éléments supprimés", "видалені", "жойылғандар", "удаленные", "פריטים שנמחקו", "العناصر المحذوفة", "موارد حذف شده", "รายการที่ลบ", "已删除邮件", "已刪除項目", "已刪除項目"], + "junk": ["bulk mail", "correo no deseado", "courrier indésirable", "istenmeyen", "istenmeyen e-posta", "junk", "levélszemét", "nevyžiadaná pošta", "nevyžádaná pošta", "no deseado", "posta indesiderata", "pourriel", "roskaposti", "skräppost", "spam", "spam", "spamowanie", "søppelpost", "thư rác", "спам", "דואר זבל", "الرسائل العشوائية", "هرزنامه", "สแปม", "‎垃圾郵件", "垃圾邮件", "垃圾電郵"], + "drafts": ["ba brouillon", "borrador", "borrador", "borradores", "bozze", "brouillons", "bản thảo", "ciorne", "concepten", "draf", "drafts", "drög", "entwürfe", "esborranys", "garalamalar", "ihe edeturu", "iidrafti", "izinhlaka", "juodraščiai", "kladd", "kladder", "koncepty", "koncepty", "konsep", "konsepte", "kopie robocze", "layihələr", "luonnokset", "melnraksti", "meralo", "mesazhe të padërguara", "mga draft", "mustandid", "nacrti", "nacrti", "osnutki", "piszkozatok", "rascunhos", "rasimu", "skice", "taslaklar", "tsararrun saƙonni", "utkast", "vakiraoka", "vázlatok", "zirriborroak", "àwọn àkọpamọ́", "πρόχειρα", "жобалар", "нацрти", "нооргууд", "сиёҳнавис", "хомаки хатлар", "чарнавікі", "чернетки", "чернови", "черновики", "черновиктер", "սևագրեր", "טיוטות", "مسودات", "مسودات", "موسودې", "پیش نویسها", "ڈرافٹ/", "ड्राफ़्ट", "प्रारूप", "খসড়া", "খসড়া", "ড্ৰাফ্ট", "ਡ੍ਰਾਫਟ", "ડ્રાફ્ટસ", "ଡ୍ରାଫ୍ଟ", "வரைவுகள்", "చిత్తు ప్రతులు", "ಕರಡುಗಳು", "കരടുകള്‍", "කෙටුම් පත්", "ฉบับร่าง", "მონახაზები", "ረቂቆች", "សារព្រាង", "下書き", "草稿", "草稿", "草稿", "임시 보관함"] +} \ No newline at end of file diff --git a/package.json b/package.json index 919926a..2cf11b8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.4", + "version": "0.1.5", "author" : "Andris Reinman", "maintainers":[ { @@ -12,7 +12,8 @@ "main": "lib/client.js", "dependencies": { - "mimelib": "*" + "mimelib": "*", + "iconv": "*" }, "devDependencies": { diff --git a/tools/clientplayground.js b/tools/clientplayground.js index b54f09a..d21c4ab 100644 --- a/tools/clientplayground.js +++ b/tools/clientplayground.js @@ -13,7 +13,6 @@ var client = inbox.createConnection(false, "imap.gmail.com", { client.connect(); client.on("connect", function(){ - console.log(client.getMailboxList()); client.openMailbox("INBOX", function(error, mailbox){ if(error) throw error; @@ -24,6 +23,7 @@ client.on("connect", function(){ }); }); + /* client.fetchData(52, function(err, message){ console.log(message); }); @@ -34,6 +34,25 @@ client.on("connect", function(){ client.updateFlags(52, ["\\Answered", "\\Flagged"], "+", console.log) client.removeFlags(52, ["\\Answered", "\\Flagged"], console.log) client.addFlags(52, ["\\Flagged"], console.log) + */ + + function walkMailboxes(name, level, node){ + level = level || 0; + (node.listChildren || node.listRoot).call(node, function(err, list){ + if(err){return;} + console.log("> "+name); + for(var i=0; i Date: Fri, 4 May 2012 00:04:37 +0300 Subject: [PATCH 007/106] Mailbox API updates --- README.md | 35 +++++++-- examples/list.js | 4 +- examples/xoauth.js | 4 +- lib/client.js | 138 ++++++++--------------------------- lib/mailbox.js | 148 ++++++++++++++++++++++++++++++++++++++ tools/clientplayground.js | 19 ++++- 6 files changed, 225 insertions(+), 123 deletions(-) create mode 100644 lib/mailbox.js diff --git a/README.md b/README.md index 6381571..363b089 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ When the connection has been successfully established a 'connect' event is emitt To list the available mailboxes use - client.listRoot(callback) + client.listMailboxes(callback) Where @@ -93,11 +93,12 @@ Mailbox objects have the following properties Additionally mailboxes have the following methods + * **open** *([options, ]callback)* - open the mailbox (shorthand for *client.openMailbox*) * **listChildren** *(callback)* - if the mailbox has children (*hasChildren* is true), lists the child mailboxes Example: - client.listRoot(function(error, mailboxes){ + client.listMailboxes(function(error, mailboxes){ for(var i=0, len = mailboxes.length; i=0){ + this._mailboxList[j].disabled = true; + } if(curXinfo.tags.indexOf("\\Inbox")>=0){ this._mailboxList[j].type = "Inbox"; @@ -971,7 +976,7 @@ IMAPClient.prototype._handlerUntaggedLsub = function(list){ delimiter = list.shift() || this._mailboxDelimiter, path = (list.shift() || "").substr(this._rootPath.length), name = delimiter?path.split(delimiter).pop():path, - mailbox = new this.Mailbox({ + mailbox = new Mailbox({ client: this, path: path, name: this._convertFromUTF7(name), @@ -986,10 +991,6 @@ IMAPClient.prototype._handlerUntaggedLsub = function(list){ mailbox.type="Inbox"; } - if(tags.indexOf("\\Noselect")>=0 && path != "INBOX"){ - mailbox.disabled = true; - } - this._mailboxList.push(mailbox); }; @@ -1209,7 +1210,7 @@ IMAPClient.prototype._convertFromUTF7 = function(str){ try{ return str.replace(/&([^\-]+)\-/g, function(a, r){ r = (r || "").replace(/,/g, "/"); - return iconv.convert(new Buffer(r, "base64")).toString("utf-8"); + return fromUTF7.convert(new Buffer(r, "base64")).toString("utf-8"); }); }catch(E){ return str; @@ -1232,106 +1233,6 @@ IMAPClient.prototype._checkNewMail = function(){ }).bind(this)); }; - -IMAPClient.prototype.Mailbox = function(options){ - options = options || {}; - - Object.defineProperty(this, "client", { - value: options.client || {}, - enumerable: false - }); - - this.name = options.name || ""; - this.path = options.path || this.name; - this.type = options.type || this.detectType(); - this.delimiter = options.delimiter || this.client._mailboxDelimiter || ""; -} - -IMAPClient.prototype.Mailbox.prototype.detectType = function(){ - - if(mailboxNames.sent.indexOf(this.name.toLowerCase())>=0){ - return "Sent"; - } - - if(mailboxNames.trash.indexOf(this.name.toLowerCase())>=0){ - return "Trash"; - } - - if(mailboxNames.junk.indexOf(this.name.toLowerCase())>=0){ - return "Junk"; - } - - if(mailboxNames.drafts.indexOf(this.name.toLowerCase())>=0){ - return "Drafts"; - } - - return "Normal"; -} - -IMAPClient.prototype.Mailbox.prototype.listChildren = function(callback){ - var path = this.client._escapeString(this.path ? this.path + this.delimiter + "%":"%"); - - if(this.client._capabilities.indexOf("SPECIAL-USE")>=0 || this.client._capabilities.indexOf("XLIST")>=0){ - this.gatherChildInfo(path, callback); - return; - } - - this.listSubscribed(path, callback); -} - -IMAPClient.prototype.Mailbox.prototype.gatherChildInfo = function(path, callback){ - var command, suffix = ""; - - if(this.client._capabilities.indexOf("SPECIAL-USE")>=0){ - command = "LIST"; - suffix = " RETURN (SPECIAL-USE)"; - }else if(this.client._capabilities.indexOf("XLIST")>=0){ - command = "XLIST"; - } - - this.client._send(command+" "+this.client._escapeString(this.client._rootPath) + " " + path + suffix, - (function(status){ - this.listSubscribed(path, this.client._mailboxList, callback); - }).bind(this), - (function(){ - this.client._mailboxList = []; - }).bind(this)); - -} - -IMAPClient.prototype.Mailbox.prototype.listSubscribed = function(path, xinfo, callback){ - if(!callback && typeof xinfo == "function"){ - callback = xinfo; - xinfo = undefined; - } - - xinfo = xinfo || []; - - this.client._send("LSUB "+this.client._escapeString(this.client._rootPath)+" "+path, - this.client._handlerTaggedLsub.bind(this.client, xinfo, callback), - (function(){ - this.client._mailboxList = []; - }).bind(this)); -} - -IMAPClient.prototype.Mailbox.prototype.createChild = function(name, callback){ - var path = (this.path ? this.path + this.delimiter + name:name); - this.client._send("CREATE "+this.client._escapeString(path), (function(status){ - if(typeof callback == "function"){ - if(status == "OK"){ - callback(null, new this.Mailbox({ - client: this, - path: path, - name: name, - delimiter: this.delimiter - })); - }else{ - callback(new Error("Creating mailbox failed")); - } - } - }).bind(this)); -} - // PUBLIC API /** @@ -1339,7 +1240,7 @@ IMAPClient.prototype.Mailbox.prototype.createChild = function(name, callback){ * * @param {Function} callback Callback function to run with the mailbox list */ -IMAPClient.prototype.listRoot = function(callback){ +IMAPClient.prototype.listMailboxes = function(callback){ this._rootMailbox.listChildren(callback); } @@ -1365,6 +1266,10 @@ IMAPClient.prototype.openMailbox = function(mailboxName, options, callback){ command = "EXAMINE"; } + if(typeof mailboxName == "object"){ + mailboxName = mailboxName.path; + } + mailboxName = mailboxName || this._inboxName || "INBOX"; this._selectedMailbox = { @@ -1637,6 +1542,19 @@ IMAPClient.prototype.createMessageStream = function(uid){ return stream; }; + +IMAPClient.prototype.getMailbox = function(path, callback){ + this._rootMailbox.listChildren(path, function(error, mailboxes){ + if(error){ + callback(error); + }else if(mailboxes && mailboxes.length){ + callback(null, mailboxes[0]); + }else{ + callback(null, null); + } + }); +} + /** * Enter IDLE mode */ diff --git a/lib/mailbox.js b/lib/mailbox.js new file mode 100644 index 0000000..045fbf3 --- /dev/null +++ b/lib/mailbox.js @@ -0,0 +1,148 @@ +var mailboxNames = require("./names.json"); + +/** + * Expose to the world + * @namespace mailbox + */ +module.exports.Mailbox = Mailbox; + +/** + * Create a mailbox object + * + * @memberOf mailbox + * @constructor + * @param {Object} options Options object + */ +function Mailbox(options){ + options = options || {}; + + Object.defineProperty(this, "client", { + value: options.client || {}, + enumerable: false + }); + + Object.defineProperty(this, "tags", { + value: options.tags || [], + enumerable: false + }); + + this.name = options.name || ""; + this.path = options.path || this.name; + this.type = options.type || this.detectType(); + this.delimiter = options.delimiter || this.client._mailboxDelimiter || ""; +} + +/** + * Open the mailbox + * + * @param {Object} [options] Optional options object + * @param {Boolean} [options.readOnly] If set to true, open the mailbox in read-only mode (seen/unseen flags won't be touched) + * @param {Function} callback Callback function to run when the mailbox is opened + */ +Mailbox.prototype.open = function(options, callback){ + this.client.openMailbox(this.path, options, callback) +} + +/** + * Detects the type by the name of the mailbox + */ +Mailbox.prototype.detectType = function(){ + + if(mailboxNames.sent.indexOf(this.name.toLowerCase())>=0){ + return "Sent"; + } + + if(mailboxNames.trash.indexOf(this.name.toLowerCase())>=0){ + return "Trash"; + } + + if(mailboxNames.junk.indexOf(this.name.toLowerCase())>=0){ + return "Junk"; + } + + if(mailboxNames.drafts.indexOf(this.name.toLowerCase())>=0){ + return "Drafts"; + } + + return "Normal"; +} + +/** + * Lists children for the mailbox + * + * @param {Function} callback Callback function to run with the mailbox list + */ +Mailbox.prototype.listChildren = function(path, callback){ + if(!callback && typeof path == "function"){ + callback = path; + path = undefined; + } + + var path = this.client._escapeString(path || (this.path ? this.path + this.delimiter + "%":"%")), + command = "LIST", suffix = ""; + + if(this.client._capabilities.indexOf("SPECIAL-USE")>=0){ + command = "LIST"; + suffix = " RETURN (SPECIAL-USE)"; + }else if(this.client._capabilities.indexOf("XLIST")>=0){ + command = "XLIST"; + } + + this.client._send(command+" "+this.client._escapeString(this.client._rootPath) + " " + path + suffix, + (function(status){ + this.listSubscribed(path, this.client._mailboxList, callback); + }).bind(this), + (function(){ + this.client._mailboxList = []; + }).bind(this)); + +} + +/** + * Fetches subscribed mailboxes + * + * @param {String} path Parent mailbox + * @param {Array} xinfo Results from XLIST or LIST + * @param {Function} callback Callback function to run with the mailbox list + */ +Mailbox.prototype.listSubscribed = function(path, xinfo, callback){ + if(!callback && typeof xinfo == "function"){ + callback = xinfo; + xinfo = undefined; + } + + xinfo = xinfo || []; + + this.client._send("LSUB "+this.client._escapeString(this.client._rootPath)+" "+path, + this.client._handlerTaggedLsub.bind(this.client, xinfo, callback), + (function(){ + this.client._mailboxList = []; + }).bind(this)); +} + +/** + * Creates a new mailbox and subscribes to it + * + * @param {String} name Name of the mailbox + * @param {Function} callback Callback function to run with the created mailbox object + */ +Mailbox.prototype.createChild = function(name, callback){ + var path = (this.path ? this.path + this.delimiter + name:name); + this.client._send("CREATE "+this.client._escapeString(path), (function(status){ + if(status == "OK"){ + this.client._send("SUBSCRIBE "+this.client._escapeString(path), (function(status){ + if(typeof callback == "function"){ + callback(null, new this.client.Mailbox({ + client: this.client, + path: path, + name: name, + delimiter: this.delimiter, + tags: [] + })); + } + }).bind(this)); + }else{ + callback(new Error("Creating mailbox failed")); + } + }).bind(this)); +} \ No newline at end of file diff --git a/tools/clientplayground.js b/tools/clientplayground.js index d21c4ab..55001d2 100644 --- a/tools/clientplayground.js +++ b/tools/clientplayground.js @@ -38,7 +38,7 @@ client.on("connect", function(){ function walkMailboxes(name, level, node){ level = level || 0; - (node.listChildren || node.listRoot).call(node, function(err, list){ + (node.listChildren || node.listMailboxes).call(node, function(err, list){ if(err){return;} console.log("> "+name); for(var i=0; i Date: Fri, 4 May 2012 00:12:32 +0300 Subject: [PATCH 008/106] jshinted --- lib/client.js | 26 ++++++++++++-------------- lib/mailbox.js | 17 +++++++++-------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/lib/client.js b/lib/client.js index 76b71b2..c48d5d4 100644 --- a/lib/client.js +++ b/lib/client.js @@ -51,7 +51,6 @@ function createXOAuthGenerator(options){ return new xoauth.XOAuthGenerator(options); } - /** * Creates an IMAP connection object for communicating with the server * @@ -796,7 +795,7 @@ IMAPClient.prototype._handlerTaggedCondstore = function(status){ }else{ this._postReady(); } -} +}; /** * Handle mailbox listing with LSUB @@ -859,7 +858,7 @@ IMAPClient.prototype._handlerTaggedLsub = function(xinfo, callback, status){ if(typeof callback == "function"){ callback(null, this._mailboxList); } -} +}; /** * Handle SELECT and EXAMINE commands. If succeeded, move to SELECTED state. @@ -960,7 +959,7 @@ IMAPClient.prototype._handlerUntaggedList = function(list){ delimiter: delimiter, path: path, tags: tags - } + }; this._mailboxList.push(mailbox); }; @@ -1078,7 +1077,7 @@ IMAPClient.prototype._postCapability = function(){ */ IMAPClient.prototype._postAuth = function(){ if(this._capabilities.indexOf("CONDSTORE")>=0){ - this._send("ENABLE CONDSTORE", this._handlerTaggedCondstore.bind(this)) + this._send("ENABLE CONDSTORE", this._handlerTaggedCondstore.bind(this)); }else if(this._capabilities.indexOf("ID")>=0){ this._send("ID (" + "\"name\" "+this._escapeString(X_CLIENT_NAME) + @@ -1215,7 +1214,7 @@ IMAPClient.prototype._convertFromUTF7 = function(str){ }catch(E){ return str; } -} +}; /** * Check for new mail, since the last known UID @@ -1242,7 +1241,7 @@ IMAPClient.prototype._checkNewMail = function(){ */ IMAPClient.prototype.listMailboxes = function(callback){ this._rootMailbox.listChildren(callback); -} +}; /** * Opens a selected mailbox. This is needed before you can open any message. @@ -1390,9 +1389,8 @@ IMAPClient.prototype.updateFlags = function(uid, flags, updateType, callback){ return; } - this._send("UID STORE "+uid+":"+uid+" "+updateType+"FLAGS ("+ - flags.join(" ") - +")", (function(status){ + this._send("UID STORE "+uid+":"+uid+" "+updateType+"FLAGS (" + flags.join(" ") + ")", + (function(status){ this._collectMailList = false; if(typeof callback != "function"){ @@ -1416,7 +1414,7 @@ IMAPClient.prototype.updateFlags = function(uid, flags, updateType, callback){ this._collectMailList = true; this._mailList = []; }).bind(this)); -} +}; /** * Add flags for selected message @@ -1430,7 +1428,7 @@ IMAPClient.prototype.addFlags = function(uid, flags, callback){ flags = [flags]; } this.updateFlags(uid, flags, "+", callback); -} +}; /** * Removes flags for selected message @@ -1444,7 +1442,7 @@ IMAPClient.prototype.removeFlags = function(uid, flags, callback){ flags = [flags]; } this.updateFlags(uid, flags, "-", callback); -} +}; /** * Fetches envelope object for selected message @@ -1553,7 +1551,7 @@ IMAPClient.prototype.getMailbox = function(path, callback){ callback(null, null); } }); -} +}; /** * Enter IDLE mode diff --git a/lib/mailbox.js b/lib/mailbox.js index 045fbf3..02a45da 100644 --- a/lib/mailbox.js +++ b/lib/mailbox.js @@ -40,8 +40,8 @@ function Mailbox(options){ * @param {Function} callback Callback function to run when the mailbox is opened */ Mailbox.prototype.open = function(options, callback){ - this.client.openMailbox(this.path, options, callback) -} + this.client.openMailbox(this.path, options, callback); +}; /** * Detects the type by the name of the mailbox @@ -65,7 +65,7 @@ Mailbox.prototype.detectType = function(){ } return "Normal"; -} +}; /** * Lists children for the mailbox @@ -78,8 +78,9 @@ Mailbox.prototype.listChildren = function(path, callback){ path = undefined; } - var path = this.client._escapeString(path || (this.path ? this.path + this.delimiter + "%":"%")), - command = "LIST", suffix = ""; + var command = "LIST", suffix = ""; + + path = this.client._escapeString(path || (this.path ? this.path + this.delimiter + "%":"%")); if(this.client._capabilities.indexOf("SPECIAL-USE")>=0){ command = "LIST"; @@ -96,7 +97,7 @@ Mailbox.prototype.listChildren = function(path, callback){ this.client._mailboxList = []; }).bind(this)); -} +}; /** * Fetches subscribed mailboxes @@ -118,7 +119,7 @@ Mailbox.prototype.listSubscribed = function(path, xinfo, callback){ (function(){ this.client._mailboxList = []; }).bind(this)); -} +}; /** * Creates a new mailbox and subscribes to it @@ -145,4 +146,4 @@ Mailbox.prototype.createChild = function(name, callback){ callback(new Error("Creating mailbox failed")); } }).bind(this)); -} \ No newline at end of file +}; \ No newline at end of file From 32b7624a78cee0dd6c35e539bd8eb43c912ca4cd Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Fri, 4 May 2012 00:23:05 +0300 Subject: [PATCH 009/106] renamed name to path --- README.md | 4 ++-- lib/client.js | 14 +++++++------- lib/mailbox.js | 1 + 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 363b089..06119e0 100644 --- a/README.md +++ b/README.md @@ -132,11 +132,11 @@ Example: Before you can check mailbox contents, you need to select one with - client.openMailbox(name[, options], callback) + client.openMailbox(path[, options], callback) Where - * **name** is the name of the mailbox (ie. "INBOX") or a mailbox object + * **path** is the path to the mailbox (ie. "INBOX" or "INBOX/Arhiiv") or a mailbox object * **options** is an optional options object * **options.readOnly** - if set to true, open the mailbox in read-only mode (downloading messages does not update seen/unseen flag) * **callback** *(error, info)* is a callback function to run after the mailbox has been opened. Has an error param in case the opening failed and a info param with the properties of the opened mailbox. diff --git a/lib/client.js b/lib/client.js index c48d5d4..1a94aa2 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1246,12 +1246,12 @@ IMAPClient.prototype.listMailboxes = function(callback){ /** * Opens a selected mailbox. This is needed before you can open any message. * - * @param {String} mailboxName Mailbox name with full path, ie "INBOX/Sent Items" + * @param {String} path Mailbox full path, ie "INBOX/Sent Items" * @param {Object} [options] Optional options object * @param {Boolean} [options.readOnly] If set to true, open the mailbox in read-only mode (seen/unseen flags won't be touched) * @param {Function} callback Callback function to run when the mailbox is opened */ -IMAPClient.prototype.openMailbox = function(mailboxName, options, callback){ +IMAPClient.prototype.openMailbox = function(path, options, callback){ var command = "SELECT"; if(typeof options == "function" && !callback){ @@ -1265,17 +1265,17 @@ IMAPClient.prototype.openMailbox = function(mailboxName, options, callback){ command = "EXAMINE"; } - if(typeof mailboxName == "object"){ - mailboxName = mailboxName.path; + if(typeof path == "object"){ + path = path.path; } - mailboxName = mailboxName || this._inboxName || "INBOX"; + path = path || this._inboxName || "INBOX"; this._selectedMailbox = { - name: mailboxName + path: path }; - this._send(command + " " + this._escapeString(mailboxName)+( + this._send(command + " " + this._escapeString(path)+( this._condstoreEnabled?" (CONDSTORE)":"" ), this._handlerTaggedSelect.bind(this, callback)); }; diff --git a/lib/mailbox.js b/lib/mailbox.js index 02a45da..54c6869 100644 --- a/lib/mailbox.js +++ b/lib/mailbox.js @@ -70,6 +70,7 @@ Mailbox.prototype.detectType = function(){ /** * Lists children for the mailbox * + * @param {String} [path] If set, list only selected path info but not the children * @param {Function} callback Callback function to run with the mailbox list */ Mailbox.prototype.listChildren = function(path, callback){ From 7cc94ff5ed97af25d539ea8a210ce76c86736b9d Mon Sep 17 00:00:00 2001 From: andris Reinman Date: Tue, 3 Jul 2012 16:08:57 +0300 Subject: [PATCH 010/106] Added 'path' property to message envelopes --- lib/client.js | 3 ++- package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/client.js b/lib/client.js index 1a94aa2..6ccfde9 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1138,7 +1138,8 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ } var message = { - UIDValidity: this._selectedMailbox.UIDValidity + UIDValidity: this._selectedMailbox.UIDValidity, + path: this._selectedMailbox.path }; if(dataObject.UID){ diff --git a/package.json b/package.json index 2cf11b8..c17501b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.5", + "version": "0.1.6", "author" : "Andris Reinman", "maintainers":[ { From a76a988765ef80fd946056219721a6a5f7f983c2 Mon Sep 17 00:00:00 2001 From: andris Reinman Date: Wed, 4 Jul 2012 10:03:39 +0300 Subject: [PATCH 011/106] fixed connection close issue --- lib/client.js | 17 ++++++++++++++++- package.json | 2 +- tools/proxy.js | 43 +++++++++++++++++++++++++++---------------- 3 files changed, 44 insertions(+), 18 deletions(-) diff --git a/lib/client.js b/lib/client.js index 6ccfde9..d19aa43 100644 --- a/lib/client.js +++ b/lib/client.js @@ -458,6 +458,8 @@ IMAPClient.prototype._onClose = function(){ if(this.debug){ console.log("EVENT: CLOSE"); } + + this.close(); }; /** @@ -470,6 +472,8 @@ IMAPClient.prototype._onEnd = function(){ if(this.debug){ console.log("EVENT: END"); } + + this.close(); }; /** @@ -645,7 +649,7 @@ IMAPClient.prototype._send = function(data, callback, prewrite){ */ IMAPClient.prototype._processCommandQueue = function(){ - if(!this._commandQueue.length){ + if(!this._commandQueue.length || !this._connection){ return; } @@ -1573,8 +1577,19 @@ IMAPClient.prototype.idle = function(){ * // FIXME - should LOGOUT first! */ IMAPClient.prototype.close = function(){ + if(!this._connection){ + return; + } + var socket = this._connection.socket || this._connection; + if(socket && !socket.destroyed){ socket.destroy(); } + + this._connection = false; + this._commandQueue = []; + clearTimeout(this._shouldIdleTimer); + clearTimeout(this._idleTimer); + this.emit("close"); }; diff --git a/package.json b/package.json index c17501b..1aef9fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.6", + "version": "0.1.7", "author" : "Andris Reinman", "maintainers":[ { diff --git a/tools/proxy.js b/tools/proxy.js index a24fbba..1f72879 100644 --- a/tools/proxy.js +++ b/tools/proxy.js @@ -40,22 +40,33 @@ var sessionCounter = 0, socket.end(); } }); - - var target = (targetSecure?tls:net).connect(targetPort, targetHost, function() { - - console.log("Server connected"); - - socket = targetSecure ? target.socket : target; - - socket.setKeepAlive(true); - - if(client && !client.destroyed){ - client.pipe(target); - }else{ - socket.end(); - } - - }); + console.log(targetPort, targetHost) + var params = [targetPort, targetHost, function() { + + console.log("Server connected"); + + socket = targetSecure ? target.socket : target; + + socket.setKeepAlive(true); + + if(client && !client.destroyed){ + client.pipe(target); + }else{ + socket.end(); + } + + }]; + + if(targetSecure){ + params.shift(); + params.shift(); + params.unshift({ + port: targetPort, + host: targetHost + }); + } + + var target = (targetSecure?tls:net).connect.apply((targetSecure?tls:net), params); target.on('data', function(chunk) { var str = (chunk || "").toString("utf-8").trim(); From 1327651f36835558b8bb26875e04c29b4a4daca2 Mon Sep 17 00:00:00 2001 From: andris Reinman Date: Wed, 4 Jul 2012 12:19:35 +0300 Subject: [PATCH 012/106] Added errorType param for specific errors like Auth etc. --- lib/client.js | 30 ++++++++++++++++++++++-------- package.json | 2 +- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/lib/client.js b/lib/client.js index d19aa43..ab38f4b 100644 --- a/lib/client.js +++ b/lib/client.js @@ -119,7 +119,7 @@ IMAPClient.prototype.modes = { /** * Delay for breaking IDLE loop and running NOOP */ -IMAPClient.prototype.IDLE_TIMEOUT = 15 * 1000; +IMAPClient.prototype.IDLE_TIMEOUT = 30 * 1000; /** * Delay for entering IDLE mode after any command @@ -513,7 +513,11 @@ IMAPClient.prototype._handlerGreeting = function(data){ } if(data[0] != "*" && data[1] != "OK"){ - return this.emit("error", "Bad greeting from the server"); + var error = new Error("Bad greeting from the server"); + error.errorType = "ProtocolError"; + this.emit("error", error); + this.close(); + return; } this._currentState = this.states.PREAUTH; @@ -527,7 +531,9 @@ IMAPClient.prototype._handlerGreeting = function(data){ * emit an error and close the socket */ IMAPClient.prototype._handleGreetingTimeout = function(){ - this.emit("error", "Timeout waiting for a greeting"); + var error = new Error("Timeout waiting for a greeting"); + error.errorType = "TimeoutError"; + this.emit("error", error); this.close(); }; @@ -717,7 +723,9 @@ IMAPClient.prototype._handlerTaggedCapability = function(status){ this._postCapability(); }else{ - this.emit("error", new Error("Invalid capability response")); + var error = new Error("Invalid capability response"); + error.errorType = "ProtocolError"; + this.emit("error", error); this.close(); } }; @@ -747,7 +755,9 @@ IMAPClient.prototype._handlerTaggedStartTLS = function(status){ this._send("CAPABILITY", this._handlerTaggedCapability.bind(this)); }).bind(this)); }else{ - this.emit("error", new Error("Invalid starttls response")); + var error = new Error("Invalid starttls response"); + error.errorType = "TLSError"; + this.emit("error", error); this.close(); } }; @@ -766,7 +776,9 @@ IMAPClient.prototype._handlerTaggedLogin = function(status){ this._postAuth(); } }else{ - this.emit("error", new Error("Authentication failed")); + var error = new Error("Authentication failed"); + error.errorType = "AuthenticationError"; + this.emit("error", error); this.close(); } }; @@ -895,10 +907,12 @@ IMAPClient.prototype._handlerTaggedSelect = function(callback, status, params){ this.emit("mailbox", this._selectedMailbox); } }else{ + var error = new Error("Mailbox select failed"); + error.errorType = "MailboxError"; if(typeof callback == "function"){ - callback(null, new Error("Mailbox select failed")); + callback(null, error); }else{ - this.emit("error", new Error("Mailbox select failed")); + this.emit("error", error); } } }; diff --git a/package.json b/package.json index 1aef9fa..76f3a5b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.7", + "version": "0.1.8", "author" : "Andris Reinman", "maintainers":[ { From ec8009865e0786e2c58b0d3c0856a0da2177b542 Mon Sep 17 00:00:00 2001 From: andris Reinman Date: Thu, 5 Jul 2012 11:29:10 +0300 Subject: [PATCH 013/106] Added IMAP log for some errors --- .gitignore | 3 ++- lib/client.js | 30 ++++++++++++++++++++++++++++++ lib/lineparser.js | 1 + package.json | 2 +- test/lineparser.js | 16 ++++++++++++++++ 5 files changed, 50 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index b512c09..fd4f2b0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -node_modules \ No newline at end of file +node_modules +.DS_Store diff --git a/lib/client.js b/lib/client.js index ab38f4b..53fda47 100644 --- a/lib/client.js +++ b/lib/client.js @@ -301,6 +301,16 @@ IMAPClient.prototype._init = function(){ */ this._ignoreData = false; + /** + * Keep IMAP log for error trace + */ + this._log = []; + + /** + * IMAP log length in lines + */ + this._logLength = 10; + /** * Root mailbox */ @@ -317,6 +327,7 @@ IMAPClient.prototype._init = function(){ this._currentHandler = this._handlerGreeting; this.lineparser.on("line", this._onServerResponse.bind(this)); + this.lineparser.on("log", this._onServerLog.bind(this, "S")); }; /** @@ -498,6 +509,18 @@ IMAPClient.prototype._onServerResponse = function(data){ this._currentHandler(data); }; +/* + * Log IMAP commands into ._log array + * + * @param {String} data IMAP command line + */ +IMAPClient.prototype._onServerLog = function(type, data){ + this._log.unshift((type?type + ": " :"") + (data || "").toString().trim()); + if(this._log.length > this._logLength){ + this._log.pop(); + } +} + /** * Run as the handler for the initial command coming from the server. If it * is a greeting with status OK, enter PREAUTH state and run CAPABILITY @@ -515,6 +538,7 @@ IMAPClient.prototype._handlerGreeting = function(data){ if(data[0] != "*" && data[1] != "OK"){ var error = new Error("Bad greeting from the server"); error.errorType = "ProtocolError"; + error.errorLog = this._log.slice(0, this._logLength); this.emit("error", error); this.close(); return; @@ -667,6 +691,7 @@ IMAPClient.prototype._processCommandQueue = function(){ if(this.debug){ console.log("CLIENT: DONE"); } + this._onServerLog("C", "DONE"); this._connection.write("DONE\r\n"); this._idleEnd = true; } @@ -680,6 +705,7 @@ IMAPClient.prototype._processCommandQueue = function(){ command.prewrite(); } + this._onServerLog("C", command.data); this._connection.write(command.data); if(this.debug){ @@ -725,6 +751,7 @@ IMAPClient.prototype._handlerTaggedCapability = function(status){ }else{ var error = new Error("Invalid capability response"); error.errorType = "ProtocolError"; + error.errorLog = this._log.slice(0, this._logLength); this.emit("error", error); this.close(); } @@ -757,6 +784,7 @@ IMAPClient.prototype._handlerTaggedStartTLS = function(status){ }else{ var error = new Error("Invalid starttls response"); error.errorType = "TLSError"; + error.errorLog = this._log.slice(0, this._logLength); this.emit("error", error); this.close(); } @@ -778,6 +806,7 @@ IMAPClient.prototype._handlerTaggedLogin = function(status){ }else{ var error = new Error("Authentication failed"); error.errorType = "AuthenticationError"; + error.errorLog = this._log.slice(0, this._logLength); this.emit("error", error); this.close(); } @@ -909,6 +938,7 @@ IMAPClient.prototype._handlerTaggedSelect = function(callback, status, params){ }else{ var error = new Error("Mailbox select failed"); error.errorType = "MailboxError"; + error.errorLog = this._log.slice(0, this._logLength); if(typeof callback == "function"){ callback(null, error); }else{ diff --git a/lib/lineparser.js b/lib/lineparser.js index b8141a6..e4f7e24 100644 --- a/lib/lineparser.js +++ b/lib/lineparser.js @@ -99,6 +99,7 @@ IMAPLineParser.prototype.end = function(chunk){ } } + process.nextTick(this.emit.bind(this, "log", this._currentLine)); process.nextTick(this.emit.bind(this, "line", this.finalize())); this._init(); }; diff --git a/package.json b/package.json index 76f3a5b..92dc0a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.8", + "version": "0.1.9", "author" : "Andris Reinman", "maintainers":[ { diff --git a/test/lineparser.js b/test/lineparser.js index 87a5da6..959fd8b 100644 --- a/test/lineparser.js +++ b/test/lineparser.js @@ -202,4 +202,20 @@ exports["Structure tests"] = { lp.end("TAG1 [ALERT] BODY[TEXT HEADER]"); } +} + +exports["Logging tests"] = { + "Simple log": function(test){ + var lp = new IMAPLineParser(); + + test.expect(1); + + lp.on("log", function(data){ + test.equal(data, "TAG1 FETCH (NAME HEADER BODY)"); + test.done(); + }); + + lp.write("TAG1 ") + lp.end("FETCH (NAME HEADER BODY)"); + } } \ No newline at end of file From bb458de75fb0e50a7ee919edddbb87b20292878b Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 16 Jul 2012 11:09:44 +0300 Subject: [PATCH 014/106] update --- lib/client.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/client.js b/lib/client.js index 53fda47..9b42df9 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1635,5 +1635,6 @@ IMAPClient.prototype.close = function(){ this._commandQueue = []; clearTimeout(this._shouldIdleTimer); clearTimeout(this._idleTimer); + clearTimeout(this._greetingTimeout); this.emit("close"); }; From 6f40051fd16a4a144e03bf0f52cd8f003deeea2f Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 16 Jul 2012 11:10:14 +0300 Subject: [PATCH 015/106] bumped version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 92dc0a4..e13cd9b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.9", + "version": "0.1.10", "author" : "Andris Reinman", "maintainers":[ { @@ -23,4 +23,4 @@ "scripts":{ "test": "nodeunit test" } -} \ No newline at end of file +} From 49729bb6a72d0b11c205d9069d543e2c31258b76 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 16 Jul 2012 15:47:35 +0300 Subject: [PATCH 016/106] If XLIST is supported, skip name quessing --- lib/mailbox.js | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/mailbox.js b/lib/mailbox.js index 54c6869..689b1cc 100644 --- a/lib/mailbox.js +++ b/lib/mailbox.js @@ -25,10 +25,10 @@ function Mailbox(options){ value: options.tags || [], enumerable: false }); - + this.name = options.name || ""; this.path = options.path || this.name; - this.type = options.type || this.detectType(); + this.type = options.type || (this.client._capabilities.indexOf("XLIST")<0 && this.detectType() || "Normal"); this.delimiter = options.delimiter || this.client._mailboxDelimiter || ""; } diff --git a/package.json b/package.json index e13cd9b..bec1072 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.10", + "version": "0.1.11", "author" : "Andris Reinman", "maintainers":[ { From 16f1de943cf41bfef8360d85390f5985e97aab9c Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 16 Jul 2012 21:30:27 +0300 Subject: [PATCH 017/106] Added storeMessage for uploading messages to current mailbox --- README.md | 21 +++++++++++ examples/append.js | 37 ++++++++++++++++++++ lib/client.js | 86 ++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 133 insertions(+), 11 deletions(-) create mode 100644 examples/append.js diff --git a/README.md b/README.md index 06119e0..208b1d4 100644 --- a/README.md +++ b/README.md @@ -291,6 +291,27 @@ Example console.log("Current flags for a message: ", flags); }); +### Upload a message + +You can upload a message to current mailbox with `client.storeMessage()` + + client.storeMessage(message[, flags], callback) + +Where + + * **message** is the message to be uploaded either as a string or a Buffer. + * **flags** is an array of flags to set to the message (ie. `["\\Seen"]`) + * **callback** is the callback function, gets message UID and UID and UIDValitity as a param + +Example + + client.storeMessage("From: ....", ["\\Seen"], function(err, params){ + console.log(err || params.UIDValidity +", "+ params.UID); + }); + +When adding a message to the mailbox, also new message event is raised, after +the mail has been stored. + ### Wait for new messages You can listen for new incoming e-mails with event "new" diff --git a/examples/append.js b/examples/append.js new file mode 100644 index 0000000..6980b30 --- /dev/null +++ b/examples/append.js @@ -0,0 +1,37 @@ +var inbox = require(".."), + util = require("util"); + +var client = inbox.createConnection(false, "imap.gmail.com", { + secureConnection: true, + auth:{ + user: "test.nodemailer@gmail.com", + pass: "Nodemailer123" + }, + debug: false +}); + +client.connect(); + +client.on("connect", function(){ + + client.openMailbox("[Gmail]/Sent Mail", function(error, mailbox){ + if(error) throw error; + + client.storeMessage("From: andris@node.ee\r\n"+ + "To: andris@kreata.ee\r\n"+ + "Message-Id: 1234\r\n"+ + "Subject: test 2\r\n"+ + "\r\n"+ + "Tere tere 2!", ["\\Seen"], console.log); + + }); + + // on new messages, print to console + client.on("new", function(message){ + console.log("New message:"); + console.log(util.inspect(message, false, 7)); + + client.createMessageStream(message.UID).pipe(process.stdout, {end: false}); + + }); +}); diff --git a/lib/client.js b/lib/client.js index 9b42df9..3d30a58 100644 --- a/lib/client.js +++ b/lib/client.js @@ -226,6 +226,11 @@ IMAPClient.prototype._init = function(){ */ this._shouldIdleTimer = true; + /** + * If true check mail before entering idle + */ + this._shouldCheckOnIdle = false; + /** * Timeout to wait for a successful greeting from the server */ @@ -266,6 +271,11 @@ IMAPClient.prototype._init = function(){ */ this._messageStream = false; + /** + * Literal handler + */ + this._literalHandler = false; + /** * Personal mailbox root */ @@ -582,6 +592,12 @@ IMAPClient.prototype._responseRouter = function(data){ if(this._idleWait){ this._handlerUntaggedIdle(); } + + if(this._literalHandler){ + this._literalHandler(); + } + }else if(this._literalHandler){ + this._literalHandler = null; } // handle untagged commands (tagged with *) @@ -640,12 +656,14 @@ IMAPClient.prototype._responseRouter = function(data){ this._selectedMailbox.count--; } } - + if(!isNaN(data[1]) && data[2] == "EXISTS"){ if(this._selectedMailbox.count != Number(data[1])){ this._selectedMailbox.count = Number(data[1]) || this._selectedMailbox.count || 0; if(this.idling){ this._checkNewMail(); + }else{ + this._shouldCheckOnIdle = true; } } return; @@ -1047,6 +1065,11 @@ IMAPClient.prototype._handlerUntaggedLsub = function(list){ IMAPClient.prototype._handlerUntaggedIdle = function(){ this._idleWait = false; this.idling = true; + if(this._shouldCheckOnIdle){ + this._shouldCheckOnIdle = false; + this._checkNewMail(); + } + this._processCommandQueue(); }; @@ -1408,7 +1431,7 @@ IMAPClient.prototype.listMessages = function(from, limit, callback){ */ IMAPClient.prototype.updateFlags = function(uid, flags, updateType, callback){ uid = Number(uid) || 0; - flags = flags || []; + flags = [].concat(flags || []); if(!callback && typeof updateType == "function"){ callback = updateType; @@ -1473,9 +1496,7 @@ IMAPClient.prototype.updateFlags = function(uid, flags, updateType, callback){ * @param {Function} callback Callback function to run, returns an array of flags */ IMAPClient.prototype.addFlags = function(uid, flags, callback){ - if(typeof flags == "string"){ - flags = [flags]; - } + flags = [].concat(flags || []); this.updateFlags(uid, flags, "+", callback); }; @@ -1487,9 +1508,7 @@ IMAPClient.prototype.addFlags = function(uid, flags, callback){ * @param {Function} callback Callback function to run, returns an array of flags */ IMAPClient.prototype.removeFlags = function(uid, flags, callback){ - if(typeof flags == "string"){ - flags = [flags]; - } + flags = [].concat(flags || []); this.updateFlags(uid, flags, "-", callback); }; @@ -1565,9 +1584,7 @@ IMAPClient.prototype.createMessageStream = function(uid){ this._send("UID FETCH "+uid+":"+uid+" BODY[]", (function(status){ this._collectMailList = false; this._literalStreaming = false; - - - + if(!this._mailList.length){ if(status == "OK"){ stream.emit("error", new Error("Selected message not found")); @@ -1589,6 +1606,53 @@ IMAPClient.prototype.createMessageStream = function(uid){ return stream; }; +/** + * Upload a message to the mailbox + * + * This totally sucks but as the length of the message need to be known + * beforehand, it is probably a good idea to include it in whole - easier + * to implement and gives as total byte count + */ +IMAPClient.prototype.storeMessage = function(message, flags, callback){ + if(typeof flags == "function" && !callback){ + callback = flags; + flags = undefined; + } + + message = message || ""; + if(typeof message == "string"){ + message = new Buffer(message, "utf-8"); + } + + flags = [].concat(flags || []); + this._send("APPEND " + this._escapeString(this._selectedMailbox.path) + (flags.length ? " (" + flags.join(" ")+")":"") + " {" + message.length+"}", (function(status, data){ + this._literalHandler = null; + if(status == "OK"){ + + // supports APPENDUID + if(data && data[0] && data[0].params && data[0].params[0] == "APPENDUID"){ + return callback(null, { + UIDValidity: data[0].params[1] || "", + UID: data[0].params[2] || "" + }); + } + + // Guess the values from mailbox data. Not sure if it really works :S + return callback(null, { + UIDValidity: this._selectedMailbox.UIDValidity, + UID: this._selectedMailbox.UIDNext + }); + }else{ + return callback(new Error("Error saving message to mailbox")); + } + }).bind(this), (function(){ + this._literalHandler = (function(){ + this._connection.write(message); + this._connection.write("\r\n"); + }).bind(this); + }).bind(this)); +} + IMAPClient.prototype.getMailbox = function(path, callback){ this._rootMailbox.listChildren(path, function(error, mailboxes){ From d8323f103741cea8ee38b5a5878df442fd595484 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 16 Jul 2012 21:31:02 +0300 Subject: [PATCH 018/106] bumped version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bec1072..5bf4fcd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.11", + "version": "0.1.12", "author" : "Andris Reinman", "maintainers":[ { From 07a3269eb0c2ab35561fd46ac5544a59f2cef783 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Tue, 17 Jul 2012 10:57:34 +0300 Subject: [PATCH 019/106] Added references and in-reply-to params to fetched message object --- README.md | 4 +++- lib/client.js | 35 ++++++++++++++++++++++++----------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 208b1d4..77c1303 100644 --- a/README.md +++ b/README.md @@ -211,7 +211,9 @@ Example output for a message listing } ], - messageId: '<04541AB5-9FBD-4255-81AA-18FE67CB97E5@gmail.com>' + messageId: '<04541AB5-9FBD-4255-81AA-18FE67CB97E5@gmail.com>', + inReplyTo: '<4FB16D5A.30808@gmail.com>', + references: ['<4FB16D5A.30808@gmail.com>','<1299323903.19454@foo.bar>'] }, ... ] diff --git a/lib/client.js b/lib/client.js index 3d30a58..f96cdb8 100644 --- a/lib/client.js +++ b/lib/client.js @@ -662,8 +662,6 @@ IMAPClient.prototype._responseRouter = function(data){ this._selectedMailbox.count = Number(data[1]) || this._selectedMailbox.count || 0; if(this.idling){ this._checkNewMail(); - }else{ - this._shouldCheckOnIdle = true; } } return; @@ -1069,7 +1067,6 @@ IMAPClient.prototype._handlerUntaggedIdle = function(){ this._shouldCheckOnIdle = false; this._checkNewMail(); } - this._processCommandQueue(); }; @@ -1099,7 +1096,7 @@ IMAPClient.prototype._handlerUntaggedFetch = function(list){ if(this._collectMailList){ this._mailList.push(envelopeData); } - + // emit as new message if(this._checkForNewMail){ this.emit("new", envelopeData); @@ -1197,11 +1194,11 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ return null; } - var dataObject = {}, lastKey = false; + var dataObject = {}, lastKey = false, headers; for(var i=0, len = envelopeData.length; i\s*]/g,"")+">"; + }); + })); + } + } + return message; }; /** @@ -1296,7 +1308,7 @@ IMAPClient.prototype._checkNewMail = function(){ return; } - this._send("UID FETCH "+this._selectedMailbox.UIDNext+":* (FLAGS ENVELOPE)", (function(){ + this._send("UID FETCH "+this._selectedMailbox.UIDNext+":* (FLAGS ENVELOPE BODY[HEADER.FIELDS (REFERENCES)])", (function(){ this._checkForNewMail = false; }).bind(this), (function(){ @@ -1406,7 +1418,7 @@ IMAPClient.prototype.listMessages = function(from, limit, callback){ this._collectMailList = true; this._mailList = []; - this._send("FETCH "+from+":"+to+" (UID FLAGS ENVELOPE)", (function(status){ + this._send("FETCH "+from+":"+to+" (UID FLAGS ENVELOPE BODY[HEADER.FIELDS (REFERENCES)])", (function(status){ this._collectMailList = false; if(typeof callback != "function"){ @@ -1535,7 +1547,7 @@ IMAPClient.prototype.fetchData = function(uid, callback){ return; } - this._send("UID FETCH "+uid+":"+uid+" (FLAGS ENVELOPE)", (function(status){ + this._send("UID FETCH "+uid+":"+uid+" (FLAGS ENVELOPE BODY[HEADER.FIELDS (REFERENCES)])", (function(status){ this._collectMailList = false; if(typeof callback != "function"){ @@ -1628,6 +1640,7 @@ IMAPClient.prototype.storeMessage = function(message, flags, callback){ this._send("APPEND " + this._escapeString(this._selectedMailbox.path) + (flags.length ? " (" + flags.join(" ")+")":"") + " {" + message.length+"}", (function(status, data){ this._literalHandler = null; if(status == "OK"){ + this._shouldCheckOnIdle = true; // supports APPENDUID if(data && data[0] && data[0].params && data[0].params[0] == "APPENDUID"){ From 71974787425b5ef7dee1769f098450ffda4c5216 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Tue, 17 Jul 2012 10:57:49 +0300 Subject: [PATCH 020/106] bumped version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5bf4fcd..bec1072 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.12", + "version": "0.1.11", "author" : "Andris Reinman", "maintainers":[ { From ed2fd079d8d4bb49ac50aae90370896f03a69cd0 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Tue, 17 Jul 2012 17:00:27 +0300 Subject: [PATCH 021/106] Added possibility to set client "ID" values --- README.md | 2 ++ examples/id.js | 27 +++++++++++++++++++++++++++ lib/client.js | 36 ++++++++++++++++++++++++------------ package.json | 2 +- 4 files changed, 54 insertions(+), 13 deletions(-) create mode 100644 examples/id.js diff --git a/README.md b/README.md index 77c1303..e775d31 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,8 @@ where * **options.auth.user** is the IMAP username * **options.auth.pass** is the IMAP password * **options.auth.XOAuthToken** (optional) is either a String or *inbox.createXOAuthGenerator* object + * **options.clientId** is optional client ID params object + * **options.clientId.name** is is the name param etc. see [rfc 2971](http://tools.ietf.org/html/rfc2971#section-3.3) for possible field names Example: diff --git a/examples/id.js b/examples/id.js new file mode 100644 index 0000000..8503cbd --- /dev/null +++ b/examples/id.js @@ -0,0 +1,27 @@ +var inbox = require(".."), + util = require("util"); + +var client = inbox.createConnection(false, "imap.gmail.com", { + secureConnection: true, + auth:{ + user: "test.nodemailer@gmail.com", + pass: "Nodemailer123" + }, + clientId: { + name: "test", + "support-url": "test2" + }, + debug: false +}); + +client.connect(); + +client.on("connect", function(){ + + client.openMailbox("INBOX", function(error, mailbox){ + if(error) throw error; + + + }); + +}); diff --git a/lib/client.js b/lib/client.js index f96cdb8..41486e7 100644 --- a/lib/client.js +++ b/lib/client.js @@ -847,12 +847,30 @@ IMAPClient.prototype._handlerTaggedId = function(status){ * @param {String} status If "OK" then the command succeeded */ IMAPClient.prototype._handlerTaggedCondstore = function(status){ + var clientData = { + name: X_CLIENT_NAME + }; + + if(packageData.version){ + clientData.version = packageData.version; + } + + if(X_CLIENT_URL){ + clientData["support-url"] = X_CLIENT_URL; + } + + if(this.options.clientId){ + Object.keys(this.options.clientId).forEach((function(key){ + clientData[key] = this.options.clientId[key]; + }).bind(this)); + } + + clientData = Object.keys(clientData).map((function(key){ + return this._escapeString(key) + " " + this._escapeString(clientData[key]); + }).bind(this)).join(" "); + if(this._capabilities.indexOf("ID")>=0){ - this._send("ID (" + - "\"name\" "+this._escapeString(X_CLIENT_NAME) + - (packageData.version?" \"version\" "+this._escapeString(packageData.version):"") + - (X_CLIENT_URL?" \"support-url\" "+this._escapeString(X_CLIENT_URL):"") + - ")", this._handlerTaggedId.bind(this)); + this._send("ID (" + clientData + ")", this._handlerTaggedId.bind(this)); }else{ this._postReady(); } @@ -1146,14 +1164,8 @@ IMAPClient.prototype._postCapability = function(){ IMAPClient.prototype._postAuth = function(){ if(this._capabilities.indexOf("CONDSTORE")>=0){ this._send("ENABLE CONDSTORE", this._handlerTaggedCondstore.bind(this)); - }else if(this._capabilities.indexOf("ID")>=0){ - this._send("ID (" + - "\"name\" "+this._escapeString(X_CLIENT_NAME) + - (packageData.version?" \"version\" "+this._escapeString(packageData.version):"") + - (X_CLIENT_URL?" \"support-url\" "+this._escapeString(X_CLIENT_URL):"") + - ")", this._handlerTaggedId.bind(this)); }else{ - this._postReady(); + this._handlerTaggedCondstore("OK"); } }; diff --git a/package.json b/package.json index bec1072..5bf4fcd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.11", + "version": "0.1.12", "author" : "Andris Reinman", "maintainers":[ { From d80eb64ff5eebfd2e9ea2218c126ba39946711e7 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Tue, 17 Jul 2012 17:01:22 +0300 Subject: [PATCH 022/106] bv --- npm-debug.log | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 npm-debug.log diff --git a/npm-debug.log b/npm-debug.log new file mode 100644 index 0000000..57b12d1 --- /dev/null +++ b/npm-debug.log @@ -0,0 +1,108 @@ +0 info it worked if it ends with ok +1 verbose cli [ 'node', '/usr/local/bin/npm', 'publish' ] +2 info using npm@1.1.33 +3 info using node@v0.8.1 +4 verbose config file /Users/andris/.npmrc +5 verbose config file /usr/local/etc/npmrc +6 verbose config file /usr/local/lib/node_modules/npm/npmrc +7 verbose publish [ '.' ] +8 verbose read json /Users/andris/Projects/inbox/package.json +9 verbose cache add [ '.', null ] +10 silly cache add name=undefined spec="." args=[".",null] +11 verbose parsed url { pathname: '.', path: '.', href: '.' } +12 verbose lock . /Users/andris/.npm/3a52ce780950d4d969792a2559cd519d7ee8c727--.lock +13 verbose read json package.json +14 verbose tar pack [ '/var/folders/z1/mz6slcdn37z7tnt0v7m_l_780000gn/T/npm-66866/1342533658619-0.44802877004258335/tmp.tgz', +14 verbose tar pack '.' ] +15 verbose tarball /var/folders/z1/mz6slcdn37z7tnt0v7m_l_780000gn/T/npm-66866/1342533658619-0.44802877004258335/tmp.tgz +16 verbose folder . +17 info prepublish inbox@0.1.12 +18 verbose tar unpack /var/folders/z1/mz6slcdn37z7tnt0v7m_l_780000gn/T/npm-66866/1342533658619-0.44802877004258335/tmp.tgz +19 silly gunzTarPerm modes [ '755', '644' ] +20 silly gunzTarPerm extractEntry package.json +21 silly gunzTarPerm extractEntry .npmignore +22 silly gunzTarPerm extractEntry README.md +23 silly gunzTarPerm extractEntry .travis.yml +24 silly gunzTarPerm extractEntry examples/append.js +25 silly gunzTarPerm extractEntry examples/id.js +26 silly gunzTarPerm extractEntry examples/list.js +27 silly gunzTarPerm extractEntry examples/xoauth.js +28 silly gunzTarPerm extractEntry lib/client.js +29 silly gunzTarPerm extractEntry lib/lineparser.js +30 silly gunzTarPerm extractEntry lib/mailbox.js +31 silly gunzTarPerm extractEntry lib/starttls.js +32 silly gunzTarPerm extractEntry lib/xoauth.js +33 silly gunzTarPerm extractEntry lib/names.json +34 silly gunzTarPerm extractEntry test/lineparser.js +35 silly gunzTarPerm extractEntry tools/clientplayground.js +36 silly gunzTarPerm extractEntry tools/parserplayground.js +37 silly gunzTarPerm extractEntry tools/proxy.js +38 verbose read json /var/folders/z1/mz6slcdn37z7tnt0v7m_l_780000gn/T/npm-66866/1342533658619-0.44802877004258335/package/package.json +39 verbose from cache /var/folders/z1/mz6slcdn37z7tnt0v7m_l_780000gn/T/npm-66866/1342533658619-0.44802877004258335/package/package.json +40 verbose tar pack [ '/Users/andris/.npm/inbox/0.1.12/package.tgz', +40 verbose tar pack '/var/folders/z1/mz6slcdn37z7tnt0v7m_l_780000gn/T/npm-66866/1342533658619-0.44802877004258335/package' ] +41 verbose tarball /Users/andris/.npm/inbox/0.1.12/package.tgz +42 verbose folder /var/folders/z1/mz6slcdn37z7tnt0v7m_l_780000gn/T/npm-66866/1342533658619-0.44802877004258335/package +43 verbose tar unpack /Users/andris/.npm/inbox/0.1.12/package.tgz +44 silly gunzTarPerm modes [ '755', '644' ] +45 silly gunzTarPerm extractEntry package.json +46 silly gunzTarPerm extractEntry .npmignore +47 silly gunzTarPerm extractEntry README.md +48 silly gunzTarPerm extractEntry .travis.yml +49 silly gunzTarPerm extractEntry examples/append.js +50 silly gunzTarPerm extractEntry examples/id.js +51 silly gunzTarPerm extractEntry examples/list.js +52 silly gunzTarPerm extractEntry examples/xoauth.js +53 silly gunzTarPerm extractEntry lib/client.js +54 silly gunzTarPerm extractEntry lib/lineparser.js +55 silly gunzTarPerm extractEntry lib/mailbox.js +56 silly gunzTarPerm extractEntry lib/starttls.js +57 silly gunzTarPerm extractEntry lib/xoauth.js +58 silly gunzTarPerm extractEntry lib/names.json +59 silly gunzTarPerm extractEntry test/lineparser.js +60 silly gunzTarPerm extractEntry tools/clientplayground.js +61 silly gunzTarPerm extractEntry tools/parserplayground.js +62 silly gunzTarPerm extractEntry tools/proxy.js +63 verbose read json /Users/andris/.npm/inbox/0.1.12/package/package.json +64 silly shasum updated bytes 26303 +65 info shasum 48654d86a706c6c2d413b1b15a52c82766ce06d6 +65 info shasum /Users/andris/.npm/inbox/0.1.12/package.tgz +66 verbose from cache /Users/andris/.npm/inbox/0.1.12/package/package.json +67 verbose chmod /Users/andris/.npm/inbox/0.1.12/package.tgz 644 +68 verbose chown /Users/andris/.npm/inbox/0.1.12/package.tgz [ 501, 20 ] +69 silly publish { name: 'inbox', +69 silly publish version: '0.1.12', +69 silly publish author: { name: 'Andris Reinman' }, +69 silly publish maintainers: [ { name: 'andris', email: 'andris@node.ee' } ], +69 silly publish main: 'lib/client.js', +69 silly publish dependencies: { mimelib: '*', iconv: '*' }, +69 silly publish devDependencies: { nodeunit: '*' }, +69 silly publish scripts: { test: 'nodeunit test' }, +69 silly publish readme: '# inbox\n\nThis is a work in progress IMAP client for node.js. \n\nThe project consists of two major parts\n\n * IMAP command parser (token based, more or less complete)\n * IMAP control for accessing mailboxes (under construction)\n\n[![Build Status](https://secure.travis-ci.org/andris9/inbox.png)](http://travis-ci.org/andris9/inbox)\n\n## Installation\n\nInstall from npm\n\n npm install inbox\n\n## API\n\n**NB!** This API is preliminary and may change.\n\nUse **inbox** module\n\n var inbox = require("inbox");\n\n### Create new IMAP connection\n\nCreate connection object with \n\n inbox.createConnection(port, host, options)\n\nwhere\n\n * **port** is the port to the server (defaults to 143 on non-secure and to 993 on secure connection)\n * **host** is the hostname of the server\n * **options** is an options object for auth etc.\n * **options.secureConnection** is a Boolean value to indicate if the connection is initially secure or not\n * **options.auth** is an authentication object\n * **options.auth.user** is the IMAP username\n * **options.auth.pass** is the IMAP password\n * **options.auth.XOAuthToken** (optional) is either a String or *inbox.createXOAuthGenerator* object\n * **options.clientId** is optional client ID params object\n * **options.clientId.name** is is the name param etc. see [rfc 2971](http://tools.ietf.org/html/rfc2971#section-3.3) for possible field names\n\nExample:\n\n var client = inbox.createConnection(false, "imap.gmail.com", {\n secureConnection: true,\n auth:{\n user: "test.nodemailer@gmail.com",\n pass: "Nodemailer123"\n }\n });\n\nOr when login with XOAUTH (see examples/xoauth.js)\n \n var client = inbox.createConnection(false, "imap.gmail.com", {\n secureConnection: true,\n auth:{\n XOAuthToken: inbox.createXOAuthGenerator({\n user: "test.nodemailer@gmail.com",\n token: "1/Gr2OVA2Ol64fNyjZCns-bkRau5eLisbdlEa_HSuTaEk",\n tokenSecret: "ymFpseHtEnrIsuL8Ppbfnnk3"\n })\n }\n });\n \nOnce the connection object has been created, use connect() to create the actual connection.\n\n client.connect();\n \nWhen the connection has been successfully established a \'connect\' event is emitted.\n\n client.on("connect", function(){\n console.log("Successfully connected to server");\n });\n\n### List available mailboxes\n\nTo list the available mailboxes use \n\n client.listMailboxes(callback)\n \nWhere\n\n * **callback** *(error, mailboxes)* returns a list of root mailbox object\n \nMailbox objects have the following properties\n\n * **name** - the display name of the mailbox\n * **path** - the actual name of the mailbox, use it for opening the mailbox\n * **type** - the type of the mailbox (if server hints about it)\n * **hasChildren** - boolean indicator, if true, has child mailboxes\n * **disabled** - boolean indicator, if true, can not be selected\n\nAdditionally mailboxes have the following methods\n\n * **open** *([options, ]callback)* - open the mailbox (shorthand for *client.openMailbox*)\n * **listChildren** *(callback)* - if the mailbox has children (*hasChildren* is true), lists the child mailboxes\n\nExample:\n\n client.listMailboxes(function(error, mailboxes){\n for(var i=0, len = mailboxes.length; i\',\n inReplyTo: \'<4FB16D5A.30808@gmail.com>\',\n references: [\'<4FB16D5A.30808@gmail.com>\',\'<1299323903.19454@foo.bar>\']\n },\n ...\n ]\n \n**NB!** if some properties are not present in a message, it may be not included\nin the message object - for example, if there are no "cc:" addresses listed, \nthere is no "cc" field in the message object \n\n### Fetch message details\n\nTo fetch message data (flags, title, etc) for a specific message, use\n\n client.fetchData(uid, callback)\n \nWhere\n\n * **uid** is the UID value for the mail\n * **callback** *(error, message)* is the callback function to with the message data object (or null if the message was not found). Gets an error parameter if error occured\n\nExample\n\n client.fetchData(123, function(error, message){\n console.log(message.flags);\n });\n\n### Fetch message contents\n\nMessage listing only retrieves the envelope part of the message. To get the full RFC822 message body\nyou need to fetch the message.\n\n var messageStream = client.createMessageStream(uid)\n \nWhere\n\n * **uid** is the UID value for the mail\n\nExample (output message contents to console)\n\n client.createMessageStream(123).pipe(process.stdout, {end: false});\n\n**NB!** If the opened mailbox is not in read-only mode, the message will be \nautomatically marked as read (\\Seen flag is set) when the message is fetched.\n\n### Message flags\n\nYou can add and remove message flags like `\\Seen` or `\\Answered` with `client.addFlags()` and `client.removeFlags()`\n\n**Add flags**\n\n client.addFlags(uid, flags, callback)\n\nWhere\n\n * **uid** is the message identifier\n * **flags** is the array of flags to be added\n * **callback** *(error, flags)* is the callback to run, gets message flags array as a parameter \n\n**Remove flags**\n\n client.removeFlags(uid, flags, callback)\n\nWhere\n\n * **uid** is the message identifier\n * **flags** is the array of flags to be removed\n * **callback** *(error, flags)* is the callback to run, gets message flags array as a parameter\n\nExample\n\n // add \\Seen and \\Flagged flag to a message\n client.addFlags(123, ["\\\\Seen", "\\\\Flagged"], function(err, flags){\n console.log("Current flags for a message: ", flags);\n });\n \n // remove \\Flagged flag from a message\n client.removeFlags(123, ["\\\\Flagged"], function(err, flags){\n console.log("Current flags for a message: ", flags);\n });\n\n### Upload a message\n\nYou can upload a message to current mailbox with `client.storeMessage()`\n\n client.storeMessage(message[, flags], callback)\n\nWhere\n\n * **message** is the message to be uploaded either as a string or a Buffer.\n * **flags** is an array of flags to set to the message (ie. `["\\\\Seen"]`)\n * **callback** is the callback function, gets message UID and UID and UIDValitity as a param\n\nExample\n\n client.storeMessage("From: ....", ["\\\\Seen"], function(err, params){\n console.log(err || params.UIDValidity +", "+ params.UID);\n });\n\nWhen adding a message to the mailbox, also new message event is raised, after \nthe mail has been stored.\n\n### Wait for new messages\n\nYou can listen for new incoming e-mails with event "new"\n\n client.on("new", function(message){\n console.log("New incoming message " + message.title);\n });\n \n## Complete example\n\nListing newest 10 messages:\n\n var inbox = require("inbox");\n \n var client = inbox.createConnection(false, "imap.gmail.com", {\n secureConnection: true,\n auth:{\n user: "test.nodemailer@gmail.com",\n pass: "Nodemailer123"\n }\n });\n \n client.connect();\n \n client.on("connect", function(){\n client.openMailbox("INBOX", function(error, info){\n if(error) throw error;\n \n client.listMessages(-10, function(err, messages){\n messages.forEach(function(message){\n console.log(message.UID + ": " + message.title);\n });\n });\n\n });\n });', +69 silly publish _id: 'inbox@0.1.12', +69 silly publish description: 'This is a work in progress IMAP client for node.js.', +69 silly publish dist: { shasum: '48654d86a706c6c2d413b1b15a52c82766ce06d6' } } +70 verbose url raw inbox +71 verbose url resolving [ 'https://registry.npmjs.org/', './inbox' ] +72 verbose url resolved https://registry.npmjs.org/inbox +73 info retry registry request attempt 1 at 17:00:58 +74 http PUT https://registry.npmjs.org/inbox +75 http 409 https://registry.npmjs.org/inbox +76 verbose url raw inbox +77 verbose url resolving [ 'https://registry.npmjs.org/', './inbox' ] +78 verbose url resolved https://registry.npmjs.org/inbox +79 info retry registry request attempt 1 at 17:01:00 +80 http GET https://registry.npmjs.org/inbox +81 http 200 https://registry.npmjs.org/inbox +82 error publish fail Cannot publish over existing version. +82 error publish fail Bump the 'version' field, set the --force flag, or +82 error publish fail npm unpublish 'inbox@0.1.12' +82 error publish fail and try again +83 error System Darwin 11.4.0 +84 error command "node" "/usr/local/bin/npm" "publish" +85 error cwd /Users/andris/Projects/inbox +86 error node -v v0.8.1 +87 error npm -v 1.1.33 +88 error code EPUBLISHCONFLICT +89 error message publish fail +90 verbose exit [ 1, true ] diff --git a/package.json b/package.json index 5bf4fcd..e9c630f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.12", + "version": "0.1.13", "author" : "Andris Reinman", "maintainers":[ { From 725be37a40def20212d5a54863fc50d5b6cff303 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Fri, 20 Jul 2012 14:36:26 +0300 Subject: [PATCH 023/106] fixed new message bug --- lib/client.js | 12 ++++-- npm-debug.log | 108 -------------------------------------------------- package.json | 2 +- 3 files changed, 10 insertions(+), 112 deletions(-) delete mode 100644 npm-debug.log diff --git a/lib/client.js b/lib/client.js index 41486e7..3207c26 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1102,12 +1102,18 @@ IMAPClient.prototype._handlerUntaggedSearch = function(list){ * @param {Array} list Params about a message */ IMAPClient.prototype._handlerUntaggedFetch = function(list){ - var envelope = list[1] || [], + console.log("LISTLISTLOST", list) + var envelope = (list || [])[3] || [], + envelopeData = this._formatEnvelope(envelope), nextUID = Number(this._selectedMailbox.UIDNext) || 0, - currentUID = Number(envelope[1]) || 0, - envelopeData = this._formatEnvelope((list || [])[3]); + currentUID = Number(envelopeData.UID) || 0; +console.log(envelopeData); + +console.log(nextUID, currentUID) + if(!nextUID || nextUID <= currentUID){ + console.log("!!!!!!!!") this._selectedMailbox.UIDNext = currentUID+1; } diff --git a/npm-debug.log b/npm-debug.log deleted file mode 100644 index 57b12d1..0000000 --- a/npm-debug.log +++ /dev/null @@ -1,108 +0,0 @@ -0 info it worked if it ends with ok -1 verbose cli [ 'node', '/usr/local/bin/npm', 'publish' ] -2 info using npm@1.1.33 -3 info using node@v0.8.1 -4 verbose config file /Users/andris/.npmrc -5 verbose config file /usr/local/etc/npmrc -6 verbose config file /usr/local/lib/node_modules/npm/npmrc -7 verbose publish [ '.' ] -8 verbose read json /Users/andris/Projects/inbox/package.json -9 verbose cache add [ '.', null ] -10 silly cache add name=undefined spec="." args=[".",null] -11 verbose parsed url { pathname: '.', path: '.', href: '.' } -12 verbose lock . /Users/andris/.npm/3a52ce780950d4d969792a2559cd519d7ee8c727--.lock -13 verbose read json package.json -14 verbose tar pack [ '/var/folders/z1/mz6slcdn37z7tnt0v7m_l_780000gn/T/npm-66866/1342533658619-0.44802877004258335/tmp.tgz', -14 verbose tar pack '.' ] -15 verbose tarball /var/folders/z1/mz6slcdn37z7tnt0v7m_l_780000gn/T/npm-66866/1342533658619-0.44802877004258335/tmp.tgz -16 verbose folder . -17 info prepublish inbox@0.1.12 -18 verbose tar unpack /var/folders/z1/mz6slcdn37z7tnt0v7m_l_780000gn/T/npm-66866/1342533658619-0.44802877004258335/tmp.tgz -19 silly gunzTarPerm modes [ '755', '644' ] -20 silly gunzTarPerm extractEntry package.json -21 silly gunzTarPerm extractEntry .npmignore -22 silly gunzTarPerm extractEntry README.md -23 silly gunzTarPerm extractEntry .travis.yml -24 silly gunzTarPerm extractEntry examples/append.js -25 silly gunzTarPerm extractEntry examples/id.js -26 silly gunzTarPerm extractEntry examples/list.js -27 silly gunzTarPerm extractEntry examples/xoauth.js -28 silly gunzTarPerm extractEntry lib/client.js -29 silly gunzTarPerm extractEntry lib/lineparser.js -30 silly gunzTarPerm extractEntry lib/mailbox.js -31 silly gunzTarPerm extractEntry lib/starttls.js -32 silly gunzTarPerm extractEntry lib/xoauth.js -33 silly gunzTarPerm extractEntry lib/names.json -34 silly gunzTarPerm extractEntry test/lineparser.js -35 silly gunzTarPerm extractEntry tools/clientplayground.js -36 silly gunzTarPerm extractEntry tools/parserplayground.js -37 silly gunzTarPerm extractEntry tools/proxy.js -38 verbose read json /var/folders/z1/mz6slcdn37z7tnt0v7m_l_780000gn/T/npm-66866/1342533658619-0.44802877004258335/package/package.json -39 verbose from cache /var/folders/z1/mz6slcdn37z7tnt0v7m_l_780000gn/T/npm-66866/1342533658619-0.44802877004258335/package/package.json -40 verbose tar pack [ '/Users/andris/.npm/inbox/0.1.12/package.tgz', -40 verbose tar pack '/var/folders/z1/mz6slcdn37z7tnt0v7m_l_780000gn/T/npm-66866/1342533658619-0.44802877004258335/package' ] -41 verbose tarball /Users/andris/.npm/inbox/0.1.12/package.tgz -42 verbose folder /var/folders/z1/mz6slcdn37z7tnt0v7m_l_780000gn/T/npm-66866/1342533658619-0.44802877004258335/package -43 verbose tar unpack /Users/andris/.npm/inbox/0.1.12/package.tgz -44 silly gunzTarPerm modes [ '755', '644' ] -45 silly gunzTarPerm extractEntry package.json -46 silly gunzTarPerm extractEntry .npmignore -47 silly gunzTarPerm extractEntry README.md -48 silly gunzTarPerm extractEntry .travis.yml -49 silly gunzTarPerm extractEntry examples/append.js -50 silly gunzTarPerm extractEntry examples/id.js -51 silly gunzTarPerm extractEntry examples/list.js -52 silly gunzTarPerm extractEntry examples/xoauth.js -53 silly gunzTarPerm extractEntry lib/client.js -54 silly gunzTarPerm extractEntry lib/lineparser.js -55 silly gunzTarPerm extractEntry lib/mailbox.js -56 silly gunzTarPerm extractEntry lib/starttls.js -57 silly gunzTarPerm extractEntry lib/xoauth.js -58 silly gunzTarPerm extractEntry lib/names.json -59 silly gunzTarPerm extractEntry test/lineparser.js -60 silly gunzTarPerm extractEntry tools/clientplayground.js -61 silly gunzTarPerm extractEntry tools/parserplayground.js -62 silly gunzTarPerm extractEntry tools/proxy.js -63 verbose read json /Users/andris/.npm/inbox/0.1.12/package/package.json -64 silly shasum updated bytes 26303 -65 info shasum 48654d86a706c6c2d413b1b15a52c82766ce06d6 -65 info shasum /Users/andris/.npm/inbox/0.1.12/package.tgz -66 verbose from cache /Users/andris/.npm/inbox/0.1.12/package/package.json -67 verbose chmod /Users/andris/.npm/inbox/0.1.12/package.tgz 644 -68 verbose chown /Users/andris/.npm/inbox/0.1.12/package.tgz [ 501, 20 ] -69 silly publish { name: 'inbox', -69 silly publish version: '0.1.12', -69 silly publish author: { name: 'Andris Reinman' }, -69 silly publish maintainers: [ { name: 'andris', email: 'andris@node.ee' } ], -69 silly publish main: 'lib/client.js', -69 silly publish dependencies: { mimelib: '*', iconv: '*' }, -69 silly publish devDependencies: { nodeunit: '*' }, -69 silly publish scripts: { test: 'nodeunit test' }, -69 silly publish readme: '# inbox\n\nThis is a work in progress IMAP client for node.js. \n\nThe project consists of two major parts\n\n * IMAP command parser (token based, more or less complete)\n * IMAP control for accessing mailboxes (under construction)\n\n[![Build Status](https://secure.travis-ci.org/andris9/inbox.png)](http://travis-ci.org/andris9/inbox)\n\n## Installation\n\nInstall from npm\n\n npm install inbox\n\n## API\n\n**NB!** This API is preliminary and may change.\n\nUse **inbox** module\n\n var inbox = require("inbox");\n\n### Create new IMAP connection\n\nCreate connection object with \n\n inbox.createConnection(port, host, options)\n\nwhere\n\n * **port** is the port to the server (defaults to 143 on non-secure and to 993 on secure connection)\n * **host** is the hostname of the server\n * **options** is an options object for auth etc.\n * **options.secureConnection** is a Boolean value to indicate if the connection is initially secure or not\n * **options.auth** is an authentication object\n * **options.auth.user** is the IMAP username\n * **options.auth.pass** is the IMAP password\n * **options.auth.XOAuthToken** (optional) is either a String or *inbox.createXOAuthGenerator* object\n * **options.clientId** is optional client ID params object\n * **options.clientId.name** is is the name param etc. see [rfc 2971](http://tools.ietf.org/html/rfc2971#section-3.3) for possible field names\n\nExample:\n\n var client = inbox.createConnection(false, "imap.gmail.com", {\n secureConnection: true,\n auth:{\n user: "test.nodemailer@gmail.com",\n pass: "Nodemailer123"\n }\n });\n\nOr when login with XOAUTH (see examples/xoauth.js)\n \n var client = inbox.createConnection(false, "imap.gmail.com", {\n secureConnection: true,\n auth:{\n XOAuthToken: inbox.createXOAuthGenerator({\n user: "test.nodemailer@gmail.com",\n token: "1/Gr2OVA2Ol64fNyjZCns-bkRau5eLisbdlEa_HSuTaEk",\n tokenSecret: "ymFpseHtEnrIsuL8Ppbfnnk3"\n })\n }\n });\n \nOnce the connection object has been created, use connect() to create the actual connection.\n\n client.connect();\n \nWhen the connection has been successfully established a \'connect\' event is emitted.\n\n client.on("connect", function(){\n console.log("Successfully connected to server");\n });\n\n### List available mailboxes\n\nTo list the available mailboxes use \n\n client.listMailboxes(callback)\n \nWhere\n\n * **callback** *(error, mailboxes)* returns a list of root mailbox object\n \nMailbox objects have the following properties\n\n * **name** - the display name of the mailbox\n * **path** - the actual name of the mailbox, use it for opening the mailbox\n * **type** - the type of the mailbox (if server hints about it)\n * **hasChildren** - boolean indicator, if true, has child mailboxes\n * **disabled** - boolean indicator, if true, can not be selected\n\nAdditionally mailboxes have the following methods\n\n * **open** *([options, ]callback)* - open the mailbox (shorthand for *client.openMailbox*)\n * **listChildren** *(callback)* - if the mailbox has children (*hasChildren* is true), lists the child mailboxes\n\nExample:\n\n client.listMailboxes(function(error, mailboxes){\n for(var i=0, len = mailboxes.length; i\',\n inReplyTo: \'<4FB16D5A.30808@gmail.com>\',\n references: [\'<4FB16D5A.30808@gmail.com>\',\'<1299323903.19454@foo.bar>\']\n },\n ...\n ]\n \n**NB!** if some properties are not present in a message, it may be not included\nin the message object - for example, if there are no "cc:" addresses listed, \nthere is no "cc" field in the message object \n\n### Fetch message details\n\nTo fetch message data (flags, title, etc) for a specific message, use\n\n client.fetchData(uid, callback)\n \nWhere\n\n * **uid** is the UID value for the mail\n * **callback** *(error, message)* is the callback function to with the message data object (or null if the message was not found). Gets an error parameter if error occured\n\nExample\n\n client.fetchData(123, function(error, message){\n console.log(message.flags);\n });\n\n### Fetch message contents\n\nMessage listing only retrieves the envelope part of the message. To get the full RFC822 message body\nyou need to fetch the message.\n\n var messageStream = client.createMessageStream(uid)\n \nWhere\n\n * **uid** is the UID value for the mail\n\nExample (output message contents to console)\n\n client.createMessageStream(123).pipe(process.stdout, {end: false});\n\n**NB!** If the opened mailbox is not in read-only mode, the message will be \nautomatically marked as read (\\Seen flag is set) when the message is fetched.\n\n### Message flags\n\nYou can add and remove message flags like `\\Seen` or `\\Answered` with `client.addFlags()` and `client.removeFlags()`\n\n**Add flags**\n\n client.addFlags(uid, flags, callback)\n\nWhere\n\n * **uid** is the message identifier\n * **flags** is the array of flags to be added\n * **callback** *(error, flags)* is the callback to run, gets message flags array as a parameter \n\n**Remove flags**\n\n client.removeFlags(uid, flags, callback)\n\nWhere\n\n * **uid** is the message identifier\n * **flags** is the array of flags to be removed\n * **callback** *(error, flags)* is the callback to run, gets message flags array as a parameter\n\nExample\n\n // add \\Seen and \\Flagged flag to a message\n client.addFlags(123, ["\\\\Seen", "\\\\Flagged"], function(err, flags){\n console.log("Current flags for a message: ", flags);\n });\n \n // remove \\Flagged flag from a message\n client.removeFlags(123, ["\\\\Flagged"], function(err, flags){\n console.log("Current flags for a message: ", flags);\n });\n\n### Upload a message\n\nYou can upload a message to current mailbox with `client.storeMessage()`\n\n client.storeMessage(message[, flags], callback)\n\nWhere\n\n * **message** is the message to be uploaded either as a string or a Buffer.\n * **flags** is an array of flags to set to the message (ie. `["\\\\Seen"]`)\n * **callback** is the callback function, gets message UID and UID and UIDValitity as a param\n\nExample\n\n client.storeMessage("From: ....", ["\\\\Seen"], function(err, params){\n console.log(err || params.UIDValidity +", "+ params.UID);\n });\n\nWhen adding a message to the mailbox, also new message event is raised, after \nthe mail has been stored.\n\n### Wait for new messages\n\nYou can listen for new incoming e-mails with event "new"\n\n client.on("new", function(message){\n console.log("New incoming message " + message.title);\n });\n \n## Complete example\n\nListing newest 10 messages:\n\n var inbox = require("inbox");\n \n var client = inbox.createConnection(false, "imap.gmail.com", {\n secureConnection: true,\n auth:{\n user: "test.nodemailer@gmail.com",\n pass: "Nodemailer123"\n }\n });\n \n client.connect();\n \n client.on("connect", function(){\n client.openMailbox("INBOX", function(error, info){\n if(error) throw error;\n \n client.listMessages(-10, function(err, messages){\n messages.forEach(function(message){\n console.log(message.UID + ": " + message.title);\n });\n });\n\n });\n });', -69 silly publish _id: 'inbox@0.1.12', -69 silly publish description: 'This is a work in progress IMAP client for node.js.', -69 silly publish dist: { shasum: '48654d86a706c6c2d413b1b15a52c82766ce06d6' } } -70 verbose url raw inbox -71 verbose url resolving [ 'https://registry.npmjs.org/', './inbox' ] -72 verbose url resolved https://registry.npmjs.org/inbox -73 info retry registry request attempt 1 at 17:00:58 -74 http PUT https://registry.npmjs.org/inbox -75 http 409 https://registry.npmjs.org/inbox -76 verbose url raw inbox -77 verbose url resolving [ 'https://registry.npmjs.org/', './inbox' ] -78 verbose url resolved https://registry.npmjs.org/inbox -79 info retry registry request attempt 1 at 17:01:00 -80 http GET https://registry.npmjs.org/inbox -81 http 200 https://registry.npmjs.org/inbox -82 error publish fail Cannot publish over existing version. -82 error publish fail Bump the 'version' field, set the --force flag, or -82 error publish fail npm unpublish 'inbox@0.1.12' -82 error publish fail and try again -83 error System Darwin 11.4.0 -84 error command "node" "/usr/local/bin/npm" "publish" -85 error cwd /Users/andris/Projects/inbox -86 error node -v v0.8.1 -87 error npm -v 1.1.33 -88 error code EPUBLISHCONFLICT -89 error message publish fail -90 verbose exit [ 1, true ] diff --git a/package.json b/package.json index e9c630f..1bc2664 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.13", + "version": "0.1.14", "author" : "Andris Reinman", "maintainers":[ { From 0e948e3ea75981e6f33d00e4185c362e67d89710 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 23 Jul 2012 17:29:15 +0300 Subject: [PATCH 024/106] removed console.log's --- lib/client.js | 5 ----- package.json | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/client.js b/lib/client.js index 3207c26..0f1250b 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1108,12 +1108,7 @@ IMAPClient.prototype._handlerUntaggedFetch = function(list){ nextUID = Number(this._selectedMailbox.UIDNext) || 0, currentUID = Number(envelopeData.UID) || 0; -console.log(envelopeData); - -console.log(nextUID, currentUID) - if(!nextUID || nextUID <= currentUID){ - console.log("!!!!!!!!") this._selectedMailbox.UIDNext = currentUID+1; } diff --git a/package.json b/package.json index 1bc2664..1715f96 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.14", + "version": "0.1.15", "author" : "Andris Reinman", "maintainers":[ { From bb387feb6ae8b2476dea8288a8316c1529f444f5 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 23 Jul 2012 17:33:55 +0300 Subject: [PATCH 025/106] found another wild console.log --- lib/client.js | 1 - package.json | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/client.js b/lib/client.js index 0f1250b..beeec59 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1102,7 +1102,6 @@ IMAPClient.prototype._handlerUntaggedSearch = function(list){ * @param {Array} list Params about a message */ IMAPClient.prototype._handlerUntaggedFetch = function(list){ - console.log("LISTLISTLOST", list) var envelope = (list || [])[3] || [], envelopeData = this._formatEnvelope(envelope), nextUID = Number(this._selectedMailbox.UIDNext) || 0, diff --git a/package.json b/package.json index 1715f96..54da4ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.15", + "version": "0.1.16", "author" : "Andris Reinman", "maintainers":[ { From 2896c4dbd6d47b43294cc49a6fff3c2189c64482 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Tue, 31 Jul 2012 10:01:34 +0300 Subject: [PATCH 026/106] When IDLE is not available, fallback to NOOP --- lib/client.js | 54 +++++++++++++++++++++++++++++++++++---------------- package.json | 2 +- 2 files changed, 38 insertions(+), 18 deletions(-) diff --git a/lib/client.js b/lib/client.js index beeec59..8d54d0b 100644 --- a/lib/client.js +++ b/lib/client.js @@ -119,7 +119,7 @@ IMAPClient.prototype.modes = { /** * Delay for breaking IDLE loop and running NOOP */ -IMAPClient.prototype.IDLE_TIMEOUT = 30 * 1000; +IMAPClient.prototype.IDLE_TIMEOUT = 60 * 1000; /** * Delay for entering IDLE mode after any command @@ -206,6 +206,11 @@ IMAPClient.prototype._init = function(){ */ this.idling = false; + /** + * Currently "nooping" when idle not available + */ + this.nooping = false; + /** * Waiting for idle start after issuing IDLE command */ @@ -529,7 +534,7 @@ IMAPClient.prototype._onServerLog = function(type, data){ if(this._log.length > this._logLength){ this._log.pop(); } -} +}; /** * Run as the handler for the initial command coming from the server. If it @@ -660,7 +665,7 @@ IMAPClient.prototype._responseRouter = function(data){ if(!isNaN(data[1]) && data[2] == "EXISTS"){ if(this._selectedMailbox.count != Number(data[1])){ this._selectedMailbox.count = Number(data[1]) || this._selectedMailbox.count || 0; - if(this.idling){ + if(this.idling || this.nooping){ this._checkNewMail(); } } @@ -1018,11 +1023,11 @@ IMAPClient.prototype._handlerUntaggedId = function(list){ */ IMAPClient.prototype._handlerUntaggedEnabled = function(list){ list = [].concat(list || []); - switch(list[0]){ - case "CONDSTORE": - this._condstoreEnabled = true; - break; + + if(list[0] == "CONDSTORE"){ + this._condstoreEnabled = true; } + }; /** @@ -1676,9 +1681,11 @@ IMAPClient.prototype.storeMessage = function(message, flags, callback){ this._connection.write("\r\n"); }).bind(this); }).bind(this)); -} - +}; +/** + * + */ IMAPClient.prototype.getMailbox = function(path, callback){ this._rootMailbox.listChildren(path, function(error, mailboxes){ if(error){ @@ -1695,14 +1702,27 @@ IMAPClient.prototype.getMailbox = function(path, callback){ * Enter IDLE mode */ IMAPClient.prototype.idle = function(){ - this._send("IDLE", (function(){ - this.idling = false; - this._idleEnd = false; - }).bind(this), (function(){ - this._idleWait = true; - this._idleEnd = false; - this._idleTimer = setTimeout(this._idleTimeout.bind(this), this.IDLE_TIMEOUT); - }).bind(this)); + if(this._capabilities.indexOf("IDLE")>=0){ + this._send("IDLE", (function(status){ + this.idling = false; + this._idleEnd = false; + }).bind(this), (function(){ + this._idleWait = true; + this._idleEnd = false; + this._idleTimer = setTimeout(this._idleTimeout.bind(this), this.IDLE_TIMEOUT); + }).bind(this)); + }else{ + if(this.debug){ + console.log("WARNING: Server does not support IDLE, fallback to NOOP"); + } + this._idleTimer = setTimeout((function(){ + this._send("NOOP", (function(status){ + this.nooping = false; + }).bind(this), (function(){ + this.nooping = true; + }).bind(this)); + }).bind(this), this.IDLE_TIMEOUT); + } }; /** diff --git a/package.json b/package.json index 54da4ab..8cd73ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.16", + "version": "0.1.17", "author" : "Andris Reinman", "maintainers":[ { From 532a511c45724388ed7326edb7ca506fc3b4fdf9 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 6 Aug 2012 15:03:45 +0300 Subject: [PATCH 027/106] added COPY/MOVE/DELETE for messages --- README.md | 53 ++++++++++++++++++++++++++++++++ examples/copy.js | 33 ++++++++++++++++++++ examples/delete.js | 33 ++++++++++++++++++++ examples/move.js | 33 ++++++++++++++++++++ lib/client.js | 75 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 6 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 examples/copy.js create mode 100644 examples/delete.js create mode 100644 examples/move.js diff --git a/README.md b/README.md index e775d31..2d640d3 100644 --- a/README.md +++ b/README.md @@ -316,6 +316,59 @@ Example When adding a message to the mailbox, also new message event is raised, after the mail has been stored. +### Copy a message + +You can copy a message from current mailbox to a selected one with `client.copyMessage()` + + client.copyMessage(uid, destination, callback) + +Where + + * **uid** is the message identifier. + * **destination** is the path to the destination mailbox + * **callback** is the callback function + +Example + + client.copyMessage(123, "[GMail]/Junk", function(err){ + console.log(err || "success, copied to junk"); + }); + +### Move a message + +You can move a message from current mailbox to a selected one with `client.moveMessage()` + + client.moveMessage(uid, destination, callback) + +Where + + * **uid** is the message identifier. + * **destination** is the path to the destination mailbox + * **callback** is the callback function + +Example + + client.moveMessage(123, "[GMail]/Junk", function(err){ + console.log(err || "success, moved to junk"); + }); + +### Delete a message + +You can delete a message from current mailbox with `client.deleteMessage()` + + client.deleteMessage(uid, callback) + +Where + + * **uid** is the message identifier. + * **callback** is the callback function + +Example + + client.deleteMessage(123, function(err){ + console.log(err || "success, message deleted"); + }); + ### Wait for new messages You can listen for new incoming e-mails with event "new" diff --git a/examples/copy.js b/examples/copy.js new file mode 100644 index 0000000..3523131 --- /dev/null +++ b/examples/copy.js @@ -0,0 +1,33 @@ +var inbox = require(".."), + util = require("util"); + +var client = inbox.createConnection(false, "imap.gmail.com", { + secureConnection: true, + auth:{ + user: "test.nodemailer@gmail.com", + pass: "Nodemailer123" + }, + debug: true +}); + +client.connect(); + +client.on("connect", function(){ + + client.openMailbox("INBOX", function(error, mailbox){ + if(error) throw error; + + client.listMessages(-1, function(error, messages){ + messages.forEach(function(message){ + console.log("Message") + console.log(message); + + client.copyMessage(message.UID, "[Gmail]/Saadetud kirjad", function(error){ + console.log(arguments); + }) + }) + }) + + }); + +}); diff --git a/examples/delete.js b/examples/delete.js new file mode 100644 index 0000000..31420e5 --- /dev/null +++ b/examples/delete.js @@ -0,0 +1,33 @@ +var inbox = require(".."), + util = require("util"); + +var client = inbox.createConnection(false, "imap.gmail.com", { + secureConnection: true, + auth:{ + user: "test.nodemailer@gmail.com", + pass: "Nodemailer123" + }, + debug: true +}); + +client.connect(); + +client.on("connect", function(){ + + client.openMailbox("INBOX", function(error, mailbox){ + if(error) throw error; + + client.listMessages(-1, function(error, messages){ + messages.forEach(function(message){ + console.log("Message") + console.log(message); + + client.deleteMessage(message.UID, function(error){ + console.log(arguments); + }) + }) + }) + + }); + +}); diff --git a/examples/move.js b/examples/move.js new file mode 100644 index 0000000..4fd6878 --- /dev/null +++ b/examples/move.js @@ -0,0 +1,33 @@ +var inbox = require(".."), + util = require("util"); + +var client = inbox.createConnection(false, "imap.gmail.com", { + secureConnection: true, + auth:{ + user: "test.nodemailer@gmail.com", + pass: "Nodemailer123" + }, + debug: true +}); + +client.connect(); + +client.on("connect", function(){ + + client.openMailbox("INBOX", function(error, mailbox){ + if(error) throw error; + + client.listMessages(-1, function(error, messages){ + messages.forEach(function(message){ + console.log("Message") + console.log(message); + + client.moveMessage(message.UID, "[Gmail]/Saadetud kirjad", function(error){ + console.log(arguments); + }) + }) + }) + + }); + +}); diff --git a/lib/client.js b/lib/client.js index 8d54d0b..4dbfda8 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1635,6 +1635,81 @@ IMAPClient.prototype.createMessageStream = function(uid){ return stream; }; +/** + * Copy message from the active mailbox to the end of destination mailbox + * + * @param {Number} uid Message identifier + * @param {String} destination Destination folder to copy the message to + * @param {Function} callback Callback function to run after the copy succeeded or failed + */ +IMAPClient.prototype.copyMessage = function(uid, destination, callback){ + uid = Number(uid) || 0; + + if(!uid){ + if(typeof callback == "function"){ + callback(new Error("Invalid UID value")); + } + return; + } + + this._send("UID COPY " + uid + " " + this._escapeString(destination), (function(status){ + if(status != "OK"){ + return callback(new Error("Error copying message")); + } + return callback(null, true); + }).bind(this)); +} + +/** + * Delete message from the active mailbox + * + * @param {Number} uid Message identifier + * @param {Function} callback Callback function to run after the removal succeeded or failed + */ +IMAPClient.prototype.deleteMessage = function(uid, callback){ + uid = Number(uid) || 0; + + if(!uid){ + if(typeof callback == "function"){ + callback(new Error("Invalid UID value")); + } + return; + } + + this.addFlags(uid, "\\Deleted", (function(error, flags){ + if(error){ + return callback(error); + } + + this._send("EXPUNGE", (function(status){ + if(status != "OK"){ + return callback(new Error("Error removing message")); + } + return callback(null, true); + }).bind(this)); + + }).bind(this)); +} + +/** + * Move message from the active mailbox to the end of destination mailbox + * + * @param {Number} uid Message identifier + * @param {String} destination Destination folder to move the message to + * @param {Function} callback Callback function to run after the move succeeded or failed + */ +IMAPClient.prototype.moveMessage = function(uid, destination, callback){ + this.copyMessage(uid, destination, (function(error){ + if(error){ + return callback(error); + } + this.deleteMessage(uid, function(error){ + // we don't really care if the removal succeeded or not at this point + return callback(null, !error); + }); + }).bind(this)); +} + /** * Upload a message to the mailbox * diff --git a/package.json b/package.json index 8cd73ab..03879c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.17", + "version": "0.1.18", "author" : "Andris Reinman", "maintainers":[ { From 10bcc7084524e00e222299319d692499aca67524 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Wed, 8 Aug 2012 15:24:05 +0300 Subject: [PATCH 028/106] Added 2-legged OAuth support --- README.md | 19 ++++++++++++++++++- examples/{xoauth.js => xoauth-2lo.js} | 7 ++++--- lib/xoauth.js | 17 +++++++++++------ package.json | 2 +- 4 files changed, 34 insertions(+), 11 deletions(-) rename examples/{xoauth.js => xoauth-2lo.js} (85%) diff --git a/README.md b/README.md index 2d640d3..6c11d3a 100644 --- a/README.md +++ b/README.md @@ -52,8 +52,10 @@ Example: } }); -Or when login with XOAUTH (see examples/xoauth.js) +Or when login with XOAUTH (see examples/xoauth-3lo.js and examples/xoauth-2lo.js) + + // 3-legged- oauth var client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, auth:{ @@ -64,6 +66,21 @@ Or when login with XOAUTH (see examples/xoauth.js) }) } }); + +With 3-legged OAuth, consumerKey and consumerSecret both default to "anonymous" while with 2-legged they need to have proper values. + + // 2-legged- oauth + var client = inbox.createConnection(false, "imap.gmail.com", { + secureConnection: true, + auth:{ + XOAuthToken: inbox.createXOAuthGenerator({ + user: "test.nodemailer@gmail.com", + requestorId: "test.nodemailer@gmail.com", + consumerKey: "1/Gr2OVA2Ol64fNyjZCns-bkRau5eLisbdlEa_HSuTaEk", + consumerSecret: "ymFpseHtEnrIsuL8Ppbfnnk3" + }) + } + }); Once the connection object has been created, use connect() to create the actual connection. diff --git a/examples/xoauth.js b/examples/xoauth-2lo.js similarity index 85% rename from examples/xoauth.js rename to examples/xoauth-2lo.js index c469842..0b621b4 100644 --- a/examples/xoauth.js +++ b/examples/xoauth-2lo.js @@ -6,9 +6,10 @@ var client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, auth:{ XOAuthToken: inbox.createXOAuthGenerator({ - user: "test.nodemailer@gmail.com", - token: "1/Gr2OVA2Ol64fNyjZCns-bkRau5eLisbdlEa_HSuTaEk", - tokenSecret: "ymFpseHtEnrIsuL8Ppbfnnk3" + user: "user@example.com", + requestorId: "user@example.com", // required for 2 legged oauth + consumerKey: "abc", + consumerSecret: "def" }) }, debug: true diff --git a/lib/xoauth.js b/lib/xoauth.js index 4ba735f..04b668c 100644 --- a/lib/xoauth.js +++ b/lib/xoauth.js @@ -17,6 +17,7 @@ module.exports.XOAuthGenerator = XOAuthGenerator; * @param {Object} options * @param {String} [options.consumerKey="anonymous"] OAuth consumer key * @param {String} [options.consumerSecret="anonymous"] OAuth consumer secret + * @param {String} [options.requestorId] 2 legged OAuth requestor ID * @param {String} [options.nonce] Nonce value to be used for OAuth * @param {Number} [options.timestamp] Unix timestamp value to be used for OAuth * @param {String} options.user Username @@ -65,7 +66,6 @@ function generateOAuthBaseStr(method, requestUrl, params){ var reqArr = [method, requestUrl].concat(Object.keys(params).sort().map(function(key){ return key + "=" + encodeURIComponent(params[key]); }).join("&")); - return escapeAndJoin(reqArr); } @@ -76,20 +76,25 @@ function generateXOAuthStr(options, callback){ requestUrl = options.requestUrl || "https://mail.google.com/mail/b/" + (options.user || "") + "/imap/", baseStr, signatureKey, paramsStr, returnStr; - if(options.token){ + if(options.token && !options.requestorId){ params.oauth_token = options.token; } - + baseStr = generateOAuthBaseStr(options.method || "GET", requestUrl, params); - - signatureKey = escapeAndJoin([options.consumerSecret || "anonymous", options.tokenSecret]); + + if(options.requestorId){ + baseStr += encodeURIComponent("&xoauth_requestor_id=" + encodeURIComponent(options.requestorId)); + } + + signatureKey = escapeAndJoin([options.consumerSecret || "anonymous", ""]); params.oauth_signature = hmacSha1(baseStr, signatureKey); paramsStr = Object.keys(params).sort().map(function(key){ return key+"=\""+encodeURIComponent(params[key])+"\""; }).join(","); - returnStr = [options.method || "GET", requestUrl, paramsStr].join(" "); + returnStr = [options.method || "GET", requestUrl + + (options.requestorId ? "?xoauth_requestor_id=" + encodeURIComponent(options.requestorId) : ""), paramsStr].join(" "); if(typeof callback == "function"){ callback(null, new Buffer(returnStr, "utf-8").toString("base64")); diff --git a/package.json b/package.json index 03879c0..3036325 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.18", + "version": "0.1.19", "author" : "Andris Reinman", "maintainers":[ { From 6fd3b4edc0db44eb8386aa72a171b99cabdd1b20 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Thu, 9 Aug 2012 15:22:28 +0300 Subject: [PATCH 029/106] removed unicde spaces --- lib/xoauth.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/xoauth.js b/lib/xoauth.js index 04b668c..fddd2ee 100644 --- a/lib/xoauth.js +++ b/lib/xoauth.js @@ -54,8 +54,8 @@ function hmacSha1(str, key){ function initOAuthParams(options){ return { - oauth_consumer_key: options.consumerKey || "anonymous", - oauth_nonce: options.nonce || "" + Date.now() + Math.round(Math.random()*1000000), + oauth_consumer_key: options.consumerKey || "anonymous", + oauth_nonce: options.nonce || "" + Date.now() + Math.round(Math.random()*1000000), oauth_signature_method: "HMAC-SHA1", oauth_version: "1.0", oauth_timestamp: options.timestamp || "" + Math.round(Date.now()/1000) @@ -73,27 +73,27 @@ function generateXOAuthStr(options, callback){ options = options || {}; var params = initOAuthParams(options), - requestUrl = options.requestUrl || "https://mail.google.com/mail/b/" + (options.user || "") + "/imap/", + requestUrl = options.requestUrl || "https://mail.google.com/mail/b/" + (options.user || "") + "/imap/", baseStr, signatureKey, paramsStr, returnStr; if(options.token && !options.requestorId){ params.oauth_token = options.token; } - baseStr = generateOAuthBaseStr(options.method || "GET", requestUrl, params); + baseStr = generateOAuthBaseStr(options.method || "GET", requestUrl, params); if(options.requestorId){ baseStr += encodeURIComponent("&xoauth_requestor_id=" + encodeURIComponent(options.requestorId)); } - signatureKey = escapeAndJoin([options.consumerSecret || "anonymous", ""]); + signatureKey = escapeAndJoin([options.consumerSecret || "anonymous", ""]); params.oauth_signature = hmacSha1(baseStr, signatureKey); paramsStr = Object.keys(params).sort().map(function(key){ return key+"=\""+encodeURIComponent(params[key])+"\""; }).join(","); - returnStr = [options.method || "GET", requestUrl + + returnStr = [options.method || "GET", requestUrl + (options.requestorId ? "?xoauth_requestor_id=" + encodeURIComponent(options.requestorId) : ""), paramsStr].join(" "); if(typeof callback == "function"){ From 030c3524c76ef16beadf077aad4ebc8409e2df23 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Thu, 16 Aug 2012 14:39:36 +0300 Subject: [PATCH 030/106] fixed open mailbox error bug --- examples/delete.js | 8 +++----- lib/client.js | 2 +- lib/xoauth.js | 3 +++ package.json | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/examples/delete.js b/examples/delete.js index 31420e5..62aee75 100644 --- a/examples/delete.js +++ b/examples/delete.js @@ -24,10 +24,8 @@ client.on("connect", function(){ client.deleteMessage(message.UID, function(error){ console.log(arguments); - }) - }) - }) - + }); + }); + }); }); - }); diff --git a/lib/client.js b/lib/client.js index 4dbfda8..8ebe13b 100644 --- a/lib/client.js +++ b/lib/client.js @@ -979,7 +979,7 @@ IMAPClient.prototype._handlerTaggedSelect = function(callback, status, params){ error.errorType = "MailboxError"; error.errorLog = this._log.slice(0, this._logLength); if(typeof callback == "function"){ - callback(null, error); + callback(error); }else{ this.emit("error", error); } diff --git a/lib/xoauth.js b/lib/xoauth.js index fddd2ee..7eceba4 100644 --- a/lib/xoauth.js +++ b/lib/xoauth.js @@ -87,12 +87,15 @@ function generateXOAuthStr(options, callback){ } signatureKey = escapeAndJoin([options.consumerSecret || "anonymous", ""]); + params.oauth_signature = hmacSha1(baseStr, signatureKey); paramsStr = Object.keys(params).sort().map(function(key){ return key+"=\""+encodeURIComponent(params[key])+"\""; }).join(","); + // Liidab kokku üheks pikaks stringiks kujul "METHOD URL BODY" + // 2-legged variandi puhul lisab BODY parameetritele otsa ka requestor_id väärtuse returnStr = [options.method || "GET", requestUrl + (options.requestorId ? "?xoauth_requestor_id=" + encodeURIComponent(options.requestorId) : ""), paramsStr].join(" "); diff --git a/package.json b/package.json index 3036325..fc8a5ca 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.19", + "version": "0.1.20", "author" : "Andris Reinman", "maintainers":[ { From 6b9377499350cfab3c9f4d2a63f7ba7721135ed8 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Sat, 15 Sep 2012 19:46:01 +0300 Subject: [PATCH 031/106] added X-GM-LABELS and X-GM-THRID support --- lib/client.js | 81 ++++++++++++++++++++++++++++++++++++++++++++++---- lib/mailbox.js | 50 +++++++++++++++++++------------ lib/names.json | 2 +- package.json | 5 ++-- 4 files changed, 110 insertions(+), 28 deletions(-) diff --git a/lib/client.js b/lib/client.js index 8ebe13b..9aaaabd 100644 --- a/lib/client.js +++ b/lib/client.js @@ -13,10 +13,13 @@ var Stream = require("stream").Stream, IMAPLineParser = require("./lineparser"), mimelib = require("mimelib"), xoauth = require("./xoauth"), + xoauth2 = require("xoauth2"), packageData = require("../package.json"), Iconv = require("iconv").Iconv, fromUTF7 = new Iconv("UTF-16", "UTF-8//TRANSLIT//IGNORE"), - Mailbox = require("./mailbox").Mailbox; + mailboxlib = require("./mailbox"), + Mailbox = mailboxlib.Mailbox, + detectMailboxType = mailboxlib.detectMailboxType; var X_CLIENT_NAME = "inbox", X_CLIENT_URL = "https://github.com/andris9/inbox"; @@ -1144,7 +1147,10 @@ IMAPClient.prototype._postCapability = function(){ if(this._currentState == this.states.PREAUTH){ this._updatedCapabilities = false; - if(this._capabilities.indexOf("AUTH=XOAUTH")>=0 && this.options.auth.XOAuthToken){ + if(this._capabilities.indexOf("AUTH=XOAUTH2")>=0 && this.options.auth.XOAuth2){ + // FIXME: Add XOAUTH2 login option + throw new Error("Not yet implemented"); + }else if(this._capabilities.indexOf("AUTH=XOAUTH")>=0 && this.options.auth.XOAuthToken){ if(typeof this.options.auth.XOAuthToken == "object"){ this._send("AUTHENTICATE XOAUTH " + this.options.auth.XOAuthToken.generate(), this._handlerTaggedLogin.bind(this)); @@ -1206,7 +1212,6 @@ IMAPClient.prototype._escapeString = function(str){ * @return {Object} structured envelope data */ IMAPClient.prototype._formatEnvelope = function(envelopeData){ - if(!Array.isArray(envelopeData)){ return null; } @@ -1234,6 +1239,50 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ if(dataObject.FLAGS){ message.flags = dataObject.FLAGS || []; } + + if(dataObject["X-GM-THRID"]){ + message.xGMThreadId = dataObject["X-GM-THRID"]; + } + + if(dataObject["X-GM-LABELS"] && dataObject["X-GM-LABELS"].length){ + message.xGMLabels = (dataObject["X-GM-LABELS"] || []).map((function(label){ + var type; + + if(label && typeof label == "object" && label.params){ + label = label.params.map((function(localLabel){ + return "\\" + localLabel; + }).bind(this)).join(this._mailboxDelimiter); + } + + label = this._convertFromUTF7(label || ""); + + // trim delimiters + if(label.charAt(0) == this._mailboxDelimiter){ + label = label.substr(1); + } + if(label.charAt(label.length-1) == this._mailboxDelimiter){ + label = label.substr(0, label.length-1); + } + + if(label.search(this._mailboxDelimiter) > 0){ // ignore the first char + return label.split(this._mailboxDelimiter).map(function(localLabel){ + var localType; + if((localType = detectMailboxType(localLabel)) != "Normal"){ + // Add flag indicator + return "\\"+localType; + }else{ + return localLabel; + } + }); + } + + if((type = detectMailboxType(label)) != "Normal"){ + // Add flag indicator + return "\\"+type; + } + return label; + }).bind(this)); + } if(dataObject.ENVELOPE){ message.date = new Date(dataObject.ENVELOPE[0] || Date.now()); @@ -1273,6 +1322,26 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ }); })); } + + if(headers['thread-index'] && Array.isArray(headers['thread-index'])){ + message.threadIndex = [].concat.apply([], headers['thread-index'].map(function(row){ + + var thread = new Buffer(row, "base64"), + threads = [thread.slice(0, 22).toString("hex")], + pos = 22, i = 0; + + while(pos < thread.length){ + threads.push(threads[i++] + thread.slice(pos, pos + 5 < thread.length ? pos + 5 : thread.length).toString("hex")); + pos += 5; + } + + threads = threads.map(function(id){ + return new Buffer(id, "hex").toString("base64"); + }); + + return threads; + })); + } } return message; @@ -1325,7 +1394,7 @@ IMAPClient.prototype._checkNewMail = function(){ return; } - this._send("UID FETCH "+this._selectedMailbox.UIDNext+":* (FLAGS ENVELOPE BODY[HEADER.FIELDS (REFERENCES)])", (function(){ + this._send("UID FETCH "+this._selectedMailbox.UIDNext+":* (FLAGS ENVELOPE BODY[HEADER.FIELDS (REFERENCES THREAD-INDEX)]" +(this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"")+ ")", (function(){ this._checkForNewMail = false; }).bind(this), (function(){ @@ -1435,7 +1504,7 @@ IMAPClient.prototype.listMessages = function(from, limit, callback){ this._collectMailList = true; this._mailList = []; - this._send("FETCH "+from+":"+to+" (UID FLAGS ENVELOPE BODY[HEADER.FIELDS (REFERENCES)])", (function(status){ + this._send("FETCH "+from+":"+to+" (UID FLAGS ENVELOPE BODY[HEADER.FIELDS (REFERENCES THREAD-INDEX)]" +(this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"")+ ")", (function(status){ this._collectMailList = false; if(typeof callback != "function"){ @@ -1564,7 +1633,7 @@ IMAPClient.prototype.fetchData = function(uid, callback){ return; } - this._send("UID FETCH "+uid+":"+uid+" (FLAGS ENVELOPE BODY[HEADER.FIELDS (REFERENCES)])", (function(status){ + this._send("UID FETCH "+uid+":"+uid+" (FLAGS ENVELOPE BODY[HEADER.FIELDS (REFERENCES THREAD-INDEX)]" +(this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"")+ ")", (function(status){ this._collectMailList = false; if(typeof callback != "function"){ diff --git a/lib/mailbox.js b/lib/mailbox.js index 689b1cc..ff8e97a 100644 --- a/lib/mailbox.js +++ b/lib/mailbox.js @@ -5,6 +5,7 @@ var mailboxNames = require("./names.json"); * @namespace mailbox */ module.exports.Mailbox = Mailbox; +module.exports.detectMailboxType = detectMailboxType; /** * Create a mailbox object @@ -47,24 +48,7 @@ Mailbox.prototype.open = function(options, callback){ * Detects the type by the name of the mailbox */ Mailbox.prototype.detectType = function(){ - - if(mailboxNames.sent.indexOf(this.name.toLowerCase())>=0){ - return "Sent"; - } - - if(mailboxNames.trash.indexOf(this.name.toLowerCase())>=0){ - return "Trash"; - } - - if(mailboxNames.junk.indexOf(this.name.toLowerCase())>=0){ - return "Junk"; - } - - if(mailboxNames.drafts.indexOf(this.name.toLowerCase())>=0){ - return "Drafts"; - } - - return "Normal"; + return detectMailboxType(this.name); }; /** @@ -147,4 +131,32 @@ Mailbox.prototype.createChild = function(name, callback){ callback(new Error("Creating mailbox failed")); } }).bind(this)); -}; \ No newline at end of file +}; + +/** + * Returns mailbox type detected by the name of the mailbox + * + * @param {String} mailboxName Mailbox name + * @return {String} Mailbox type + */ +function detectMailboxType(mailboxName){ + mailboxName = (mailboxName || "").toString().trim().toLowerCase(); + + if(mailboxNames.sent.indexOf(mailboxName)>=0){ + return "Sent"; + } + + if(mailboxNames.trash.indexOf(mailboxName)>=0){ + return "Trash"; + } + + if(mailboxNames.junk.indexOf(mailboxName)>=0){ + return "Junk"; + } + + if(mailboxNames.drafts.indexOf(mailboxName)>=0){ + return "Drafts"; + } + + return "Normal"; +} \ No newline at end of file diff --git a/lib/names.json b/lib/names.json index 6fd588f..4610a17 100644 --- a/lib/names.json +++ b/lib/names.json @@ -1,5 +1,5 @@ { - "sent": ["aika", "bidaliak", "bidalita", "dihantar", "e rometsweng", "e tindami", "elküldött", "elküldöttek", "enviadas", "enviadas", "enviados", "enviats", "envoyés", "ethunyelweyo", "expediate", "ezipuru", "gesendete", "gestuur", "gönderilmiş öğeler", "göndərilənlər", "iberilen", "inviati", "išsiųstieji", "kuthunyelwe", "lasa", "lähetetyt", "messages envoyés", "naipadala", "nalefa", "napadala", "nosūtītās ziņas", "odeslané", "padala", "poslane", "poslano", "poslano", "poslané", "poslato", "saadetud", "sendt", "sendt", "sent", "sent items", "sent messages", "sända poster", "sänt", "terkirim", "ti fi ranṣẹ", "të dërguara", "verzonden", "vilivyotumwa", "wysłane", "đã gửi", "σταλθέντα", "жиберилген", "жіберілгендер", "изпратени", "илгээсэн", "ирсол шуд", "испратено", "надіслані", "отправленные", "пасланыя", "юборилган", "ուղարկված", "נשלחו", "פריטים שנשלחו", "المرسلة", "بھیجے گئے", "سوزمژہ", "لېګل شوی", "موارد ارسال شده", "पाठविले", "पाठविलेले", "प्रेषित", "भेजा गया", "প্রেরিত", "প্রেরিত", "প্ৰেৰিত", "ਭੇਜੇ", "મોકલેલા", "ପଠାଗଲା", "அனுப்பியவை", "పంపించబడింది", "ಕಳುಹಿಸಲಾದ", "അയച്ചു", "යැවු පණිවුඩ", "ส่งแล้ว", "გაგზავნილი", "የተላኩ", "បាន​ផ្ញើ", "寄件備份", "寄件備份", "已发信息", "送信済みメール", "발신 메시지", "보낸 편지함"], + "sent": ["aika", "bidaliak", "bidalita", "dihantar", "e rometsweng", "e tindami", "elküldött", "elküldöttek", "enviadas", "enviadas", "enviados", "enviats", "envoyés", "ethunyelweyo", "expediate", "ezipuru", "gesendete", "gestuur", "gönderilmiş öğeler", "göndərilənlər", "iberilen", "inviati", "išsiųstieji", "kuthunyelwe", "lasa", "lähetetyt", "messages envoyés", "naipadala", "nalefa", "napadala", "nosūtītās ziņas", "odeslané", "padala", "poslane", "poslano", "poslano", "poslané", "poslato", "saadetud", "saadetud kirjad", "sendt", "sendt", "sent", "sent items", "sent messages", "sända poster", "sänt", "terkirim", "ti fi ranṣẹ", "të dërguara", "verzonden", "vilivyotumwa", "wysłane", "đã gửi", "σταλθέντα", "жиберилген", "жіберілгендер", "изпратени", "илгээсэн", "ирсол шуд", "испратено", "надіслані", "отправленные", "пасланыя", "юборилган", "ուղարկված", "נשלחו", "פריטים שנשלחו", "المرسلة", "بھیجے گئے", "سوزمژہ", "لېګل شوی", "موارد ارسال شده", "पाठविले", "पाठविलेले", "प्रेषित", "भेजा गया", "প্রেরিত", "প্রেরিত", "প্ৰেৰিত", "ਭੇਜੇ", "મોકલેલા", "ପଠାଗଲା", "அனுப்பியவை", "పంపించబడింది", "ಕಳುಹಿಸಲಾದ", "അയച്ചു", "යැවු පණිවුඩ", "ส่งแล้ว", "გაგზავნილი", "የተላኩ", "បាន​ផ្ញើ", "寄件備份", "寄件備份", "已发信息", "送信済みメール", "발신 메시지", "보낸 편지함"], "trash": ["articole șterse", "bin", "borttagna objekt", "deleted", "deleted items", "deleted messages", "elementi eliminati", "elementos borrados", "elementos eliminados", "gelöschte objekte", "item dipadam", "itens apagados", "itens excluídos", "mục đã xóa", "odstraněné položky", "pesan terhapus", "poistetut", "praht", "silinmiş öğeler", "slettede beskeder", "slettede elementer", "trash", "törölt elemek", "usunięte wiadomości", "verwijderde items", "vymazané správy", "éléments supprimés", "видалені", "жойылғандар", "удаленные", "פריטים שנמחקו", "العناصر المحذوفة", "موارد حذف شده", "รายการที่ลบ", "已删除邮件", "已刪除項目", "已刪除項目"], "junk": ["bulk mail", "correo no deseado", "courrier indésirable", "istenmeyen", "istenmeyen e-posta", "junk", "levélszemét", "nevyžiadaná pošta", "nevyžádaná pošta", "no deseado", "posta indesiderata", "pourriel", "roskaposti", "skräppost", "spam", "spam", "spamowanie", "søppelpost", "thư rác", "спам", "דואר זבל", "الرسائل العشوائية", "هرزنامه", "สแปม", "‎垃圾郵件", "垃圾邮件", "垃圾電郵"], "drafts": ["ba brouillon", "borrador", "borrador", "borradores", "bozze", "brouillons", "bản thảo", "ciorne", "concepten", "draf", "drafts", "drög", "entwürfe", "esborranys", "garalamalar", "ihe edeturu", "iidrafti", "izinhlaka", "juodraščiai", "kladd", "kladder", "koncepty", "koncepty", "konsep", "konsepte", "kopie robocze", "layihələr", "luonnokset", "melnraksti", "meralo", "mesazhe të padërguara", "mga draft", "mustandid", "nacrti", "nacrti", "osnutki", "piszkozatok", "rascunhos", "rasimu", "skice", "taslaklar", "tsararrun saƙonni", "utkast", "vakiraoka", "vázlatok", "zirriborroak", "àwọn àkọpamọ́", "πρόχειρα", "жобалар", "нацрти", "нооргууд", "сиёҳнавис", "хомаки хатлар", "чарнавікі", "чернетки", "чернови", "черновики", "черновиктер", "սևագրեր", "טיוטות", "مسودات", "مسودات", "موسودې", "پیش نویسها", "ڈرافٹ/", "ड्राफ़्ट", "प्रारूप", "খসড়া", "খসড়া", "ড্ৰাফ্ট", "ਡ੍ਰਾਫਟ", "ડ્રાફ્ટસ", "ଡ୍ରାଫ୍ଟ", "வரைவுகள்", "చిత్తు ప్రతులు", "ಕರಡುಗಳು", "കരടുകള്‍", "කෙටුම් පත්", "ฉบับร่าง", "მონახაზები", "ረቂቆች", "សារព្រាង", "下書き", "草稿", "草稿", "草稿", "임시 보관함"] diff --git a/package.json b/package.json index fc8a5ca..627e403 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.20", + "version": "0.1.21", "author" : "Andris Reinman", "maintainers":[ { @@ -13,7 +13,8 @@ "dependencies": { "mimelib": "*", - "iconv": "*" + "iconv": "*", + "xoauth2": "*" }, "devDependencies": { From 453c1ba438af844ff16dbf361e50073ce3055a55 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Sat, 15 Sep 2012 23:31:35 +0300 Subject: [PATCH 032/106] added xoauth2, fixed 3-legged xoauth issue --- README.md | 19 +++++++++++ examples/xoauth2.js | 50 ++++++++++++++++++++++++++++ lib/client.js | 79 +++++++++++++++++++++++++++++++++++++++------ lib/xoauth.js | 2 +- 4 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 examples/xoauth2.js diff --git a/README.md b/README.md index 6c11d3a..8fc6cfd 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ where * **options.auth** is an authentication object * **options.auth.user** is the IMAP username * **options.auth.pass** is the IMAP password + * **options.auth.XOAuth2** (optional) is either an object with {user, clientId, clientSecret, refreshToken} or *xoauth2.createXOAuth2Generator* object, see [xoauth2](https://github.com/andris9/xoauth2) for details * **options.auth.XOAuthToken** (optional) is either a String or *inbox.createXOAuthGenerator* object * **options.clientId** is optional client ID params object * **options.clientId.name** is is the name param etc. see [rfc 2971](http://tools.ietf.org/html/rfc2971#section-3.3) for possible field names @@ -52,6 +53,24 @@ Example: } }); +Or when login with XOAUTH2 (see examples/xoauth2) + + // XOAUTH2 + var client = inbox.createConnection(false, "imap.gmail.com", { + secureConnection: true, + auth:{ + XOAuth2:{ + user: "example.user@gmail.com", + clientId: "8819981768.apps.googleusercontent.com", + clientSecret: "{client_secret}", + refreshToken: "1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI", + accessToken: "vF9dft4qmTc2Nvb3RlckBhdHRhdmlzdGEuY29tCg==", + timeout: 3600 + } + } + }); + + Or when login with XOAUTH (see examples/xoauth-3lo.js and examples/xoauth-2lo.js) diff --git a/examples/xoauth2.js b/examples/xoauth2.js new file mode 100644 index 0000000..764d9d1 --- /dev/null +++ b/examples/xoauth2.js @@ -0,0 +1,50 @@ + +var inbox = require(".."), + util = require("util"); + +var client = inbox.createConnection(false, "imap.gmail.com", { + secureConnection: true, + auth:{ + XOAuth2:{ + user: "example.user@gmail.com", + clientId: "8819981768.apps.googleusercontent.com", + clientSecret: "{client_secret}", + refreshToken: "1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI", + accessToken: "vF9dft4qmTc2Nvb3RlckBhdHRhdmlzdGEuY29tCg==", + timeout: 3600 + } + }, + debug: true +}); + +client.connect(); + +client.on("error", function(err){ + console.log(err) +}); + +client.on("connect", function(){ + + client.listMailboxes(console.log); + + client.openMailbox("INBOX", function(error, mailbox){ + if(error) throw error; + + // List newest 10 messages + client.listMessages(-10, function(err, messages){ + messages.forEach(function(message){ + console.log(message.UID+": "+message.title); + }); + }); + + }); + + // on new messages, print to console + client.on("new", function(message){ + console.log("New message:"); + console.log(util.inspect(message, false, 7)); + + client.createMessageStream(message.UID).pipe(process.stdout, {end: false}); + + }); +}); diff --git a/lib/client.js b/lib/client.js index 9aaaabd..bc05469 100644 --- a/lib/client.js +++ b/lib/client.js @@ -96,6 +96,24 @@ function IMAPClient(port, host, options){ */ this.debug = !!this.options.debug; + /** + * XOAuth2 token generator if XOAUTH2 auth is used + * @private + */ + this._xoauth2 = false; + this._xoauth2RetryCount = 0; + + if(typeof this.options.auth && typeof this.options.auth.XOAuth2){ + if(typeof this.options.auth.XOAuth2 == "object" && typeof this.options.auth.XOAuth2.getToken == "function"){ + this._xoauth2 = this.options.auth.XOAuth2; + }else if(typeof this.options.auth.XOAuth2 == "object"){ + if(!this.options.auth.XOAuth2.user && this.options.auth.user){ + this.options.auth.XOAuth2.user = this.options.auth.user; + } + this._xoauth2 = xoauth2.createXOAuth2Generator(this.options.auth.XOAuth2); + } + } + this._init(); } utillib.inherits(IMAPClient, Stream); @@ -602,7 +620,7 @@ IMAPClient.prototype._responseRouter = function(data){ } if(this._literalHandler){ - this._literalHandler(); + this._literalHandler(data.slice(1)); } }else if(this._literalHandler){ this._literalHandler = null; @@ -821,6 +839,7 @@ IMAPClient.prototype._handlerTaggedStartTLS = function(status){ */ IMAPClient.prototype._handlerTaggedLogin = function(status){ if(status == "OK"){ + this._xoauth2RetryCount = 0; this._currentState = this.states.AUTH; if(!this._updatedCapabilities){ this._send("CAPABILITY", this._handlerTaggedCapability.bind(this)); @@ -828,11 +847,23 @@ IMAPClient.prototype._handlerTaggedLogin = function(status){ this._postAuth(); } }else{ - var error = new Error("Authentication failed"); - error.errorType = "AuthenticationError"; - error.errorLog = this._log.slice(0, this._logLength); - this.emit("error", error); - this.close(); + if(this._xoauth2 && this._xoauth2RetryCount && this._xoauth2RetryCount<3){ + this._xoauth2.generateToken((function(err, token){ + if(err){ + err.errorType = "AuthenticationError"; + err.errorLog = this._log.slice(0, this._logLength); + this.emit("error", err); + return; + } + this._postCapability(); + }).bind(this)); + }else{ + var error = new Error("Authentication failed"); + error.errorType = "AuthenticationError"; + error.errorLog = this._log.slice(0, this._logLength); + this.emit("error", error); + this.close(); + } } }; @@ -1147,9 +1178,39 @@ IMAPClient.prototype._postCapability = function(){ if(this._currentState == this.states.PREAUTH){ this._updatedCapabilities = false; - if(this._capabilities.indexOf("AUTH=XOAUTH2")>=0 && this.options.auth.XOAuth2){ - // FIXME: Add XOAUTH2 login option - throw new Error("Not yet implemented"); + if(this._capabilities.indexOf("AUTH=XOAUTH2")>=0 && this._xoauth2){ + this._xoauth2.getToken((function(err, token){ + if(err){ + err.errorType = "AuthenticationError"; + err.errorLog = this._log.slice(0, this._logLength); + this.emit("error", err); + return; + } + this._send("AUTHENTICATE XOAUTH2 " + token, + this._handlerTaggedLogin.bind(this), (function(){ + this._literalHandler = (function(message){ + message = (Array.isArray(message) && message[0] || message || "").toString().trim(); + var data; + try{ + data = JSON.parse(new Buffer(message, "base64").toString("utf-8")); + }catch(E){ + data = { + status: 500, + error: E.message + }; + } + + if(['400', '401'].indexOf(data.status)>=0){ + this._xoauth2RetryCount = (this._xoauth2RetryCount || 0) + 1; + } + + this._connection.write("\r\n"); + if(this.debug){ + console.log("CLIENT:"); + } + }).bind(this); + }).bind(this)); + }).bind(this)); }else if(this._capabilities.indexOf("AUTH=XOAUTH")>=0 && this.options.auth.XOAuthToken){ if(typeof this.options.auth.XOAuthToken == "object"){ this._send("AUTHENTICATE XOAUTH " + this.options.auth.XOAuthToken.generate(), diff --git a/lib/xoauth.js b/lib/xoauth.js index 7eceba4..42e4c8b 100644 --- a/lib/xoauth.js +++ b/lib/xoauth.js @@ -86,7 +86,7 @@ function generateXOAuthStr(options, callback){ baseStr += encodeURIComponent("&xoauth_requestor_id=" + encodeURIComponent(options.requestorId)); } - signatureKey = escapeAndJoin([options.consumerSecret || "anonymous", ""]); + signatureKey = escapeAndJoin([options.consumerSecret || "anonymous", options.tokenSecret || ""]); params.oauth_signature = hmacSha1(baseStr, signatureKey); From 4e7b04490579c66a2e5e3c3401ad638490a360dd Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Sat, 15 Sep 2012 23:40:16 +0300 Subject: [PATCH 033/106] updated 3-legged example --- examples/xoauth-3lo.js | 46 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 examples/xoauth-3lo.js diff --git a/examples/xoauth-3lo.js b/examples/xoauth-3lo.js new file mode 100644 index 0000000..b886ab5 --- /dev/null +++ b/examples/xoauth-3lo.js @@ -0,0 +1,46 @@ +var inbox = require(".."), + util = require("util"); + +var client = inbox.createConnection(false, "imap.gmail.com", { + secureConnection: true, + auth:{ + XOAuthToken: inbox.createXOAuthGenerator({ + user: "test.nodemailer@gmail.com", + token: "1/Gr2OVA2Ol64fNyjZCns-bkRau5eLisbdlEa_HSuTaEk", + tokenSecret: "ymFpseHtEnrIsuL8Ppbfnnk3" + }) + }, + debug: true +}); + +client.connect(); + +client.on("error", function(err){ + console.log(err) +}); + +client.on("connect", function(){ + + client.listMailboxes(console.log); + + client.openMailbox("INBOX", function(error, mailbox){ + if(error) throw error; + + // List newest 10 messages + client.listMessages(-10, function(err, messages){ + messages.forEach(function(message){ + console.log(message.UID+": "+message.title); + }); + }); + + }); + + // on new messages, print to console + client.on("new", function(message){ + console.log("New message:"); + console.log(util.inspect(message, false, 7)); + + client.createMessageStream(message.UID).pipe(process.stdout, {end: false}); + + }); +}); \ No newline at end of file From ec87ae6f29a19e0b7606ce847f2071c0083c6ccf Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 17 Sep 2012 16:26:20 +0300 Subject: [PATCH 034/106] added folders and message type (draft, sent etc.) to mailObject --- lib/client.js | 68 ++++++++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 +- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/lib/client.js b/lib/client.js index bc05469..af7cca3 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1081,6 +1081,8 @@ IMAPClient.prototype._handlerUntaggedList = function(list){ path: path, tags: tags }; + + this._mailboxDelimiter = delimiter; this._mailboxList.push(mailbox); }; @@ -1305,13 +1307,24 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ message.xGMThreadId = dataObject["X-GM-THRID"]; } + var messageTypes = [], + messageType, + messageTypeMap = { + "Drafts": "Draft", + "Flagged": "Starred", + "Spam": "Junk" + }, + messageTypePreferenceOrder = ["Sent", "Draft", "Starred", "Junk", "Trash"]; + if(dataObject["X-GM-LABELS"] && dataObject["X-GM-LABELS"].length){ - message.xGMLabels = (dataObject["X-GM-LABELS"] || []).map((function(label){ + message.folders = (dataObject["X-GM-LABELS"] || []).map((function(label){ var type; if(label && typeof label == "object" && label.params){ label = label.params.map((function(localLabel){ - return "\\" + localLabel; + localLabel = this._convertFromUTF7(localLabel || ""); + messageTypes.push(localLabel); + return localLabel; }).bind(this)).join(this._mailboxDelimiter); } @@ -1330,7 +1343,8 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ var localType; if((localType = detectMailboxType(localLabel)) != "Normal"){ // Add flag indicator - return "\\"+localType; + messageTypes.push(localType); + return localLabel; }else{ return localLabel; } @@ -1339,12 +1353,58 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ if((type = detectMailboxType(label)) != "Normal"){ // Add flag indicator - return "\\"+type; + messageTypes.push(type); + return type; + } + + if(label.charAt(0)=="\\"){ + label = label.substr(1); + messageTypes.push(label); } return label; }).bind(this)); + }else{ + message.folders = (this._selectedMailbox.path || "").split(this._mailboxDelimiter) || []; + if(message.folders.length > 1){ + message.folders = [message.folders]; + }else if(message.folders.length){ + message.folders = [message.folders[0]]; + } + + if(message.folders.length){ + [].concat(message.folders[0]).forEach((function(localLabel){ + var type; + if((type = detectMailboxType(localLabel)) != "Normal"){ + // Add flag indicator + messageTypes.push(type); + } + }).bind(this)); + } + } + + if(message.flags.indexOf("\\Flagged")){ + messageTypes.push("Starred"); } + messageTypes = messageTypes.map(function(type){ + return messageTypeMap[type] || type; + }); + + messageTypes.sort(function(a, b){ + a = messageTypePreferenceOrder.indexOf(a); + b = messageTypePreferenceOrder.indexOf(b); + if(a<0){ + return 1; + } + if(b<0){ + return -1; + } + return a - b; + }); + + // FIXME: leia üks kindel tüüp, mitte massiiv + message.type = messageTypes.length && messageTypePreferenceOrder.indexOf(messageTypes[0])>=0 && messageTypes[0] || "Normal"; + if(dataObject.ENVELOPE){ message.date = new Date(dataObject.ENVELOPE[0] || Date.now()); diff --git a/package.json b/package.json index 627e403..aa44f29 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.21", + "version": "0.1.22", "author" : "Andris Reinman", "maintainers":[ { From f404e801e747320bf6f761d4281094e0266dfe63 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 17 Sep 2012 16:41:15 +0300 Subject: [PATCH 035/106] Fixed flag detection --- lib/client.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/client.js b/lib/client.js index af7cca3..7cb3467 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1382,7 +1382,7 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ } } - if(message.flags.indexOf("\\Flagged")){ + if(message.flags && message.flags.indexOf("\\Flagged")){ messageTypes.push("Starred"); } diff --git a/package.json b/package.json index aa44f29..eb1e14f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.22", + "version": "0.1.23", "author" : "Andris Reinman", "maintainers":[ { From 1208c93ed99dd60ce05032504070fcf8c7195c26 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 17 Sep 2012 22:06:09 +0300 Subject: [PATCH 036/106] fixed starred issue --- lib/client.js | 8 +++----- package.json | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/client.js b/lib/client.js index 7cb3467..e9ccc22 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1381,10 +1381,6 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ }).bind(this)); } } - - if(message.flags && message.flags.indexOf("\\Flagged")){ - messageTypes.push("Starred"); - } messageTypes = messageTypes.map(function(type){ return messageTypeMap[type] || type; @@ -1402,8 +1398,10 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ return a - b; }); - // FIXME: leia üks kindel tüüp, mitte massiiv message.type = messageTypes.length && messageTypePreferenceOrder.indexOf(messageTypes[0])>=0 && messageTypes[0] || "Normal"; + if(message.type == "Normal" && message.flags.indexOf("\\Flagged")>=0){ + message.type = "Starred"; + } if(dataObject.ENVELOPE){ message.date = new Date(dataObject.ENVELOPE[0] || Date.now()); diff --git a/package.json b/package.json index eb1e14f..a681cc2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.23", + "version": "0.1.24", "author" : "Andris Reinman", "maintainers":[ { From f4540c75578fdd6b13f6fd50e31cb4097f515ef0 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 17 Sep 2012 22:27:05 +0300 Subject: [PATCH 037/106] bumped version --- lib/client.js | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/client.js b/lib/client.js index e9ccc22..cc7deb2 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1399,7 +1399,7 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ }); message.type = messageTypes.length && messageTypePreferenceOrder.indexOf(messageTypes[0])>=0 && messageTypes[0] || "Normal"; - if(message.type == "Normal" && message.flags.indexOf("\\Flagged")>=0){ + if(message.type == "Normal" && message.flags && message.flags.indexOf("\\Flagged")>=0){ message.type = "Starred"; } @@ -1804,9 +1804,9 @@ IMAPClient.prototype.createMessageStream = function(uid){ if(!this._mailList.length){ if(status == "OK"){ - stream.emit("error", new Error("Selected message not found")); + stream.emit("error", new Error("Selected message not found: "+uid+"; "+this.port+"; "+this.host+"; "+JSON.stringify(this._selectedMailbox))); }else{ - stream.emit("error", new Error("Error fetching message")); + stream.emit("error", new Error("Error fetching message: "+uid+"; "+this.port+"; "+this.host+"; "+JSON.stringify(this._selectedMailbox))); } } diff --git a/package.json b/package.json index a681cc2..9da2a45 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.24", + "version": "0.1.25", "author" : "Andris Reinman", "maintainers":[ { From c86f0a09b6d63a049c19c5b139722a320fb225bf Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Wed, 19 Sep 2012 15:53:03 +0300 Subject: [PATCH 038/106] Updated folder handling --- lib/client.js | 21 +++++++++++++++++++-- package.json | 2 +- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/client.js b/lib/client.js index cc7deb2..950f2e4 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1342,7 +1342,6 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ return label.split(this._mailboxDelimiter).map(function(localLabel){ var localType; if((localType = detectMailboxType(localLabel)) != "Normal"){ - // Add flag indicator messageTypes.push(localType); return localLabel; }else{ @@ -1351,16 +1350,19 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ }); } + // Convert name to canonized version if((type = detectMailboxType(label)) != "Normal"){ // Add flag indicator messageTypes.push(type); return type; } - if(label.charAt(0)=="\\"){ + // Convert tags to names + if(label.charAt(0)=="\\" && label != "\\Important"){ label = label.substr(1); messageTypes.push(label); } + return label; }).bind(this)); }else{ @@ -1381,6 +1383,21 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ }).bind(this)); } } + + // remove duplicates + if(message.folders){ + var folderList = []; + for(var i=0, len=message.folders.length; i Date: Mon, 12 Nov 2012 12:13:17 +0200 Subject: [PATCH 039/106] added fetchFlags method --- .gitignore | 1 + README.md | 9 +++++++++ lib/client.js | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index fd4f2b0..2da1a66 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules .DS_Store +tools/log.txt diff --git a/README.md b/README.md index 8fc6cfd..7b18edb 100644 --- a/README.md +++ b/README.md @@ -299,6 +299,15 @@ automatically marked as read (\Seen flag is set) when the message is fetched. You can add and remove message flags like `\Seen` or `\Answered` with `client.addFlags()` and `client.removeFlags()` +**List flags** + + client.fetchFlags(uid, callback) + +Where + + * **uid** is the message identifier + * **callback** *(error, flags)* is the callback to run, gets message flags array as a parameter + **Add flags** client.addFlags(uid, flags, callback) diff --git a/lib/client.js b/lib/client.js index 950f2e4..f31dbda 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1746,6 +1746,55 @@ IMAPClient.prototype.removeFlags = function(uid, flags, callback){ this.updateFlags(uid, flags, "-", callback); }; +/** + * Fetches flags for selected message + * + * @param {Number} uid Message identifier + * @param {Function} callback Callback function to run with the flags array + */ +IMAPClient.prototype.fetchFlags = function(uid, callback){ + uid = Number(uid) || 0; + + if(!uid){ + if(typeof callback == "function"){ + callback(new Error("Invalid UID value")); + } + return; + } + + if(this._currentState != this.states.SELECTED){ + if(typeof callback == "function"){ + callback(new Error("No mailbox selected")); + } + return; + } + + this._send("UID FETCH "+uid+":"+uid+" (FLAGS)", (function(status){ + this._collectMailList = false; + + if(typeof callback != "function"){ + return; + } + + if(typeof callback == "function"){ + if(status == "OK"){ + if(!this._mailList.length){ + callback(null, null); + }else{ + callback(null, this._mailList[0].flags || []); + } + }else{ + callback(new Error("Error fetching message flags")); + } + } + + }).bind(this), + (function(){ + this._collectMailList = true; + this._mailList = []; + }).bind(this)); +}; + /** * Fetches envelope object for selected message * diff --git a/package.json b/package.json index dc16742..2d1bb36 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.26", + "version": "0.1.27", "author" : "Andris Reinman", "maintainers":[ { From d413e2acc38d0234ee8f2042f3292c3330e7ac28 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 17 Dec 2012 12:06:41 +0200 Subject: [PATCH 040/106] added listFlags --- lib/client.js | 550 ++++++++++++++++++++++++++++---------------------- 1 file changed, 305 insertions(+), 245 deletions(-) diff --git a/lib/client.js b/lib/client.js index f31dbda..3437798 100644 --- a/lib/client.js +++ b/lib/client.js @@ -34,7 +34,7 @@ module.exports.IMAPClient = IMAPClient; /** * Create an IMAP inbox object, shorthand for new IMAPClient. - * + * * @memberOf inbox * @param {Number} port IMAP server port to connect to * @param {String} host IMAP server hostname @@ -46,7 +46,7 @@ function createConnection(port, host, options){ /** * Create a XOAUTH login token generator - * + * * @memberOf inbox * @param {Object} options Options object, see {@see xoauth} */ @@ -56,7 +56,7 @@ function createXOAuthGenerator(options){ /** * Creates an IMAP connection object for communicating with the server - * + * * @constructor * @memberOf inbox * @param {Number} port IMAP server port to connect to @@ -70,32 +70,32 @@ function IMAPClient(port, host, options){ * Make this stream writeable. For future reference only, currently not needed */ this.writable = true; - + /** * Make this stream readable. Should be on by default though */ this.readable = true; - + /** * Options object for this instance */ this.options = options || {}; - + /** * Port to use for connecting to the server */ this.port = port || (this.options.secureConnection ? 993 : 143); - + /** * Server hostname */ this.host = host || "localhost"; - + /** * If set to true, print traffic between client and server to the console */ this.debug = !!this.options.debug; - + /** * XOAuth2 token generator if XOAUTH2 auth is used * @private @@ -113,7 +113,7 @@ function IMAPClient(port, host, options){ this._xoauth2 = xoauth2.createXOAuth2Generator(this.options.auth.XOAuth2); } } - + this._init(); } utillib.inherits(IMAPClient, Stream); @@ -161,72 +161,72 @@ IMAPClient.prototype._init = function(){ * Should the connection be over TLS or NET */ this.options.secureConnection = !!this.options.secureConnection; - + /** * Authentication details */ this.options.auth = this.options.auth || {user: "", pass:""}; - + /** * Connection socket to the server */ this._connection = false; - + /** * Is the connection currently in secure mode, changes with STARTTLS */ this._secureMode = !!this.options.secureConnection; - + /** * Current protocol state. */ this._currentState = this.states.NONE; - + /** - * Current stream mode for incoming data + * Current stream mode for incoming data */ this._currentMode = this.modes.COMMAND; - + /** * Expected remaining data length on stream data mode */ this._expectedDataLength = 0; - + /** * Data that was not part of the last command */ this._remainder = ""; - + /** * Counter for generating unique command tags */ this._tagCounter = 0; - + /** * Currently active command */ this._currentRequest = false; - + /** * Unprocessed commands */ this._commandQueue = []; - + /** * Server capabilities */ this._capabilities = []; - + /** * Are the capabilities updated */ this._updatedCapabilities = false; - + /** * Currently in idle */ this.idling = false; - + /** * Currently "nooping" when idle not available */ @@ -236,22 +236,22 @@ IMAPClient.prototype._init = function(){ * Waiting for idle start after issuing IDLE command */ this._idleWait = false; - + /** * Waiting for the idle to end */ this._idleEnd = false; - + /** * Timer to run NOOP when in idle */ this._idleTimer = false; - + /** * Timer for entering idle mode after other commands */ this._shouldIdleTimer = true; - + /** * If true check mail before entering idle */ @@ -261,42 +261,42 @@ IMAPClient.prototype._init = function(){ * Timeout to wait for a successful greeting from the server */ this._greetingTimeout = false; - + /** * Server ID */ this._serverId = {}; - + /** * Should the FETCH responses collected into an array */ this._collectMailList = false; - + /** * An array of collected FETCH responses */ this._mailList = []; - + /** * If set to true emit FETCH responses as new emails */ this._checkForNewMail = false; - + /** * Currently selected mailbox data */ this._selectedMailbox = {}; - + /** * Currently streaming possible literal values */ this._literalStreaming = false; - + /** * Message Stream object for streaming requested messages */ this._messageStream = false; - + /** * Literal handler */ @@ -306,37 +306,37 @@ IMAPClient.prototype._init = function(){ * Personal mailbox root */ this._rootPath = ""; - + /** * Delimiter for mailbox hierarchy */ this._mailboxDelimiter = "/"; - + /** * Default INBOX name */ this._inboxName = "INBOX"; - + /** * Default Sent folder name */ this._outgoingName = this.options.outgoingName || ""; - + /** * Active mailbox list */ this._mailboxList = []; - + /** * Is CONDSTORE enabled or not */ this._condstoreEnabled = false; - + /** * Ignore all incoming data while in TLS negotiations */ this._ignoreData = false; - + /** * Keep IMAP log for error trace */ @@ -351,17 +351,17 @@ IMAPClient.prototype._init = function(){ * Root mailbox */ this._rootMailbox = new Mailbox({client: this}); - + /** * Lineparser object to feed the incoming data to */ this.lineparser = new IMAPLineParser(); - + /** * Initially send the incoming data to greeting handler */ this._currentHandler = this._handlerGreeting; - + this.lineparser.on("line", this._onServerResponse.bind(this)); this.lineparser.on("log", this._onServerLog.bind(this, "S")); }; @@ -377,9 +377,9 @@ IMAPClient.prototype.connect = function(){ this._connection = net.connect(this.port, this.host); this._connection.on("connect", this._onConnect.bind(this)); } - + this._connection.on("error", this._onError.bind(this)); - + this._greetingTimeout = setTimeout(this._handleGreetingTimeout.bind(this), this.GREETING_TIMEOUT); }; @@ -387,7 +387,7 @@ IMAPClient.prototype.connect = function(){ /** * 'connect' event for the connection to the server. Setup other events when connected - * + * * @event */ IMAPClient.prototype._onConnect = function(){ @@ -397,11 +397,11 @@ IMAPClient.prototype._onConnect = function(){ }else if(this._connection.socket && "setKeepAlive" in this._connection.socket){ this._connection.socket.setKeepAlive(true); // secure connection } - + this._connection.on("data", this._onData.bind(this)); this._connection.on("close", this._onClose.bind(this)); this._connection.on("end", this._onEnd.bind(this)); - + }; /** @@ -409,7 +409,7 @@ IMAPClient.prototype._onConnect = function(){ * and if in COMMAND mode pass the line to the line parser and when in DATA * mode, pass it as a literal or stream if needed. If there's a remainder left from * the end of the line, rerun the function with it - * + * * @event * @param {Buffer} chunk incoming binary data chunk */ @@ -418,76 +418,76 @@ IMAPClient.prototype._onData = function(chunk){ // TLS negotiations going on, ignore everything received return; } - + var data = chunk && chunk.toString("binary") || "", line, match; - + if(this._remainder){ data = this._remainder + data; this._remainder = ""; } - + if(this._currentMode == this.modes.DATA){ if(this._expectedDataLength <= data.length){ - + if(this._expectedDataLength){ - + if(!this._literalStreaming){ this.lineparser.writeLiteral(data.substr(0, this._expectedDataLength)); }else{ this._messageStream.emit("data", new Buffer(data.substr(0, this._expectedDataLength), "binary")); } - + this._remainder = data.substr(this._expectedDataLength); this._expectedDataLength = 0; }else{ this._remainder = data; } - + if(this._literalStreaming){ this._messageStream.emit("end"); this._messageStream.removeAllListeners(); } - + this._currentMode = this.modes.COMMAND; - + return this._onData(); // rerun with the remainder }else{ - + if(!this._literalStreaming){ this.lineparser.writeLiteral(data); }else{ this._messageStream.emit("data", new Buffer(data, "binary")); } - + this._expectedDataLength -= data.length; return; } } - + if(this._currentMode == this.modes.COMMAND){ if((match = data.match(/\r?\n/))){ // find the line ending line = data.substr(0, match.index); this._remainder = data.substr(match.index + match[0].length) || ""; - + if(this.debug){ console.log("SERVER: "+line); } - + // check if the line ends with a literal notion if((match = line.match(/\{(\d+)\}\s*$/))){ this._expectedDataLength = Number(match[1]); this.lineparser.write(line); - + this._currentMode = this.modes.DATA; - + if(this._literalStreaming){ this.lineparser.writeLiteral(""); // create empty literal object } }else{ this.lineparser.end(line); } - + if(this._remainder){ return this._onData(); // rerun with the remainder } @@ -515,7 +515,7 @@ IMAPClient.prototype._onClose = function(){ */ IMAPClient.prototype._onEnd = function(){ this.emit("end"); - + if(this.debug){ console.log("EVENT: END"); } @@ -538,7 +538,7 @@ IMAPClient.prototype._onError = function(error){ * When the input command has been parsed pass it to the current command handler. * Basically there's just two - the initial greeting handler and universal * response router - * + * * @param {Array} data Parsed command, split into parameters */ IMAPClient.prototype._onServerResponse = function(data){ @@ -546,12 +546,12 @@ IMAPClient.prototype._onServerResponse = function(data){ }; /* - * Log IMAP commands into ._log array + * Log IMAP commands into ._log array * * @param {String} data IMAP command line */ IMAPClient.prototype._onServerLog = function(type, data){ - this._log.unshift((type?type + ": " :"") + (data || "").toString().trim()); + this._log.unshift((type?type + ": " :"") + (data || "").toString().trim()); if(this._log.length > this._logLength){ this._log.pop(); } @@ -561,12 +561,12 @@ IMAPClient.prototype._onServerLog = function(type, data){ * Run as the handler for the initial command coming from the server. If it * is a greeting with status OK, enter PREAUTH state and run CAPABILITY * command - * + * * @param {Array} data Parsed command */ IMAPClient.prototype._handlerGreeting = function(data){ clearTimeout(this._greetingTimeout); - + if(!data || !Array.isArray(data)){ throw new Error("Invalid input"); } @@ -579,10 +579,10 @@ IMAPClient.prototype._handlerGreeting = function(data){ this.close(); return; } - + this._currentState = this.states.PREAUTH; this._currentHandler = this._responseRouter; - + this._send("CAPABILITY", this._handlerTaggedCapability.bind(this)); }; @@ -599,20 +599,20 @@ IMAPClient.prototype._handleGreetingTimeout = function(){ /** * Checks the command data and routes it to the according handler - * + * * @param {Array} data Parsed command */ IMAPClient.prototype._responseRouter = function(data){ if(!data || !Array.isArray(data)){ return; } - + // Handle tagged commands if(this._currentRequest && this._currentRequest.tag == data[0]){ this._currentRequest.callback(data[1], data.slice(2)); return; } - + // handle commands tagged with + if(data[0]=="+"){ if(this._idleWait){ @@ -625,7 +625,7 @@ IMAPClient.prototype._responseRouter = function(data){ }else if(this._literalHandler){ this._literalHandler = null; } - + // handle untagged commands (tagged with *) if(data[0]=="*"){ switch(data[1]){ @@ -671,12 +671,12 @@ IMAPClient.prototype._responseRouter = function(data){ } return; } - + if(!isNaN(data[1]) && data[2] == "FETCH"){ this._handlerUntaggedFetch(data); return; } - + if(!isNaN(data[1]) && data[2] == "EXPUNGE"){ if(this._selectedMailbox.count){ this._selectedMailbox.count--; @@ -689,10 +689,10 @@ IMAPClient.prototype._responseRouter = function(data){ if(this.idling || this.nooping){ this._checkNewMail(); } - } + } return; } - + } }; @@ -700,7 +700,7 @@ IMAPClient.prototype._responseRouter = function(data){ /** * Prepend a tag for a command and put into command queue - * + * * @param {String} data Command to be sent to the server * @param {Function} [callback] Callback function to run when the command is completed * @param {Function} [prewrite] Function to run before the command is sent @@ -708,7 +708,7 @@ IMAPClient.prototype._responseRouter = function(data){ IMAPClient.prototype._send = function(data, callback, prewrite){ data = (data || "").toString(); var tag = "A" + (++this._tagCounter); - + this._commandQueue.push({tag: tag, data: tag + " " + data + "\r\n", callback: callback, prewrite: prewrite}); if(this.idling || !this._currentRequest){ @@ -720,7 +720,7 @@ IMAPClient.prototype._send = function(data, callback, prewrite){ * Send a command form the command queue to the server */ IMAPClient.prototype._processCommandQueue = function(){ - + if(!this._commandQueue.length || !this._connection){ return; } @@ -742,28 +742,28 @@ IMAPClient.prototype._processCommandQueue = function(){ } var command = this._commandQueue.shift(); - + if(typeof command.prewrite == "function"){ command.prewrite(); } - + this._onServerLog("C", command.data); this._connection.write(command.data); - + if(this.debug){ console.log("CLIENT: "+ (command.data || "").trim()); } - + this._currentRequest = { tag: command.tag, callback: (function(status, params){ - + clearTimeout(this._shouldIdleTimer); clearTimeout(this._idleTimer); if(!this.idling && !this._idleWait && this._currentState == this.states.SELECTED){ this._shouldIdleTimer = setTimeout(this.idle.bind(this), this.ENTER_IDLE); } - + if(typeof command.callback == "function"){ command.callback(status, params); } @@ -779,7 +779,7 @@ IMAPClient.prototype._processCommandQueue = function(){ /** * Handle tagged CAPABILITY. If in plaintext mode and STARTTLS is advertised, * run STARTTLS, otherwise report success to _postCapability() - * + * * @param {String} status If "OK" then the command succeeded */ IMAPClient.prototype._handlerTaggedCapability = function(status){ @@ -788,7 +788,7 @@ IMAPClient.prototype._handlerTaggedCapability = function(status){ this._send("STARTTLS", this._handlerTaggedStartTLS.bind(this)); return; } - + this._postCapability(); }else{ var error = new Error("Invalid capability response"); @@ -802,7 +802,7 @@ IMAPClient.prototype._handlerTaggedCapability = function(status){ /** * Handle tagged STARTTLS. If status is OK perform a TLS handshake and rerun * CAPABILITY on success. - * + * * @param {String} status If "OK" then the command succeeded */ IMAPClient.prototype._handlerTaggedStartTLS = function(status){ @@ -814,13 +814,13 @@ IMAPClient.prototype._handlerTaggedStartTLS = function(status){ this._ignoreData = false; this._secureMode = true; this._connection.on("data", this._onData.bind(this)); - + if("setKeepAlive" in this._connection){ this._connection.setKeepAlive(true); }else if(this._connection.socket && "setKeepAlive" in this._connection.socket){ this._connection.socket.setKeepAlive(true); // secure connection } - + this._send("CAPABILITY", this._handlerTaggedCapability.bind(this)); }).bind(this)); }else{ @@ -834,7 +834,7 @@ IMAPClient.prototype._handlerTaggedStartTLS = function(status){ /** * Handle LOGIN response. If status is OK, consider the user logged in. - * + * * @param {String} status If "OK" then the command succeeded */ IMAPClient.prototype._handlerTaggedLogin = function(status){ @@ -868,10 +868,10 @@ IMAPClient.prototype._handlerTaggedLogin = function(status){ }; /** - * Handle ID command. We don't reaaly care if the ID succeeded or + * Handle ID command. We don't reaaly care if the ID succeeded or * not as it is just some informational data. If it failed we still might be * able to access the mailbox - * + * * @param {String} status If "OK" then the command succeeded */ IMAPClient.prototype._handlerTaggedId = function(status){ @@ -879,10 +879,10 @@ IMAPClient.prototype._handlerTaggedId = function(status){ }; /** - * Handle CONDSTORE command. We don't reaaly care if the CONDSTORE succeeded or + * Handle CONDSTORE command. We don't reaaly care if the CONDSTORE succeeded or * not as it is just some informational data. If it failed we still might be * able to access the mailbox - * + * * @param {String} status If "OK" then the command succeeded */ IMAPClient.prototype._handlerTaggedCondstore = function(status){ @@ -917,7 +917,7 @@ IMAPClient.prototype._handlerTaggedCondstore = function(status){ /** * Handle mailbox listing with LSUB - * + * * @param {String} status If "OK" then the command succeeded */ IMAPClient.prototype._handlerTaggedLsub = function(xinfo, callback, status){ @@ -927,29 +927,29 @@ IMAPClient.prototype._handlerTaggedLsub = function(xinfo, callback, status){ } return; } - + var curXinfo, curName; - + for(var i=0, len = xinfo.length; i=0){ curName = "INBOX"; } - + for(var j=0, jlen = this._mailboxList.length; j=0){ this._mailboxList[j].disabled = true; } - + if(curXinfo.tags.indexOf("\\Inbox")>=0){ this._mailboxList[j].type = "Inbox"; }else if(curXinfo.tags.indexOf("\\All")>=0 || curXinfo.tags.indexOf("\\AllMail")>=0){ @@ -967,12 +967,12 @@ IMAPClient.prototype._handlerTaggedLsub = function(xinfo, callback, status){ }else if(curXinfo.tags.indexOf("\\Trash")>=0){ this._mailboxList[j].type = "Trash"; } - + break; } } } - + if(typeof callback == "function"){ callback(null, this._mailboxList); } @@ -981,8 +981,8 @@ IMAPClient.prototype._handlerTaggedLsub = function(xinfo, callback, status){ /** * Handle SELECT and EXAMINE commands. If succeeded, move to SELECTED state. * If callback is set runs it with selected mailbox data - * - * + * + * * @param {Function} callback Callback function to run on completion * @param {String} status If "OK" then the command succeeded * @params {Array} params Parsed params excluding tag and SELECT @@ -990,7 +990,7 @@ IMAPClient.prototype._handlerTaggedLsub = function(xinfo, callback, status){ IMAPClient.prototype._handlerTaggedSelect = function(callback, status, params){ if(status == "OK"){ this._currentState = this.states.SELECTED; - + if(Array.isArray(params) && params[0] && params[0].params){ if(params[0].params[0] == "READ-WRITE"){ this._selectedMailbox.readOnly = false; @@ -1025,7 +1025,7 @@ IMAPClient.prototype._handlerTaggedSelect = function(callback, status, params){ /** * Handle untagged CAPABILITY response, store params to _capabilities array - * + * * @param {Array} list Params for "* CAPABILITY" as an array */ IMAPClient.prototype._handlerUntaggedCapability = function(list){ @@ -1035,11 +1035,11 @@ IMAPClient.prototype._handlerUntaggedCapability = function(list){ /** * Handle untagged ID response. - * + * * @param {Array} list Params */ IMAPClient.prototype._handlerUntaggedId = function(list){ - list = (list || [])[0] || []; + list = (list || [])[0] || []; var key; for(var i=0, len = list.length; i=0){ mailbox.hasChildren = true; } - + if(name == "INBOX"){ mailbox.type="Inbox"; } - + this._mailboxList.push(mailbox); }; @@ -1139,19 +1139,19 @@ IMAPClient.prototype._handlerUntaggedSearch = function(list){ /** * Handle untagged FETCH responses, these have data about individual messages. - * + * * @param {Array} list Params about a message */ IMAPClient.prototype._handlerUntaggedFetch = function(list){ - var envelope = (list || [])[3] || [], + var envelope = (list || [])[3] || [], envelopeData = this._formatEnvelope(envelope), nextUID = Number(this._selectedMailbox.UIDNext) || 0, currentUID = Number(envelopeData.UID) || 0; - + if(!nextUID || nextUID <= currentUID){ - this._selectedMailbox.UIDNext = currentUID+1; + this._selectedMailbox.UIDNext = currentUID + 1; } - + if(this._collectMailList){ this._mailList.push(envelopeData); } @@ -1201,7 +1201,7 @@ IMAPClient.prototype._postCapability = function(){ error: E.message }; } - + if(['400', '401'].indexOf(data.status)>=0){ this._xoauth2RetryCount = (this._xoauth2RetryCount || 0) + 1; } @@ -1246,7 +1246,7 @@ IMAPClient.prototype._postAuth = function(){ /** * Run it when all the required jobs for setting up an authorized connection * are completed. Emit 'connect' event. - * + * * @param {Object} err Error object, if an error appeared */ IMAPClient.prototype._postReady = function(err){ @@ -1261,7 +1261,7 @@ IMAPClient.prototype._postReady = function(err){ /** * Escapes a string and encloses it with double quotes. - * + * * @param {String} str String to escape */ IMAPClient.prototype._escapeString = function(str){ @@ -1270,7 +1270,7 @@ IMAPClient.prototype._escapeString = function(str){ /** * Format envelope object from (FLAGS ENVELOPE) response object - * + * * @param {Array} envelopeData An array with FLAGS and ENVELOPE response data * @return {Object} structured envelope data */ @@ -1278,27 +1278,27 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ if(!Array.isArray(envelopeData)){ return null; } - - var dataObject = {}, lastKey = false, headers; - - for(var i=0, len = envelopeData.length; i 1){ message.folders = [message.folders]; }else if(message.folders.length){ @@ -1387,7 +1386,7 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ // remove duplicates if(message.folders){ var folderList = []; - for(var i=0, len=message.folders.length; i=0 && messageTypes[0] || "Normal"; + message.type = messageTypes.length && messageTypePreferenceOrder.indexOf(messageTypes[0])>=0 && messageTypes[0] || "Normal"; if(message.type == "Normal" && message.flags && message.flags.indexOf("\\Flagged")>=0){ message.type = "Starred"; } if(dataObject.ENVELOPE){ message.date = new Date(dataObject.ENVELOPE[0] || Date.now()); - + message.title = (dataObject.ENVELOPE[1] || "").toString(). - replace(/\=\?[^?]+\?[QqBb]\?[^?]+\?=/g, + replace(/\=\?[^?]+\?[QqBb]\?[^?]+\?=/g, function(mimeWord){ return mimelib.decodeMimeWord(mimeWord); }); @@ -1434,7 +1433,7 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ message.from = message.from[0]; } } - + if(dataObject.ENVELOPE[5] && dataObject.ENVELOPE[5].length){ message.to = dataObject.ENVELOPE[5].map(this._formatEnvelopeAddress); } @@ -1445,10 +1444,10 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ if(dataObject.ENVELOPE[8] && dataObject.ENVELOPE[8].length){ message.inReplyTo = (dataObject.ENVELOPE[8] || "").toString().replace(/\s/g,""); } - + message.messageId = (dataObject.ENVELOPE[9] || "").toString().toString().replace(/\s/g,""); } - + if(dataObject.BODY && (dataObject.BODY = (dataObject.BODY || "").toString().trim())){ headers = mimelib.parseHeaders(dataObject.BODY); if(headers.references){ @@ -1485,20 +1484,20 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ /** * Formats an IMAP ENVELOPE address in simpler {name, address} format - * + * * @param {Array} address IMAP ENVELOPE address array [name, smtp route, user, domain] * @return {Object} simple {name, address} format */ IMAPClient.prototype._formatEnvelopeAddress = function(address){ var name = address[0], email = (address[2] || "") + "@" + (address[3] || ""); - + if(email == "@"){ email = ""; } - + return { - name: (name || email).replace(/\=\?[^?]+\?[QqBb]\?[^?]+\?=/g, + name: (name || email).replace(/\=\?[^?]+\?[QqBb]\?[^?]+\?=/g, function(mimeWord){ return mimelib.decodeMimeWord(mimeWord); }), @@ -1514,7 +1513,7 @@ IMAPClient.prototype._convertFromUTF7 = function(str){ str = new Buffer(str, "binary").toString("utf-8"); try{ return str.replace(/&([^\-]+)\-/g, function(a, r){ - r = (r || "").replace(/,/g, "/"); + r = (r || "").replace(/,/g, "/"); return fromUTF7.convert(new Buffer(r, "base64")).toString("utf-8"); }); }catch(E){ @@ -1529,12 +1528,12 @@ IMAPClient.prototype._checkNewMail = function(){ if(isNaN(this._selectedMailbox.UIDNext)){ return; } - + this._send("UID FETCH "+this._selectedMailbox.UIDNext+":* (FLAGS ENVELOPE BODY[HEADER.FIELDS (REFERENCES THREAD-INDEX)]" +(this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"")+ ")", (function(){ this._checkForNewMail = false; - }).bind(this), + }).bind(this), (function(){ - this._checkForNewMail = true; + this._checkForNewMail = true; }).bind(this)); }; @@ -1542,7 +1541,7 @@ IMAPClient.prototype._checkNewMail = function(){ /** * Lists root mailboxes - * + * * @param {Function} callback Callback function to run with the mailbox list */ IMAPClient.prototype.listMailboxes = function(callback){ @@ -1551,36 +1550,36 @@ IMAPClient.prototype.listMailboxes = function(callback){ /** * Opens a selected mailbox. This is needed before you can open any message. - * + * * @param {String} path Mailbox full path, ie "INBOX/Sent Items" * @param {Object} [options] Optional options object * @param {Boolean} [options.readOnly] If set to true, open the mailbox in read-only mode (seen/unseen flags won't be touched) - * @param {Function} callback Callback function to run when the mailbox is opened + * @param {Function} callback Callback function to run when the mailbox is opened */ IMAPClient.prototype.openMailbox = function(path, options, callback){ var command = "SELECT"; - + if(typeof options == "function" && !callback){ callback = options; options = undefined; } - + options = options || {}; - + if(options.readOnly){ command = "EXAMINE"; } - + if(typeof path == "object"){ path = path.path; } - + path = path || this._inboxName || "INBOX"; - + this._selectedMailbox = { path: path }; - + this._send(command + " " + this._escapeString(path)+( this._condstoreEnabled?" (CONDSTORE)":"" ), this._handlerTaggedSelect.bind(this, callback)); @@ -1588,7 +1587,7 @@ IMAPClient.prototype.openMailbox = function(path, options, callback){ /** * Returns the current mailbox data object - * + * * @return {Object} Information about currently selected mailbox */ IMAPClient.prototype.getCurrentMailbox = function(){ @@ -1598,38 +1597,38 @@ IMAPClient.prototype.getCurrentMailbox = function(){ /** * Lists message envelopes for selected range. Negative numbers can be used to * count from the end of the list (most recent messages). - * + * * @param {Number} from List from position (0 based) * @param {Number} limit How many messages to fetch, defaults to all from selected position - * @param {Function} callback Callback function to run with the listed envelopes + * @param {Function} callback Callback function to run with the listed envelopes */ IMAPClient.prototype.listMessages = function(from, limit, callback){ var to; - + from = Number(from) || 0; - + if(typeof limit == "function" && !callback){ - callback = limit; + callback = limit; limit = undefined; } - + limit = Number(limit) || 0; - + if(this._currentState != this.states.SELECTED){ if(typeof callback == "function"){ callback(new Error("No mailbox selected")); } return; } - + if(from < 0){ from = this._selectedMailbox.count + from; } - + if(from < 0){ from = 0; } - + if(limit){ to = from + limit; }else{ @@ -1637,16 +1636,16 @@ IMAPClient.prototype.listMessages = function(from, limit, callback){ } from++; - + this._collectMailList = true; this._mailList = []; this._send("FETCH "+from+":"+to+" (UID FLAGS ENVELOPE BODY[HEADER.FIELDS (REFERENCES THREAD-INDEX)]" +(this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"")+ ")", (function(status){ this._collectMailList = false; - + if(typeof callback != "function"){ return; } - + if(status == "OK"){ callback(null, this._mailList); }else{ @@ -1655,9 +1654,70 @@ IMAPClient.prototype.listMessages = function(from, limit, callback){ }).bind(this)); }; +/** + * Lists flags for selected range. Negative numbers can be used to + * count from the end of the list (most recent messages). + * + * @param {Number} from List from position (0 based) + * @param {Number} limit How many messages to fetch, defaults to all from selected position + * @param {Function} callback Callback function to run with the listed envelopes + */ +IMAPClient.prototype.listFlags = function(from, limit, callback){ + var to; + + from = Number(from) || 0; + + if(typeof limit == "function" && !callback){ + callback = limit; + limit = undefined; + } + + limit = Number(limit) || 0; + + if(this._currentState != this.states.SELECTED){ + if(typeof callback == "function"){ + callback(new Error("No mailbox selected")); + } + return; + } + + if(from < 0){ + from = this._selectedMailbox.count + from; + } + + if(from < 0){ + from = 0; + } + + if(limit){ + to = from + limit; + }else{ + to = "*"; + } + + from++; + + this._collectMailList = true; + this._mailList = []; + this._send("FETCH "+from+":"+to+" (UID FLAGS)", (function(status){ + this._collectMailList = false; + + if(typeof callback != "function"){ + return; + } + + if(status == "OK"){ + callback(null, this._mailList); + }else{ + callback(new Error("Error fetching list")); + } + }).bind(this)); +}; + + /** * Updates flags for selected message - * + * * @param {Number} uid Message identifier * @param {Array} flags Flags to set for a message * @param {String} [updateType=""] If empty, replace flags; + add flag; - remove flag @@ -1665,44 +1725,44 @@ IMAPClient.prototype.listMessages = function(from, limit, callback){ */ IMAPClient.prototype.updateFlags = function(uid, flags, updateType, callback){ uid = Number(uid) || 0; - flags = [].concat(flags || []); - + flags = [].concat(flags || []); + if(!callback && typeof updateType == "function"){ callback = updateType; updateType = undefined; } - - updateType = (updateType || "").toString().trim(); - + + updateType = (updateType || "").toString().trim(); + if(!uid){ if(typeof callback == "function"){ callback(new Error("Invalid UID value")); } return; } - + if(!Array.isArray(flags)){ if(typeof callback == "function"){ callback(new Error("Invalid flags value")); } return; } - + if(this._currentState != this.states.SELECTED){ if(typeof callback == "function"){ callback(new Error("No mailbox selected")); } return; } - - this._send("UID STORE "+uid+":"+uid+" "+updateType+"FLAGS (" + flags.join(" ") + ")", + + this._send("UID STORE "+uid+":"+uid+" "+updateType+"FLAGS (" + flags.join(" ") + ")", (function(status){ this._collectMailList = false; - + if(typeof callback != "function"){ return; } - + if(typeof callback == "function"){ if(status == "OK"){ if(!this._mailList.length){ @@ -1714,7 +1774,7 @@ IMAPClient.prototype.updateFlags = function(uid, flags, updateType, callback){ callback(new Error("Error fetching message data")); } } - + }).bind(this), (function(){ this._collectMailList = true; @@ -1724,58 +1784,58 @@ IMAPClient.prototype.updateFlags = function(uid, flags, updateType, callback){ /** * Add flags for selected message - * + * * @param {Number} uid Message identifier * @param {Array} flags Flags to set for a message * @param {Function} callback Callback function to run, returns an array of flags */ IMAPClient.prototype.addFlags = function(uid, flags, callback){ - flags = [].concat(flags || []); + flags = [].concat(flags || []); this.updateFlags(uid, flags, "+", callback); }; /** * Removes flags for selected message - * + * * @param {Number} uid Message identifier * @param {Array} flags Flags to remove from a message * @param {Function} callback Callback function to run, returns an array of flags */ IMAPClient.prototype.removeFlags = function(uid, flags, callback){ - flags = [].concat(flags || []); + flags = [].concat(flags || []); this.updateFlags(uid, flags, "-", callback); }; /** * Fetches flags for selected message - * + * * @param {Number} uid Message identifier * @param {Function} callback Callback function to run with the flags array */ IMAPClient.prototype.fetchFlags = function(uid, callback){ uid = Number(uid) || 0; - + if(!uid){ if(typeof callback == "function"){ callback(new Error("Invalid UID value")); } return; } - + if(this._currentState != this.states.SELECTED){ if(typeof callback == "function"){ callback(new Error("No mailbox selected")); } return; } - + this._send("UID FETCH "+uid+":"+uid+" (FLAGS)", (function(status){ this._collectMailList = false; - + if(typeof callback != "function"){ return; } - + if(typeof callback == "function"){ if(status == "OK"){ if(!this._mailList.length){ @@ -1787,7 +1847,7 @@ IMAPClient.prototype.fetchFlags = function(uid, callback){ callback(new Error("Error fetching message flags")); } } - + }).bind(this), (function(){ this._collectMailList = true; @@ -1797,34 +1857,34 @@ IMAPClient.prototype.fetchFlags = function(uid, callback){ /** * Fetches envelope object for selected message - * + * * @param {Number} uid Message identifier * @param {Function} callback Callback function to run with the envelope object */ IMAPClient.prototype.fetchData = function(uid, callback){ uid = Number(uid) || 0; - + if(!uid){ if(typeof callback == "function"){ callback(new Error("Invalid UID value")); } return; } - + if(this._currentState != this.states.SELECTED){ if(typeof callback == "function"){ callback(new Error("No mailbox selected")); } return; } - + this._send("UID FETCH "+uid+":"+uid+" (FLAGS ENVELOPE BODY[HEADER.FIELDS (REFERENCES THREAD-INDEX)]" +(this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"")+ ")", (function(status){ this._collectMailList = false; - + if(typeof callback != "function"){ return; } - + if(typeof callback == "function"){ if(status == "OK"){ if(!this._mailList.length){ @@ -1836,7 +1896,7 @@ IMAPClient.prototype.fetchData = function(uid, callback){ callback(new Error("Error fetching message data")); } } - + }).bind(this), (function(){ this._collectMailList = true; @@ -1846,24 +1906,24 @@ IMAPClient.prototype.fetchData = function(uid, callback){ /** * Creates a Readable Stream for a selected message. - * + * * @param {Number} uid Message identifier */ IMAPClient.prototype.createMessageStream = function(uid){ var stream = new Stream(); - + uid = Number(uid) || 0; - + if(!uid){ process.nextTick(this.emit.bind(this, new Error("Invalid UID value"))); return; } - + if(this._currentState != this.states.SELECTED){ process.nextTick(this.emit.bind(this, new Error("No inbox selected"))); return; } - + this._send("UID FETCH "+uid+":"+uid+" BODY[]", (function(status){ this._collectMailList = false; this._literalStreaming = false; @@ -1875,9 +1935,9 @@ IMAPClient.prototype.createMessageStream = function(uid){ stream.emit("error", new Error("Error fetching message: "+uid+"; "+this.port+"; "+this.host+"; "+JSON.stringify(this._selectedMailbox))); } } - + this._messageStream = null; - + }).bind(this), (function(){ this._collectMailList = true; @@ -1885,7 +1945,7 @@ IMAPClient.prototype.createMessageStream = function(uid){ this._mailList = []; this._messageStream = stream; }).bind(this)); - + return stream; }; @@ -1905,14 +1965,14 @@ IMAPClient.prototype.copyMessage = function(uid, destination, callback){ } return; } - + this._send("UID COPY " + uid + " " + this._escapeString(destination), (function(status){ if(status != "OK"){ return callback(new Error("Error copying message")); } return callback(null, true); }).bind(this)); -} +}; /** * Delete message from the active mailbox @@ -1943,7 +2003,7 @@ IMAPClient.prototype.deleteMessage = function(uid, callback){ }).bind(this)); }).bind(this)); -} +}; /** * Move message from the active mailbox to the end of destination mailbox @@ -1962,7 +2022,7 @@ IMAPClient.prototype.moveMessage = function(uid, destination, callback){ return callback(null, !error); }); }).bind(this)); -} +}; /** * Upload a message to the mailbox @@ -1982,7 +2042,7 @@ IMAPClient.prototype.storeMessage = function(message, flags, callback){ message = new Buffer(message, "utf-8"); } - flags = [].concat(flags || []); + flags = [].concat(flags || []); this._send("APPEND " + this._escapeString(this._selectedMailbox.path) + (flags.length ? " (" + flags.join(" ")+")":"") + " {" + message.length+"}", (function(status, data){ this._literalHandler = null; if(status == "OK"){ @@ -2064,11 +2124,11 @@ IMAPClient.prototype.close = function(){ } var socket = this._connection.socket || this._connection; - + if(socket && !socket.destroyed){ socket.destroy(); } - + this._connection = false; this._commandQueue = []; clearTimeout(this._shouldIdleTimer); From 91a6d4ddb8e49d54f7e893bc37adfc9956ddb6c8 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 17 Dec 2012 12:10:58 +0200 Subject: [PATCH 041/106] Bump version --- README.md | 37 +++++++++++++++++++++++++++++++++++++ examples/list.js | 8 +++++++- package.json | 2 +- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7b18edb..8074b86 100644 --- a/README.md +++ b/README.md @@ -260,6 +260,43 @@ Example output for a message listing in the message object - for example, if there are no "cc:" addresses listed, there is no "cc" field in the message object +### Listing flags + +As a shorthand listing, you can also list only UID and Flags pairs + + client.listFlags(from[, limit], callback) + +Where + + * **from** is the index of the first message (0 based), you can use negative numbers to count from the end (-10 indicates the 10 last messages) + * **limit** defines the maximum count of messages to fetch, if not set or 0 all messages from the starting position will be included + * **callback** *(error, messages)* is the callback function to run with the message array + +Example + + // list flags for newest 10 messages + client.listFlags(-10, function(err, messages){ + messages.forEach(function(message){ + console.log(message.UID, message.flags); + }); + }); + +Example output for a message listing + + [ + { + // if uidvalidity changes, all uid values are void! + UIDValidity: '664399135', + + // uid value of the message + UID: 52, + + // message flags (Array) + flags: [ '\\Flagged', '\\Seen' ] + }, + ... + ] + ### Fetch message details To fetch message data (flags, title, etc) for a specific message, use diff --git a/examples/list.js b/examples/list.js index 7aafd0f..c0950c0 100644 --- a/examples/list.js +++ b/examples/list.js @@ -7,7 +7,7 @@ var client = inbox.createConnection(false, "imap.gmail.com", { user: "test.nodemailer@gmail.com", pass: "Nodemailer123" }, - debug: false + debug: true }); client.connect(); @@ -24,6 +24,12 @@ client.on("connect", function(){ messages.forEach(function(message){ console.log(message.UID+": "+message.title); }); + + client.listFlags(-10, function(err, messages){ + messages.forEach(function(message){ + console.log(message); + }); + }); }); }); diff --git a/package.json b/package.json index 2d1bb36..6efeb89 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.27", + "version": "0.1.28", "author" : "Andris Reinman", "maintainers":[ { From 68f6fa0b61a3480df646806438f8da4a0d38eb91 Mon Sep 17 00:00:00 2001 From: mikemee Date: Fri, 8 Mar 2013 09:40:24 -0800 Subject: [PATCH 042/106] minor wording editing, color code javascript --- README.md | 133 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 85 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 8074b86..edd1d3a 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,15 @@ Install from npm **NB!** This API is preliminary and may change. Use **inbox** module - +```javascript var inbox = require("inbox"); - +``` ### Create new IMAP connection Create connection object with - +```javascript inbox.createConnection(port, host, options) +``` where @@ -44,7 +45,7 @@ where * **options.clientId.name** is is the name param etc. see [rfc 2971](http://tools.ietf.org/html/rfc2971#section-3.3) for possible field names Example: - +```javascript var client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, auth:{ @@ -52,9 +53,10 @@ Example: pass: "Nodemailer123" } }); +``` -Or when login with XOAUTH2 (see examples/xoauth2) - +Or for login with XOAUTH2 (see examples/xoauth2) +```javascript // XOAUTH2 var client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, @@ -69,11 +71,12 @@ Or when login with XOAUTH2 (see examples/xoauth2) } } }); +``` -Or when login with XOAUTH (see examples/xoauth-3lo.js and examples/xoauth-2lo.js) +Or for login with XOAUTH (see examples/xoauth-3lo.js and examples/xoauth-2lo.js) - +```javascript // 3-legged- oauth var client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, @@ -85,9 +88,10 @@ Or when login with XOAUTH (see examples/xoauth-3lo.js and examples/xoauth-2lo.js }) } }); +``` -With 3-legged OAuth, consumerKey and consumerSecret both default to "anonymous" while with 2-legged they need to have proper values. - +With 2-legged OAuth, consumerKey and consumerSecret need to have proper values, vs 3-legged OAuth where both default to "anonymous". +```javascript // 2-legged- oauth var client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, @@ -100,22 +104,26 @@ With 3-legged OAuth, consumerKey and consumerSecret both default to "anonymous" }) } }); +``` Once the connection object has been created, use connect() to create the actual connection. - +```javascript client.connect(); +``` When the connection has been successfully established a 'connect' event is emitted. - +```javascript client.on("connect", function(){ console.log("Successfully connected to server"); }); +``` ### List available mailboxes To list the available mailboxes use - +```javascript client.listMailboxes(callback) +``` Where @@ -135,7 +143,7 @@ Additionally mailboxes have the following methods * **listChildren** *(callback)* - if the mailbox has children (*hasChildren* is true), lists the child mailboxes Example: - +```javascript client.listMailboxes(function(error, mailboxes){ for(var i=0, len = mailboxes.length; i Date: Thu, 2 May 2013 13:53:37 +0800 Subject: [PATCH 043/106] err is string when oauth server responsed invalid_grant --- lib/client.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/lib/client.js b/lib/client.js index 3437798..1d8f484 100644 --- a/lib/client.js +++ b/lib/client.js @@ -850,9 +850,14 @@ IMAPClient.prototype._handlerTaggedLogin = function(status){ if(this._xoauth2 && this._xoauth2RetryCount && this._xoauth2RetryCount<3){ this._xoauth2.generateToken((function(err, token){ if(err){ - err.errorType = "AuthenticationError"; - err.errorLog = this._log.slice(0, this._logLength); - this.emit("error", err); + if(typeof err != "object"){ + var error = new Error(err.toString()); + }else{ + error = err; + } + error.errorType = "AuthenticationError"; + error.errorLog = this._log.slice(0, this._logLength); + this.emit("error", error); return; } this._postCapability(); @@ -1183,9 +1188,14 @@ IMAPClient.prototype._postCapability = function(){ if(this._capabilities.indexOf("AUTH=XOAUTH2")>=0 && this._xoauth2){ this._xoauth2.getToken((function(err, token){ if(err){ - err.errorType = "AuthenticationError"; - err.errorLog = this._log.slice(0, this._logLength); - this.emit("error", err); + if(typeof err != "object"){ + var error = new Error(err.toString()); + }else{ + error = err; + } + error.errorType = "AuthenticationError"; + error.errorLog = this._log.slice(0, this._logLength); + this.emit("error", error); return; } this._send("AUTHENTICATE XOAUTH2 " + token, From ceda9f4a0c85f71f9e764ef6d2c72f6cf3ab1001 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Wed, 8 May 2013 15:33:36 +0300 Subject: [PATCH 044/106] Do not ask for references and thread-index --- lib/client.js | 41 +++++------------------------------------ 1 file changed, 5 insertions(+), 36 deletions(-) diff --git a/lib/client.js b/lib/client.js index 1d8f484..1951d9d 100644 --- a/lib/client.js +++ b/lib/client.js @@ -853,7 +853,7 @@ IMAPClient.prototype._handlerTaggedLogin = function(status){ if(typeof err != "object"){ var error = new Error(err.toString()); }else{ - error = err; + error = err; } error.errorType = "AuthenticationError"; error.errorLog = this._log.slice(0, this._logLength); @@ -1191,7 +1191,7 @@ IMAPClient.prototype._postCapability = function(){ if(typeof err != "object"){ var error = new Error(err.toString()); }else{ - error = err; + error = err; } error.errorType = "AuthenticationError"; error.errorLog = this._log.slice(0, this._logLength); @@ -1458,37 +1458,6 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ message.messageId = (dataObject.ENVELOPE[9] || "").toString().toString().replace(/\s/g,""); } - if(dataObject.BODY && (dataObject.BODY = (dataObject.BODY || "").toString().trim())){ - headers = mimelib.parseHeaders(dataObject.BODY); - if(headers.references){ - message.references = [].concat.apply([], headers.references.map(function(row){ - return row.split(/>\s*]/g,"")+">"; - }); - })); - } - - if(headers['thread-index'] && Array.isArray(headers['thread-index'])){ - message.threadIndex = [].concat.apply([], headers['thread-index'].map(function(row){ - - var thread = new Buffer(row, "base64"), - threads = [thread.slice(0, 22).toString("hex")], - pos = 22, i = 0; - - while(pos < thread.length){ - threads.push(threads[i++] + thread.slice(pos, pos + 5 < thread.length ? pos + 5 : thread.length).toString("hex")); - pos += 5; - } - - threads = threads.map(function(id){ - return new Buffer(id, "hex").toString("base64"); - }); - - return threads; - })); - } - } - return message; }; @@ -1539,7 +1508,7 @@ IMAPClient.prototype._checkNewMail = function(){ return; } - this._send("UID FETCH "+this._selectedMailbox.UIDNext+":* (FLAGS ENVELOPE BODY[HEADER.FIELDS (REFERENCES THREAD-INDEX)]" +(this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"")+ ")", (function(){ + this._send("UID FETCH "+this._selectedMailbox.UIDNext+":* (FLAGS ENVELOPE" +(this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"")+ ")", (function(){ this._checkForNewMail = false; }).bind(this), (function(){ @@ -1649,7 +1618,7 @@ IMAPClient.prototype.listMessages = function(from, limit, callback){ this._collectMailList = true; this._mailList = []; - this._send("FETCH "+from+":"+to+" (UID FLAGS ENVELOPE BODY[HEADER.FIELDS (REFERENCES THREAD-INDEX)]" +(this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"")+ ")", (function(status){ + this._send("FETCH "+from+":"+to+" (UID FLAGS ENVELOPE" +(this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"")+ ")", (function(status){ this._collectMailList = false; if(typeof callback != "function"){ @@ -1888,7 +1857,7 @@ IMAPClient.prototype.fetchData = function(uid, callback){ return; } - this._send("UID FETCH "+uid+":"+uid+" (FLAGS ENVELOPE BODY[HEADER.FIELDS (REFERENCES THREAD-INDEX)]" +(this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"")+ ")", (function(status){ + this._send("UID FETCH "+uid+":"+uid+" (FLAGS ENVELOPE" +(this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"")+ ")", (function(status){ this._collectMailList = false; if(typeof callback != "function"){ From 89eef285035570840a9a477eb0524a4403a8704b Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Wed, 8 May 2013 15:33:54 +0300 Subject: [PATCH 045/106] bumped version --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 6efeb89..75c2c61 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.28", + "version": "0.1.29", "author" : "Andris Reinman", "maintainers":[ { @@ -8,19 +8,19 @@ "email":"andris@node.ee" } ], - + "main": "lib/client.js", - + "dependencies": { "mimelib": "*", "iconv": "*", "xoauth2": "*" }, - + "devDependencies": { "nodeunit": "*" }, - + "scripts":{ "test": "nodeunit test" } From 49b5ca151d0805e089454c6d8f0e2aec9c0ffb7a Mon Sep 17 00:00:00 2001 From: alexperezpaya Date: Fri, 10 May 2013 21:01:19 +0200 Subject: [PATCH 046/106] [Implementation] Custom Request App Name and URL --- README.md | 17 +++++++++++++++++ lib/client.js | 37 +++++++++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index edd1d3a..a830522 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,10 @@ Example: auth:{ user: "test.nodemailer@gmail.com", pass: "Nodemailer123" + }, + client: { + name: 'nodeapp', + url: 'http://herewesellcats.com' } }); ``` @@ -118,6 +122,19 @@ When the connection has been successfully established a 'connect' event is emitt }); ``` +### Logout and disconnect + +Logout from IMAP and close NET connection. + +```javascript + client.close(); + + client.on('close', function (){ + console.log('DISCONNECTED!'); + }); + +``` + ### List available mailboxes To list the available mailboxes use diff --git a/lib/client.js b/lib/client.js index 1951d9d..658afcd 100644 --- a/lib/client.js +++ b/lib/client.js @@ -41,6 +41,16 @@ module.exports.IMAPClient = IMAPClient; * @param {Object} options Options object for authentication etc. */ function createConnection(port, host, options){ + if(options && options.client){ + if(options.client.name){ + X_CLIENT_NAME = options.client.name; + } + + if(options.client.url){ + X_CLIENT_URL = options.client.url; + } + + } return new IMAPClient(port, host, options); } @@ -2101,17 +2111,24 @@ IMAPClient.prototype.close = function(){ if(!this._connection){ return; } - - var socket = this._connection.socket || this._connection; - - if(socket && !socket.destroyed){ - socket.destroy(); - } - - this._connection = false; - this._commandQueue = []; clearTimeout(this._shouldIdleTimer); clearTimeout(this._idleTimer); clearTimeout(this._greetingTimeout); - this.emit("close"); + + var that = this; + this._send('LOGOUT', function (){ + var socket = that._connection.socket || that._connection; + + if(socket && !socket.destroyed){ + socket.destroy(); + } + + if(that.debug){ + console.log('CLOSED CONNECTION'); + } + + that._connection = false; + that._commandQueue = []; + that.emit("close"); + }); }; From 8fb34734c95cf93c857ea72a04c921a48dabb3c8 Mon Sep 17 00:00:00 2001 From: alexperezpaya Date: Fri, 10 May 2013 21:40:15 +0200 Subject: [PATCH 047/106] Working Logout and Disconnect --- README.md | 4 ---- lib/client.js | 10 ---------- 2 files changed, 14 deletions(-) diff --git a/README.md b/README.md index a830522..5af09c0 100644 --- a/README.md +++ b/README.md @@ -51,10 +51,6 @@ Example: auth:{ user: "test.nodemailer@gmail.com", pass: "Nodemailer123" - }, - client: { - name: 'nodeapp', - url: 'http://herewesellcats.com' } }); ``` diff --git a/lib/client.js b/lib/client.js index 658afcd..98a3e0b 100644 --- a/lib/client.js +++ b/lib/client.js @@ -41,16 +41,6 @@ module.exports.IMAPClient = IMAPClient; * @param {Object} options Options object for authentication etc. */ function createConnection(port, host, options){ - if(options && options.client){ - if(options.client.name){ - X_CLIENT_NAME = options.client.name; - } - - if(options.client.url){ - X_CLIENT_URL = options.client.url; - } - - } return new IMAPClient(port, host, options); } From 6192f0e17485ce11614ff44ba60303c4fe8cc51b Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Sat, 11 May 2013 23:20:09 +0300 Subject: [PATCH 048/106] update list test --- examples/list.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/examples/list.js b/examples/list.js index c0950c0..86e6ee3 100644 --- a/examples/list.js +++ b/examples/list.js @@ -29,6 +29,7 @@ client.on("connect", function(){ messages.forEach(function(message){ console.log(message); }); + client.close(); }); }); }); @@ -39,6 +40,10 @@ client.on("connect", function(){ console.log(util.inspect(message, false, 7)); client.createMessageStream(message.UID).pipe(process.stdout, {end: false}); + + client.on('close', function (){ + console.log('DISCONNECTED!'); + }); }); }); From f3edbca4128c669ccad9ea0550d01c5f6c391e08 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 13 May 2013 14:49:46 +0300 Subject: [PATCH 049/106] added ._close, .close only performs logout --- README.md | 94 ++++++++++++++++++++-------------------- lib/client.js | 49 ++++++++++++--------- lib/lineparser.js | 108 +++++++++++++++++++++++----------------------- lib/mailbox.js | 56 ++++++++++++------------ lib/xoauth.js | 18 ++++---- 5 files changed, 166 insertions(+), 159 deletions(-) diff --git a/README.md b/README.md index 5af09c0..b8a04ea 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # inbox -This is a work in progress IMAP client for node.js. +This is a work in progress IMAP client for node.js. The project consists of two major parts @@ -25,7 +25,7 @@ Use **inbox** module ``` ### Create new IMAP connection -Create connection object with +Create connection object with ```javascript inbox.createConnection(port, host, options) ``` @@ -56,7 +56,7 @@ Example: ``` Or for login with XOAUTH2 (see examples/xoauth2) -```javascript +```javascript // XOAUTH2 var client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, @@ -75,7 +75,7 @@ Or for login with XOAUTH2 (see examples/xoauth2) Or for login with XOAUTH (see examples/xoauth-3lo.js and examples/xoauth-2lo.js) - + ```javascript // 3-legged- oauth var client = inbox.createConnection(false, "imap.gmail.com", { @@ -105,12 +105,12 @@ With 2-legged OAuth, consumerKey and consumerSecret need to have proper values, } }); ``` - + Once the connection object has been created, use connect() to create the actual connection. ```javascript client.connect(); ``` - + When the connection has been successfully established a 'connect' event is emitted. ```javascript client.on("connect", function(){ @@ -133,15 +133,15 @@ Logout from IMAP and close NET connection. ### List available mailboxes -To list the available mailboxes use +To list the available mailboxes use ```javascript client.listMailboxes(callback) ``` - + Where * **callback** *(error, mailboxes)* returns a list of root mailbox object - + Mailbox objects have the following properties * **name** - the display name of the mailbox @@ -170,12 +170,12 @@ Example: ### Fetch a specified mailbox object -If you need to access a specific mailbox object (for creating or listing child +If you need to access a specific mailbox object (for creating or listing child mailboxes etc.), you can do it with ```javascript client.getMailbox(path, callback) ``` - + Where * **path** is the mailbox directory path @@ -196,7 +196,7 @@ Before you can check mailbox contents, you need to select one with ```javascript client.openMailbox(path[, options], callback) ``` - + Where * **path** is the path to the mailbox (ie. "INBOX" or "INBOX/Arhiiv") or a mailbox object @@ -226,7 +226,7 @@ Where * **from** is the index of the first message (0 based), you can use negative numbers to count from the end (-10 indicates the 10 last messages) * **limit** defines the maximum count of messages to fetch, if not set or 0 all messages from the starting position will be included * **callback** *(error, messages)* is the callback function to run with the message array - + Example ```javascript // list newest 10 messages @@ -240,27 +240,27 @@ Example Example output for a message listing ```javascript [ - { + { // if uidvalidity changes, all uid values are void! UIDValidity: '664399135', - + // uid value of the message UID: 52, - + // message flags (Array) flags: [ '\\Flagged', '\\Seen' ], - + // date of the message (Date object) date: Wed, 25 Apr 2012 12:23:05 GMT, - + title: 'This is a message, may contain unicode symbols', - + // single "from:" address - from: { - name: 'Andris Reinman', - address: 'andris.reinman@gmail.com' + from: { + name: 'Andris Reinman', + address: 'andris.reinman@gmail.com' }, - + // an array of "to:" addresses to: [ { @@ -268,7 +268,7 @@ Example output for a message listing address: 'test.nodemailer@gmail.com' } ], - + // an array of "cc:" addresses cc: [ { @@ -276,7 +276,7 @@ Example output for a message listing address: 'test.nodemailer@gmail.com' } ], - + messageId: '<04541AB5-9FBD-4255-81AA-18FE67CB97E5@gmail.com>', inReplyTo: '<4FB16D5A.30808@gmail.com>', references: ['<4FB16D5A.30808@gmail.com>','<1299323903.19454@foo.bar>'] @@ -284,9 +284,9 @@ Example output for a message listing ... ] ``` - + **NB!** If some properties are not present in a message, it may be not included -in the message object - for example, if there are no "cc:" addresses listed, +in the message object - for example, if there are no "cc:" addresses listed, there is no "cc" field in the message object. ### Listing flags @@ -301,7 +301,7 @@ Where * **from** is the index of the first message (0 based), you can use negative numbers to count from the end (-10 indicates the 10 last messages) * **limit** defines the maximum count of messages to fetch, if not set or 0 all messages from the starting position will be included * **callback** *(error, messages)* is the callback function to run with the message array - + Example ```javascript // list flags for newest 10 messages @@ -315,18 +315,18 @@ Example Example output for a message listing ```javascript [ - { + { // if uidvalidity changes, all uid values are void! UIDValidity: '664399135', - + // uid value of the message UID: 52, - + // message flags (Array) flags: [ '\\Flagged', '\\Seen' ] }, ... - ] + ] ``` ### Fetch message details @@ -335,7 +335,7 @@ To fetch message data (flags, title, etc) for a specific message, use ```javascript client.fetchData(uid, callback) ``` - + Where * **uid** is the UID value for the mail @@ -355,7 +355,7 @@ you need to fetch the message. ```javascript var messageStream = client.createMessageStream(uid) ``` - + Where * **uid** is the UID value for the mail @@ -365,7 +365,7 @@ Example (output message contents to console) client.createMessageStream(123).pipe(process.stdout, {end: false}); ``` -**NB!** If the opened mailbox is not in read-only mode, the message will be +**NB!** If the opened mailbox is not in read-only mode, the message will be automatically marked as read (\Seen flag is set) when the message is fetched. ### Message flags @@ -380,7 +380,7 @@ You can add and remove message flags like `\Seen` or `\Answered` with `client.ad Where * **uid** is the message identifier - * **callback** *(error, flags)* is the callback to run, gets message flags array as a parameter + * **callback** *(error, flags)* is the callback to run, gets message flags array as a parameter **Add flags** ```javascript @@ -391,7 +391,7 @@ Where * **uid** is the message identifier * **flags** is the array of flags to be added - * **callback** *(error, flags)* is the callback to run, gets message flags array as a parameter + * **callback** *(error, flags)* is the callback to run, gets message flags array as a parameter **Remove flags** ```javascript @@ -410,7 +410,7 @@ Example client.addFlags(123, ["\\Seen", "\\Flagged"], function(err, flags){ console.log("Current flags for a message: ", flags); }); - + // remove \Flagged flag from a message client.removeFlags(123, ["\\Flagged"], function(err, flags){ console.log("Current flags for a message: ", flags); @@ -433,11 +433,11 @@ Where Example ```javascript client.storeMessage("From: ....", ["\\Seen"], function(err, params){ - console.log(err || params.UIDValidity +", "+ params.UID); + console.log(err || params.UIDValidity +", "+ params.UID); }); ``` -When adding a message to the mailbox, the new message event is also raised after +When adding a message to the mailbox, the new message event is also raised after the mail has been stored. ### Copy a message @@ -456,7 +456,7 @@ Where Example ```javascript client.copyMessage(123, "[GMail]/Junk", function(err){ - console.log(err || "success, copied to junk"); + console.log(err || "success, copied to junk"); }); ``` @@ -476,7 +476,7 @@ Where Example ```javascript client.moveMessage(123, "[GMail]/Junk", function(err){ - console.log(err || "success, moved to junk"); + console.log(err || "success, moved to junk"); }); ``` @@ -495,7 +495,7 @@ Where Example ```javascript client.deleteMessage(123, function(err){ - console.log(err || "success, message deleted"); + console.log(err || "success, message deleted"); }); ``` @@ -513,7 +513,7 @@ You can listen for new incoming e-mails with event "new" Listing newest 10 messages: ```javascript var inbox = require("inbox"); - + var client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, auth:{ @@ -521,13 +521,13 @@ Listing newest 10 messages: pass: "Nodemailer123" } }); - + client.connect(); - + client.on("connect", function(){ client.openMailbox("INBOX", function(error, info){ if(error) throw error; - + client.listMessages(-10, function(err, messages){ messages.forEach(function(message){ console.log(message.UID + ": " + message.title); diff --git a/lib/client.js b/lib/client.js index 98a3e0b..a9636b8 100644 --- a/lib/client.js +++ b/lib/client.js @@ -506,7 +506,7 @@ IMAPClient.prototype._onClose = function(){ console.log("EVENT: CLOSE"); } - this.close(); + this._close(); }; /** @@ -520,7 +520,7 @@ IMAPClient.prototype._onEnd = function(){ console.log("EVENT: END"); } - this.close(); + this._close(); }; /** @@ -576,7 +576,7 @@ IMAPClient.prototype._handlerGreeting = function(data){ error.errorType = "ProtocolError"; error.errorLog = this._log.slice(0, this._logLength); this.emit("error", error); - this.close(); + this._close(); return; } @@ -594,7 +594,7 @@ IMAPClient.prototype._handleGreetingTimeout = function(){ var error = new Error("Timeout waiting for a greeting"); error.errorType = "TimeoutError"; this.emit("error", error); - this.close(); + this._close(); }; /** @@ -795,7 +795,7 @@ IMAPClient.prototype._handlerTaggedCapability = function(status){ error.errorType = "ProtocolError"; error.errorLog = this._log.slice(0, this._logLength); this.emit("error", error); - this.close(); + this._close(); } }; @@ -828,7 +828,7 @@ IMAPClient.prototype._handlerTaggedStartTLS = function(status){ error.errorType = "TLSError"; error.errorLog = this._log.slice(0, this._logLength); this.emit("error", error); - this.close(); + this._close(); } }; @@ -867,7 +867,7 @@ IMAPClient.prototype._handlerTaggedLogin = function(status){ error.errorType = "AuthenticationError"; error.errorLog = this._log.slice(0, this._logLength); this.emit("error", error); - this.close(); + this._close(); } } }; @@ -2097,28 +2097,35 @@ IMAPClient.prototype.idle = function(){ * Closes the socket to the server * // FIXME - should LOGOUT first! */ -IMAPClient.prototype.close = function(){ +IMAPClient.prototype._close = function(){ if(!this._connection){ return; } + clearTimeout(this._shouldIdleTimer); clearTimeout(this._idleTimer); clearTimeout(this._greetingTimeout); - var that = this; - this._send('LOGOUT', function (){ - var socket = that._connection.socket || that._connection; + var socket = this._connection.socket || this._connection; - if(socket && !socket.destroyed){ - socket.destroy(); - } + if(socket && !socket.destroyed){ + socket.destroy(); + } - if(that.debug){ - console.log('CLOSED CONNECTION'); - } + if(this.debug){ + console.log("Connection to server closed"); + } - that._connection = false; - that._commandQueue = []; - that.emit("close"); - }); + this._connection = false; + this._commandQueue = []; + this.emit("close"); + + this.removeAllListeners(); +}; + +// Calls LOGOUT +IMAPClient.prototype.close = function(){ + this._send("LOGOUT", (function (){ + this.close(); + }).bind(this)); }; diff --git a/lib/lineparser.js b/lib/lineparser.js index e4f7e24..d76c5b9 100644 --- a/lib/lineparser.js +++ b/lib/lineparser.js @@ -5,20 +5,20 @@ var Stream = require("stream").Stream, utillib = require("util"); - + // expose to the world module.exports = IMAPLineParser; /** * Creates a reusable parser for parsing. It is a writable stream for piping * data directly in. - * + * * @constructor */ function IMAPLineParser(){ Stream.call(this); this.writable = true; - + this._init(); } utillib.inherits(IMAPLineParser, Stream); @@ -46,8 +46,8 @@ IMAPLineParser.prototype.types = { // PUBLIC METHODS /** - * Appends a chunk for parsing - * + * Appends a chunk for parsing + * * @param {Buffer|String} chunk Data to be appended to the parse string * @return {Boolean} Always returns true */ @@ -62,34 +62,34 @@ IMAPLineParser.prototype.write = function(chunk){ * If a literal occurs ({123}\r\n) do not parse it, since the length is known. * Just add it separately and it will included as the node value instead of * length property. - * + * * @param {Buffer|String} chunk Data to be appended to the literal string value */ IMAPLineParser.prototype.writeLiteral = function(chunk){ if(!this.currentNode.value){ this.currentNode.value = ""; } - + if(this.currentNode.type != this.types.LITERAL){ //this.currentNode.literal = this.currentNode.value; this.currentNode.value = ""; //this.currentNode.type = this.types.LITERAL; } - + this.currentNode.value += (chunk || "").toString("binary"); }; /** - * Finishes current parsing and reesets internal variables. Emits 'line' event + * Finishes current parsing and reesets internal variables. Emits 'line' event * with the parsed data - * + * * @param {Buffer|String} chunk Data to be appended to the parse string */ IMAPLineParser.prototype.end = function(chunk){ if(chunk && chunk.length){ this.write(chunk); } - + if(this.currentNode.value){ if(this._state == this.states.ATOM || this._state==this.states.QUOTED){ if(this._state == this.states.ATOM && this.currentNode.value == "NIL"){ @@ -98,7 +98,7 @@ IMAPLineParser.prototype.end = function(chunk){ this._branch.childNodes.push(this.currentNode); } } - + process.nextTick(this.emit.bind(this, "log", this._currentLine)); process.nextTick(this.emit.bind(this, "line", this.finalize())); this._init(); @@ -107,7 +107,7 @@ IMAPLineParser.prototype.end = function(chunk){ /** * Generates a structured object with the data currently known. Useful if you * need to check parse status in the middle of the process - * + * * @return {Array} Parsed data */ IMAPLineParser.prototype.finalize = function(){ @@ -122,25 +122,25 @@ IMAPLineParser.prototype.finalize = function(){ * Resets all internal variables and creates a new parse tree */ IMAPLineParser.prototype._init = function(){ - + /** * Current state the parser is in * @private */ this._state = this.states.DEFAULT; - + /** * Which quote symbol is used for current quoted string * @private */ this._quoteMark = ''; - + /** * Is the current character escaped by \ * @private */ this._escapedChar = false; - + /** * Parse tree to hold the parsed data structure * @private @@ -148,19 +148,19 @@ IMAPLineParser.prototype._init = function(){ this._parseTree = { childNodes: [] }; - + /** * Active branch, by default it's the tree itselt * @private */ this._branch = this._parseTree; - + /** * Hold the original line data * @private */ this._currentLine = ""; - + /** * Starting node * @private @@ -176,20 +176,20 @@ IMAPLineParser.prototype._init = function(){ * Parses the data currently known, continues from the previous state. * This is a token based parser. Special characters are space, backslash, * quotes, (), [] and <>. After every character the parseTree is updated. - * + * * @param {String} line Data to be parsed */ IMAPLineParser.prototype._parseLine = function(line){ var i=0, curchar; - + while(i < line.length){ - + curchar = line.charAt(i); // Check all characters one by one switch(curchar){ - + // Handle whitespace case " ": case "\t": @@ -203,7 +203,7 @@ IMAPLineParser.prototype._parseLine = function(line){ this._createNode(); } break; - + // Backspace is for escaping in quoted strings case '\\': if(this._escapedChar || this._state == this.states.ATOM){ @@ -215,7 +215,7 @@ IMAPLineParser.prototype._parseLine = function(line){ this._createNode(curchar); } break; - + // Handle quotes, remember the quote type to allow other unescaped quotes case '"': case "'": @@ -236,7 +236,7 @@ IMAPLineParser.prototype._parseLine = function(line){ this._createNode(); } break; - + // Handle different group types case "[": case "(": @@ -249,7 +249,7 @@ IMAPLineParser.prototype._parseLine = function(line){ if(this._state == this.states.ATOM){ this._addToBranch(); } - + // () gets a separate node, [] uses last node as parent if(this._state == this.states.ATOM && curchar == "["){ this._branch = this._branch.lastNode || this._parseTree; @@ -271,9 +271,9 @@ IMAPLineParser.prototype._parseLine = function(line){ this.currentNode.type = this.types.PARAMS; break; } - + this._addToBranch(); - + this._branch = this.currentNode || this._parseTree; if(!this._branch.childNodes){ this._branch.childNodes = []; @@ -281,9 +281,9 @@ IMAPLineParser.prototype._parseLine = function(line){ } this._state = this.states.DEFAULT; - + this._createNode(); - + break; case "]": case ")": @@ -292,21 +292,21 @@ IMAPLineParser.prototype._parseLine = function(line){ this.currentNode.value += curchar; break; } - + if(this._state == this.states.ATOM){ this._addToBranch(); } - + this._state = this.states.DEFAULT; this._branch = this._branch.parentNode || this._branch; if(!this._branch.childNodes){ this._branch.childNodes = []; } - + this._createNode(); break; - + // Add to existing string or create a new atom default: if(this._state == this.states.ATOM || this._state == this.states.QUOTED){ @@ -316,15 +316,15 @@ IMAPLineParser.prototype._parseLine = function(line){ this._createNode(curchar); } } - + // cancel escape if it didn't happen if(this._escapedChar && curchar != "\\"){ this._escapedChar = false; } - + i++; } - + }; /** @@ -340,24 +340,24 @@ IMAPLineParser.prototype._addToBranch = function(){ /** * Creates a new empty node - * + * * @param {String} [defaultValue] If specified will be used as node.value value */ IMAPLineParser.prototype._createNode = function(defaultValue){ this.lastNode = this.currentNode; - + this.currentNode = {}; - + if(defaultValue !== false){ this.currentNode.value = defaultValue || ""; } - + this.currentNode.parentNode = this._branch; }; /** * Recursive function to walk the parseTree and generate structured output object - * + * * @param {Array} branch Current branch to check * @param {Array} local Output object node to append the data to */ @@ -366,19 +366,19 @@ IMAPLineParser.prototype._nodeWalker = function(branch, local){ for(i=0, len = branch.length; i=0){ command = "LIST"; suffix = " RETURN (SPECIAL-USE)"; }else if(this.client._capabilities.indexOf("XLIST")>=0){ command = "XLIST"; } - + this.client._send(command+" "+this.client._escapeString(this.client._rootPath) + " " + path + suffix, (function(status){ this.listSubscribed(path, this.client._mailboxList, callback); @@ -81,26 +81,26 @@ Mailbox.prototype.listChildren = function(path, callback){ (function(){ this.client._mailboxList = []; }).bind(this)); - + }; /** * Fetches subscribed mailboxes - * + * * @param {String} path Parent mailbox * @param {Array} xinfo Results from XLIST or LIST - * @param {Function} callback Callback function to run with the mailbox list + * @param {Function} callback Callback function to run with the mailbox list */ Mailbox.prototype.listSubscribed = function(path, xinfo, callback){ if(!callback && typeof xinfo == "function"){ callback = xinfo; xinfo = undefined; } - - xinfo = xinfo || []; - this.client._send("LSUB "+this.client._escapeString(this.client._rootPath)+" "+path, - this.client._handlerTaggedLsub.bind(this.client, xinfo, callback), + xinfo = xinfo || []; + + this.client._send("LSUB "+this.client._escapeString(this.client._rootPath)+" "+path, + this.client._handlerTaggedLsub.bind(this.client, xinfo, callback), (function(){ this.client._mailboxList = []; }).bind(this)); @@ -108,7 +108,7 @@ Mailbox.prototype.listSubscribed = function(path, xinfo, callback){ /** * Creates a new mailbox and subscribes to it - * + * * @param {String} name Name of the mailbox * @param {Function} callback Callback function to run with the created mailbox object */ @@ -145,18 +145,18 @@ function detectMailboxType(mailboxName){ if(mailboxNames.sent.indexOf(mailboxName)>=0){ return "Sent"; } - + if(mailboxNames.trash.indexOf(mailboxName)>=0){ return "Trash"; } - + if(mailboxNames.junk.indexOf(mailboxName)>=0){ return "Junk"; } - + if(mailboxNames.drafts.indexOf(mailboxName)>=0){ return "Drafts"; } - - return "Normal"; -} \ No newline at end of file + + return "Normal"; +} diff --git a/lib/xoauth.js b/lib/xoauth.js index 42e4c8b..4923209 100644 --- a/lib/xoauth.js +++ b/lib/xoauth.js @@ -11,7 +11,7 @@ module.exports.XOAuthGenerator = XOAuthGenerator; /** * Create a XOAUTH login token generator - * + * * @constructor * @memberOf xoauth * @param {Object} options @@ -32,7 +32,7 @@ function XOAuthGenerator(options){ /** * Generate a XOAuth login token - * + * * @param {Function} [callback] Callback function to run when the access token is genertaed * @return {String|undefined} If callback is not set, return the token value, otherwise run callback instead */ @@ -71,11 +71,11 @@ function generateOAuthBaseStr(method, requestUrl, params){ function generateXOAuthStr(options, callback){ options = options || {}; - + var params = initOAuthParams(options), requestUrl = options.requestUrl || "https://mail.google.com/mail/b/" + (options.user || "") + "/imap/", baseStr, signatureKey, paramsStr, returnStr; - + if(options.token && !options.requestorId){ params.oauth_token = options.token; } @@ -86,22 +86,22 @@ function generateXOAuthStr(options, callback){ baseStr += encodeURIComponent("&xoauth_requestor_id=" + encodeURIComponent(options.requestorId)); } - signatureKey = escapeAndJoin([options.consumerSecret || "anonymous", options.tokenSecret || ""]); + signatureKey = escapeAndJoin([options.consumerSecret || "anonymous", options.tokenSecret || ""]); params.oauth_signature = hmacSha1(baseStr, signatureKey); paramsStr = Object.keys(params).sort().map(function(key){ return key+"=\""+encodeURIComponent(params[key])+"\""; }).join(","); - + // Liidab kokku üheks pikaks stringiks kujul "METHOD URL BODY" // 2-legged variandi puhul lisab BODY parameetritele otsa ka requestor_id väärtuse - returnStr = [options.method || "GET", requestUrl + + returnStr = [options.method || "GET", requestUrl + (options.requestorId ? "?xoauth_requestor_id=" + encodeURIComponent(options.requestorId) : ""), paramsStr].join(" "); - + if(typeof callback == "function"){ callback(null, new Buffer(returnStr, "utf-8").toString("base64")); }else{ return new Buffer(returnStr, "utf-8").toString("base64"); } -} \ No newline at end of file +} From d701910b067fd2f24b5d9827e2aea53c3adeceae Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 13 May 2013 14:51:28 +0300 Subject: [PATCH 050/106] fixed example --- examples/list.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/examples/list.js b/examples/list.js index 86e6ee3..804d891 100644 --- a/examples/list.js +++ b/examples/list.js @@ -1,6 +1,6 @@ var inbox = require(".."), util = require("util"); - + var client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, auth:{ @@ -13,12 +13,12 @@ var client = inbox.createConnection(false, "imap.gmail.com", { client.connect(); client.on("connect", function(){ - + client.listMailboxes(console.log); - + client.openMailbox("INBOX", function(error, mailbox){ if(error) throw error; - + // List newest 10 messages client.listMessages(-10, function(err, messages){ messages.forEach(function(message){ @@ -33,17 +33,16 @@ client.on("connect", function(){ }); }); }); - + // on new messages, print to console client.on("new", function(message){ console.log("New message:"); console.log(util.inspect(message, false, 7)); - + client.createMessageStream(message.UID).pipe(process.stdout, {end: false}); + }); client.on('close', function (){ console.log('DISCONNECTED!'); }); - - }); }); From a0adb7ab51faa4dab8dc58479889c678da170474 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 13 May 2013 15:13:30 +0300 Subject: [PATCH 051/106] updated example --- examples/list.js | 13 +++++++++---- lib/client.js | 4 ++++ package.json | 2 +- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/examples/list.js b/examples/list.js index 804d891..24c895e 100644 --- a/examples/list.js +++ b/examples/list.js @@ -29,7 +29,7 @@ client.on("connect", function(){ messages.forEach(function(message){ console.log(message); }); - client.close(); + //client.close(); }); }); }); @@ -41,8 +41,13 @@ client.on("connect", function(){ client.createMessageStream(message.UID).pipe(process.stdout, {end: false}); }); +}); - client.on('close', function (){ - console.log('DISCONNECTED!'); - }); +client.on('error', function (err){ + console.log('Error'); + console.log(err) +}); + +client.on('close', function (){ + console.log('DISCONNECTED!'); }); diff --git a/lib/client.js b/lib/client.js index a9636b8..c2a10dc 100644 --- a/lib/client.js +++ b/lib/client.js @@ -392,6 +392,10 @@ IMAPClient.prototype.connect = function(){ */ IMAPClient.prototype._onConnect = function(){ + if(this.debug){ + console.log("Connection to server opened"); + } + if("setKeepAlive" in this._connection){ this._connection.setKeepAlive(true); }else if(this._connection.socket && "setKeepAlive" in this._connection.socket){ diff --git a/package.json b/package.json index 75c2c61..3c6b4e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.29", + "version": "0.1.30", "author" : "Andris Reinman", "maintainers":[ { From 9565d787ee12ce95d68420ab28845aff9cfc5b14 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Thu, 16 May 2013 11:08:27 +0300 Subject: [PATCH 052/106] define license as MIT for this project --- LICENSE | 16 ++++++++++++++++ README.md | 4 ++++ package.json | 4 +++- 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8a4cfc4 --- /dev/null +++ b/LICENSE @@ -0,0 +1,16 @@ +Copyright (c) 2012-2013 Andris Reinman + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index b8a04ea..446a2b8 100644 --- a/README.md +++ b/README.md @@ -537,3 +537,7 @@ Listing newest 10 messages: }); }); ``` + +## License + +**MIT** diff --git a/package.json b/package.json index 3c6b4e4..20ce613 100644 --- a/package.json +++ b/package.json @@ -23,5 +23,7 @@ "scripts":{ "test": "nodeunit test" - } + }, + + "license": "MIT" } From 20a45c4f6a879d371c331b44c169ea9f7979667e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=98=9F?= Date: Sat, 8 Jun 2013 15:40:14 +0800 Subject: [PATCH 053/106] =?UTF-8?q?=E4=BF=AE=E6=94=B9ignore=E6=96=87?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2da1a66..f1f71e8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules .DS_Store tools/log.txt +.idea \ No newline at end of file From fa0b7bf80377f0dc49b8a2d0a4657b9eb0685af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=98=9F?= Date: Sat, 8 Jun 2013 15:48:13 +0800 Subject: [PATCH 054/106] add repository in package.json andris9/inbox#25 --- package.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/package.json b/package.json index 20ce613..1e820e5 100644 --- a/package.json +++ b/package.json @@ -25,5 +25,10 @@ "test": "nodeunit test" }, + "repository": { + "type": "git", + "url": "https://github.com/andris9/inbox" + }, + "license": "MIT" } From 967623acd90471d6c3b12c043bd44fdc1dde8759 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Tue, 16 Jul 2013 10:26:31 +0300 Subject: [PATCH 055/106] handles XOAUTH2 endless loop, fixes #27 --- lib/client.js | 10 ++++++++-- package.json | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/client.js b/lib/client.js index c2a10dc..b940e1b 100644 --- a/lib/client.js +++ b/lib/client.js @@ -102,6 +102,7 @@ function IMAPClient(port, host, options){ */ this._xoauth2 = false; this._xoauth2RetryCount = 0; + this._xoauth2UntaggedResponse = false; if(typeof this.options.auth && typeof this.options.auth.XOAuth2){ if(typeof this.options.auth.XOAuth2 == "object" && typeof this.options.auth.XOAuth2.getToken == "function"){ @@ -844,6 +845,7 @@ IMAPClient.prototype._handlerTaggedStartTLS = function(status){ IMAPClient.prototype._handlerTaggedLogin = function(status){ if(status == "OK"){ this._xoauth2RetryCount = 0; + this._xoauth2UntaggedResponse = false; this._currentState = this.states.AUTH; if(!this._updatedCapabilities){ this._send("CAPABILITY", this._handlerTaggedCapability.bind(this)); @@ -851,7 +853,7 @@ IMAPClient.prototype._handlerTaggedLogin = function(status){ this._postAuth(); } }else{ - if(this._xoauth2 && this._xoauth2RetryCount && this._xoauth2RetryCount<3){ + if(this._xoauth2 && this._xoauth2UntaggedResponse && this._xoauth2RetryCount && this._xoauth2RetryCount < 3){ this._xoauth2.generateToken((function(err, token){ if(err){ if(typeof err != "object"){ @@ -1204,7 +1206,11 @@ IMAPClient.prototype._postCapability = function(){ } this._send("AUTHENTICATE XOAUTH2 " + token, this._handlerTaggedLogin.bind(this), (function(){ + this._xoauth2UntaggedResponse = false; this._literalHandler = (function(message){ + + this._xoauth2UntaggedResponse = true; + message = (Array.isArray(message) && message[0] || message || "").toString().trim(); var data; try{ @@ -1216,7 +1222,7 @@ IMAPClient.prototype._postCapability = function(){ }; } - if(['400', '401'].indexOf(data.status)>=0){ + if(['400', '401'].indexOf((data.status || "").toString().trim())>=0){ this._xoauth2RetryCount = (this._xoauth2RetryCount || 0) + 1; } diff --git a/package.json b/package.json index 1e820e5..27771e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.30", + "version": "0.1.31", "author" : "Andris Reinman", "maintainers":[ { From 4cb334210d06d7f8e8738c3f3da2bb0bf6998a5c Mon Sep 17 00:00:00 2001 From: javiergarmon Date: Tue, 16 Jul 2013 13:08:54 +0200 Subject: [PATCH 056/106] If envelope parse fails this makes a correct envelope array --- lib/client.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/client.js b/lib/client.js index b940e1b..e3aca21 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1447,6 +1447,19 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ function(mimeWord){ return mimelib.decodeMimeWord(mimeWord); }); + + if( dataObject.ENVELOPE[2] && typeof dataObject.ENVELOPE[2] === 'string' ){ + + dataObject.ENVELOPE[2] = [ [ dataObject.ENVELOPE[2], null, dataObject.ENVELOPE[4], dataObject.ENVELOPE[6] ] ]; + dataObject.ENVELOPE[3] = [ [ dataObject.ENVELOPE[8], null, dataObject.ENVELOPE[10], dataObject.ENVELOPE[12] ] ]; + dataObject.ENVELOPE[4] = [ [ dataObject.ENVELOPE[14], null, dataObject.ENVELOPE[16], dataObject.ENVELOPE[18] ] ]; + + dataObject.ENVELOPE[5] = null; + dataObject.ENVELOPE[6] = null; + dataObject.ENVELOPE[8] = null; + + } + if(dataObject.ENVELOPE[2] && dataObject.ENVELOPE[2].length){ message.from = dataObject.ENVELOPE[2].map(this._formatEnvelopeAddress); if(message.from.length == 1){ From d2cba62c4e83f0e10691c37332e91d6a6d50bcff Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 5 Aug 2013 14:25:04 +0300 Subject: [PATCH 057/106] Use MODSEQ values --- lib/client.js | 25 ++++++++++++++++++++++--- package.json | 2 +- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/client.js b/lib/client.js index b940e1b..3ff11b5 100644 --- a/lib/client.js +++ b/lib/client.js @@ -665,6 +665,9 @@ IMAPClient.prototype._responseRouter = function(data){ }else if(data[2].params[0] == "UIDNEXT"){ this._selectedMailbox.UIDNext = data[2].params[1]; return; + }else if(data[2].params[0] == "HIGHESTMODSEQ"){ + this._selectedMailbox.highestModSeq = Number(data[2].params[1]); + return; }else if(data[2].params[0] == "UNSEEN"){ this._selectedMailbox.unseen = data[2].params[1]; return; @@ -861,7 +864,7 @@ IMAPClient.prototype._handlerTaggedLogin = function(status){ }else{ error = err; } - error.errorType = "AuthenticationError"; + error.errorType = "XOAUTH2Error"; error.errorLog = this._log.slice(0, this._logLength); this.emit("error", error); return; @@ -1157,6 +1160,7 @@ IMAPClient.prototype._handlerUntaggedFetch = function(list){ var envelope = (list || [])[3] || [], envelopeData = this._formatEnvelope(envelope), nextUID = Number(this._selectedMailbox.UIDNext) || 0, + nextUID = Number(this._selectedMailbox.UIDNext) || 0, currentUID = Number(envelopeData.UID) || 0; if(!nextUID || nextUID <= currentUID){ @@ -1327,6 +1331,10 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ message.xGMThreadId = dataObject["X-GM-THRID"]; } + if(dataObject["MODSEQ"]){ + message.modSeq = Number(dataObject["MODSEQ"]); + } + var messageTypes = [], messageTypeMap = { "Drafts": "Draft", @@ -1591,16 +1599,22 @@ IMAPClient.prototype.getCurrentMailbox = function(){ * @param {Number} limit How many messages to fetch, defaults to all from selected position * @param {Function} callback Callback function to run with the listed envelopes */ -IMAPClient.prototype.listMessages = function(from, limit, callback){ +IMAPClient.prototype.listMessages = function(from, limit, extendedOptions, callback){ var to; from = Number(from) || 0; + if(typeof options == "function" && !callback){ + callback = options; + options = undefined; + } + if(typeof limit == "function" && !callback){ callback = limit; limit = undefined; } + extendedOptions = extendedOptions || ""; limit = Number(limit) || 0; if(this._currentState != this.states.SELECTED){ @@ -1628,7 +1642,12 @@ IMAPClient.prototype.listMessages = function(from, limit, callback){ this._collectMailList = true; this._mailList = []; - this._send("FETCH "+from+":"+to+" (UID FLAGS ENVELOPE" +(this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"")+ ")", (function(status){ + + this._send( + "FETCH "+from+":"+to+ + " (UID FLAGS ENVELOPE" + + (this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"")+ ")" + + (extendedOptions ? " "+extendedOptions : ""), (function(status){ this._collectMailList = false; if(typeof callback != "function"){ diff --git a/package.json b/package.json index 27771e3..e829041 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.31", + "version": "0.1.32", "author" : "Andris Reinman", "maintainers":[ { From 6e1643badfdd83d59427211fba029bcbba5e5b23 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Thu, 8 Aug 2013 11:58:06 +0300 Subject: [PATCH 058/106] throw an error if trying to copy message without opening a mailbox first. Fixes #35 --- lib/client.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/client.js b/lib/client.js index 3ff11b5..e316f89 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1974,6 +1974,13 @@ IMAPClient.prototype.copyMessage = function(uid, destination, callback){ return; } + if(this._currentState != this.states.SELECTED){ + if(typeof callback == "function"){ + callback(new Error("No mailbox selected")); + } + return; + } + this._send("UID COPY " + uid + " " + this._escapeString(destination), (function(status){ if(status != "OK"){ return callback(new Error("Error copying message")); From 702e6c7232da5823abe027b529ac21080b1fb8d0 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Fri, 9 Aug 2013 14:37:16 +0300 Subject: [PATCH 059/106] return empty array if mailbox is empty while listing messages --- lib/client.js | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/client.js b/lib/client.js index e316f89..410d0e4 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1624,6 +1624,11 @@ IMAPClient.prototype.listMessages = function(from, limit, extendedOptions, callb return; } + // Nothing to retrieve + if(!this._selectedMailbox.count){ + return callback(null, []); + } + if(from < 0){ from = this._selectedMailbox.count + from; } diff --git a/package.json b/package.json index e829041..fb2a695 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.32", + "version": "0.1.33", "author" : "Andris Reinman", "maintainers":[ { From 14c8847edfb33d9f4050579ef1f72b68fe4d0cac Mon Sep 17 00:00:00 2001 From: Felix Hammerl Date: Thu, 8 Aug 2013 10:21:09 +0200 Subject: [PATCH 060/106] Fix close method --- lib/client.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/client.js b/lib/client.js index b940e1b..d0db062 100644 --- a/lib/client.js +++ b/lib/client.js @@ -2136,6 +2136,6 @@ IMAPClient.prototype._close = function(){ // Calls LOGOUT IMAPClient.prototype.close = function(){ this._send("LOGOUT", (function (){ - this.close(); + this._close(); }).bind(this)); }; From 5e17add707dffd56159c33fb6f4dcf72799526da Mon Sep 17 00:00:00 2001 From: Felix Hammerl Date: Fri, 16 Aug 2013 15:15:37 +0200 Subject: [PATCH 061/106] Change iconv for utf7 --- lib/client.js | 14 ++------------ package.json | 2 +- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/lib/client.js b/lib/client.js index d0db062..5d3beda 100644 --- a/lib/client.js +++ b/lib/client.js @@ -15,8 +15,7 @@ var Stream = require("stream").Stream, xoauth = require("./xoauth"), xoauth2 = require("xoauth2"), packageData = require("../package.json"), - Iconv = require("iconv").Iconv, - fromUTF7 = new Iconv("UTF-16", "UTF-8//TRANSLIT//IGNORE"), + utf7 = require('utf7').imap, mailboxlib = require("./mailbox"), Mailbox = mailboxlib.Mailbox, detectMailboxType = mailboxlib.detectMailboxType; @@ -1498,16 +1497,7 @@ IMAPClient.prototype._formatEnvelopeAddress = function(address){ * Convert from IMAP UTF7 to UTF-8 - useful for mailbox names */ IMAPClient.prototype._convertFromUTF7 = function(str){ - // default string encoding by inbox is binary - str = new Buffer(str, "binary").toString("utf-8"); - try{ - return str.replace(/&([^\-]+)\-/g, function(a, r){ - r = (r || "").replace(/,/g, "/"); - return fromUTF7.convert(new Buffer(r, "base64")).toString("utf-8"); - }); - }catch(E){ - return str; - } + return utf7.decode(str); }; /** diff --git a/package.json b/package.json index 27771e3..f012dc4 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "dependencies": { "mimelib": "*", - "iconv": "*", + "utf7": "~1.0.0", "xoauth2": "*" }, From b730139abac5c10e512183e8b699091acd73f877 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Sat, 17 Aug 2013 11:59:09 +0300 Subject: [PATCH 062/106] bumped version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0baa904..f43c664 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "0.1.33", + "version": "1.1.34", "author" : "Andris Reinman", "maintainers":[ { From ca3c604574a8402c11a8da893e12f6c381facdc9 Mon Sep 17 00:00:00 2001 From: Tankred Hase Date: Sat, 17 Aug 2013 12:12:09 +0200 Subject: [PATCH 063/106] options should be replaced by extendedOptions --- lib/client.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/client.js b/lib/client.js index df1b55c..438106b 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1594,9 +1594,9 @@ IMAPClient.prototype.listMessages = function(from, limit, extendedOptions, callb from = Number(from) || 0; - if(typeof options == "function" && !callback){ - callback = options; - options = undefined; + if(typeof extendedOptions == "function" && !callback){ + callback = extendedOptions; + extendedOptions = undefined; } if(typeof limit == "function" && !callback){ From e8d72a5ad8b2747b6c75a2839fecf96bb9c1f32b Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Sat, 17 Aug 2013 15:26:46 +0300 Subject: [PATCH 064/106] bumped version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f43c664..ebe3e88 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.34", + "version": "1.1.35", "author" : "Andris Reinman", "maintainers":[ { From 33878cd1bd881ccc072f526c246801aedfaf22bf Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Thu, 22 Aug 2013 10:55:20 +0300 Subject: [PATCH 065/106] If X-GM-LABELS is empty, do not force Inbox as a folder --- lib/client.js | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/client.js b/lib/client.js index 438106b..191cebc 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1342,7 +1342,7 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ }, messageTypePreferenceOrder = ["Sent", "Draft", "Starred", "Junk", "Trash"]; - if(dataObject["X-GM-LABELS"] && dataObject["X-GM-LABELS"].length){ + if(dataObject["X-GM-LABELS"]){ message.folders = (dataObject["X-GM-LABELS"] || []).map((function(label){ var type; @@ -1420,7 +1420,7 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ } message.folders = folderList; - if(!message.folders.length){ + if(!dataObject["X-GM-LABELS"] && !message.folders.length){ message.folders = ["Inbox"]; } } diff --git a/package.json b/package.json index ebe3e88..a11bd0b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.35", + "version": "1.1.36", "author" : "Andris Reinman", "maintainers":[ { From 6420b1a473260f33bf4906a57cdb6f2c0479ffd7 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 2 Sep 2013 09:42:51 +0300 Subject: [PATCH 066/106] use BODY.PEEK instead of BODY when fetchin messages (preserves unseen flag) --- lib/client.js | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/client.js b/lib/client.js index 191cebc..564d327 100644 --- a/lib/client.js +++ b/lib/client.js @@ -372,7 +372,7 @@ IMAPClient.prototype._init = function(){ IMAPClient.prototype.connect = function(){ if(this.options.secureConnection){ - this._connection = tls.connect(this.port, this.host, {}, this._onConnect.bind(this)); + this._connection = tls.connect(this.port, this.host, {rejectUnauthorized: false}, this._onConnect.bind(this)); }else{ this._connection = net.connect(this.port, this.host); this._connection.on("connect", this._onConnect.bind(this)); @@ -1927,7 +1927,7 @@ IMAPClient.prototype.createMessageStream = function(uid){ return; } - this._send("UID FETCH "+uid+":"+uid+" BODY[]", (function(status){ + this._send("UID FETCH "+uid+":"+uid+" BODY.PEEK[]", (function(status){ this._collectMailList = false; this._literalStreaming = false; diff --git a/package.json b/package.json index a11bd0b..754be5c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.36", + "version": "1.1.37", "author" : "Andris Reinman", "maintainers":[ { From aa630dc2433e5fbb1110eaa6c8f7226cdde9268e Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 2 Sep 2013 11:56:08 +0300 Subject: [PATCH 067/106] Added add/removeMessageLabels (Gmail specific) --- lib/client.js | 94 +++++++++++++++++++++++++++++++++++++++++++++++++-- package.json | 2 +- 2 files changed, 93 insertions(+), 3 deletions(-) diff --git a/lib/client.js b/lib/client.js index 564d327..b737378 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1717,7 +1717,6 @@ IMAPClient.prototype.listFlags = function(from, limit, callback){ }).bind(this)); }; - /** * Updates flags for selected message * @@ -1774,7 +1773,7 @@ IMAPClient.prototype.updateFlags = function(uid, flags, updateType, callback){ callback(null, this._mailList[0].flags || []); } }else{ - callback(new Error("Error fetching message data")); + callback(new Error("Error setting flags")); } } @@ -1809,6 +1808,97 @@ IMAPClient.prototype.removeFlags = function(uid, flags, callback){ this.updateFlags(uid, flags, "-", callback); }; +/** + * Updates labels for selected message + * + * @param {Number} uid Message identifier + * @param {Array} labels Labels to set for a message + * @param {String} [updateType=""] If empty, replace labels; + add label; - remove label + * @param {Function} callback Callback function to run, returns an array of labels + */ +IMAPClient.prototype.updateLabels = function(uid, labels, updateType, callback){ + uid = Number(uid) || 0; + labels = [].concat(labels || []); + + if(!callback && typeof updateType == "function"){ + callback = updateType; + updateType = undefined; + } + + updateType = (updateType || "").toString().trim(); + + if(!uid){ + if(typeof callback == "function"){ + callback(new Error("Invalid UID value")); + } + return; + } + + if(!Array.isArray(labels)){ + if(typeof callback == "function"){ + callback(new Error("Invalid labels value")); + } + return; + } + + if(this._currentState != this.states.SELECTED){ + if(typeof callback == "function"){ + callback(new Error("No mailbox selected")); + } + return; + } + + this._send("UID STORE "+uid+":"+uid+" "+updateType+"X-GM-LABELS (" + labels.join(" ") + ")", + (function(status){ + this._collectMailList = false; + + if(typeof callback != "function"){ + return; + } + + if(typeof callback == "function"){ + if(status == "OK"){ + if(!this._mailList.length){ + callback(null, true); + }else{ + callback(null, this._mailList[0].labels || []); + } + }else{ + callback(new Error("Error setting labels")); + } + } + + }).bind(this), + (function(){ + this._collectMailList = true; + this._mailList = []; + }).bind(this)); +}; + +/** + * Add labels for selected message + * + * @param {Number} uid Message identifier + * @param {Array} labels Labels to set for a message + * @param {Function} callback Callback function to run, returns an array of labels + */ +IMAPClient.prototype.addLabels = function(uid, labels, callback){ + labels = [].concat(labels || []); + this.updateLabels(uid, labels, "+", callback); +}; + +/** + * Removes labels for selected message + * + * @param {Number} uid Message identifier + * @param {Array} labels Labels to remove from a message + * @param {Function} callback Callback function to run, returns an array of labels + */ +IMAPClient.prototype.removeLabels = function(uid, labels, callback){ + labels = [].concat(labels || []); + this.updateLabels(uid, labels, "-", callback); +}; + /** * Fetches flags for selected message * diff --git a/package.json b/package.json index 754be5c..61b2d33 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.37", + "version": "1.1.38", "author" : "Andris Reinman", "maintainers":[ { From 21eda771ba96c8687f0ea0851f2fa7b0f24a335a Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 2 Sep 2013 15:57:20 +0300 Subject: [PATCH 068/106] allow uid ranges for flag and label updates --- lib/client.js | 18 ++++++++---------- package.json | 2 +- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/lib/client.js b/lib/client.js index b737378..1e84560 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1720,13 +1720,12 @@ IMAPClient.prototype.listFlags = function(from, limit, callback){ /** * Updates flags for selected message * - * @param {Number} uid Message identifier + * @param {String} uid Message identifier * @param {Array} flags Flags to set for a message * @param {String} [updateType=""] If empty, replace flags; + add flag; - remove flag * @param {Function} callback Callback function to run, returns an array of flags */ IMAPClient.prototype.updateFlags = function(uid, flags, updateType, callback){ - uid = Number(uid) || 0; flags = [].concat(flags || []); if(!callback && typeof updateType == "function"){ @@ -1757,7 +1756,7 @@ IMAPClient.prototype.updateFlags = function(uid, flags, updateType, callback){ return; } - this._send("UID STORE "+uid+":"+uid+" "+updateType+"FLAGS (" + flags.join(" ") + ")", + this._send("UID STORE "+uid+" "+updateType+"FLAGS (" + flags.join(" ") + ")", (function(status){ this._collectMailList = false; @@ -1787,7 +1786,7 @@ IMAPClient.prototype.updateFlags = function(uid, flags, updateType, callback){ /** * Add flags for selected message * - * @param {Number} uid Message identifier + * @param {String} uid Message identifier * @param {Array} flags Flags to set for a message * @param {Function} callback Callback function to run, returns an array of flags */ @@ -1799,7 +1798,7 @@ IMAPClient.prototype.addFlags = function(uid, flags, callback){ /** * Removes flags for selected message * - * @param {Number} uid Message identifier + * @param {String} uid Message identifier * @param {Array} flags Flags to remove from a message * @param {Function} callback Callback function to run, returns an array of flags */ @@ -1811,13 +1810,12 @@ IMAPClient.prototype.removeFlags = function(uid, flags, callback){ /** * Updates labels for selected message * - * @param {Number} uid Message identifier + * @param {String} uid Message identifier * @param {Array} labels Labels to set for a message * @param {String} [updateType=""] If empty, replace labels; + add label; - remove label * @param {Function} callback Callback function to run, returns an array of labels */ IMAPClient.prototype.updateLabels = function(uid, labels, updateType, callback){ - uid = Number(uid) || 0; labels = [].concat(labels || []); if(!callback && typeof updateType == "function"){ @@ -1848,7 +1846,7 @@ IMAPClient.prototype.updateLabels = function(uid, labels, updateType, callback){ return; } - this._send("UID STORE "+uid+":"+uid+" "+updateType+"X-GM-LABELS (" + labels.join(" ") + ")", + this._send("UID STORE "+uid+" "+updateType+"X-GM-LABELS (" + labels.join(" ") + ")", (function(status){ this._collectMailList = false; @@ -1878,7 +1876,7 @@ IMAPClient.prototype.updateLabels = function(uid, labels, updateType, callback){ /** * Add labels for selected message * - * @param {Number} uid Message identifier + * @param {String} uid Message identifier * @param {Array} labels Labels to set for a message * @param {Function} callback Callback function to run, returns an array of labels */ @@ -1890,7 +1888,7 @@ IMAPClient.prototype.addLabels = function(uid, labels, callback){ /** * Removes labels for selected message * - * @param {Number} uid Message identifier + * @param {String} uid Message identifier * @param {Array} labels Labels to remove from a message * @param {Function} callback Callback function to run, returns an array of labels */ diff --git a/package.json b/package.json index 61b2d33..7215049 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.38", + "version": "1.1.39", "author" : "Andris Reinman", "maintainers":[ { From 1ee74918d301203790b48748894f9611a0512d06 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Thu, 28 Nov 2013 14:03:44 +0200 Subject: [PATCH 069/106] Code maintenance, added first IMAP test with Hoodiecrow --- examples/append.js | 10 +- examples/copy.js | 8 +- examples/delete.js | 4 +- examples/id.js | 10 +- examples/move.js | 8 +- examples/xoauth-2lo.js | 12 +- examples/xoauth-3lo.js | 12 +- examples/xoauth2.js | 12 +- lib/client.js | 40 ++--- lib/lineparser.js | 3 +- lib/mailbox.js | 6 +- lib/starttls.js | 20 +-- lib/xoauth.js | 2 + package.json | 59 ++++--- test/inbox.js | 61 ++++++++ test/lineparser.js | 317 ++++++++++++++++++++------------------ tools/clientplayground.js | 28 ++-- tools/proxy.js | 32 ++-- 18 files changed, 359 insertions(+), 285 deletions(-) create mode 100644 test/inbox.js diff --git a/examples/append.js b/examples/append.js index 6980b30..a0b7ca9 100644 --- a/examples/append.js +++ b/examples/append.js @@ -1,6 +1,6 @@ var inbox = require(".."), util = require("util"); - + var client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, auth:{ @@ -16,7 +16,7 @@ client.on("connect", function(){ client.openMailbox("[Gmail]/Sent Mail", function(error, mailbox){ if(error) throw error; - + client.storeMessage("From: andris@node.ee\r\n"+ "To: andris@kreata.ee\r\n"+ "Message-Id: 1234\r\n"+ @@ -25,13 +25,13 @@ client.on("connect", function(){ "Tere tere 2!", ["\\Seen"], console.log); }); - + // on new messages, print to console client.on("new", function(message){ console.log("New message:"); console.log(util.inspect(message, false, 7)); - + client.createMessageStream(message.UID).pipe(process.stdout, {end: false}); - + }); }); diff --git a/examples/copy.js b/examples/copy.js index 3523131..0229041 100644 --- a/examples/copy.js +++ b/examples/copy.js @@ -1,6 +1,6 @@ var inbox = require(".."), util = require("util"); - + var client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, auth:{ @@ -16,12 +16,12 @@ client.on("connect", function(){ client.openMailbox("INBOX", function(error, mailbox){ if(error) throw error; - + client.listMessages(-1, function(error, messages){ messages.forEach(function(message){ console.log("Message") console.log(message); - + client.copyMessage(message.UID, "[Gmail]/Saadetud kirjad", function(error){ console.log(arguments); }) @@ -29,5 +29,5 @@ client.on("connect", function(){ }) }); - + }); diff --git a/examples/delete.js b/examples/delete.js index 62aee75..11c204e 100644 --- a/examples/delete.js +++ b/examples/delete.js @@ -1,6 +1,6 @@ var inbox = require(".."), util = require("util"); - + var client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, auth:{ @@ -16,7 +16,7 @@ client.on("connect", function(){ client.openMailbox("INBOX", function(error, mailbox){ if(error) throw error; - + client.listMessages(-1, function(error, messages){ messages.forEach(function(message){ console.log("Message") diff --git a/examples/id.js b/examples/id.js index 8503cbd..c1b8d82 100644 --- a/examples/id.js +++ b/examples/id.js @@ -1,6 +1,6 @@ var inbox = require(".."), util = require("util"); - + var client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, auth:{ @@ -17,11 +17,11 @@ var client = inbox.createConnection(false, "imap.gmail.com", { client.connect(); client.on("connect", function(){ - + client.openMailbox("INBOX", function(error, mailbox){ if(error) throw error; - - + + }); - + }); diff --git a/examples/move.js b/examples/move.js index 4fd6878..2b9b9de 100644 --- a/examples/move.js +++ b/examples/move.js @@ -1,6 +1,6 @@ var inbox = require(".."), util = require("util"); - + var client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, auth:{ @@ -16,12 +16,12 @@ client.on("connect", function(){ client.openMailbox("INBOX", function(error, mailbox){ if(error) throw error; - + client.listMessages(-1, function(error, messages){ messages.forEach(function(message){ console.log("Message") console.log(message); - + client.moveMessage(message.UID, "[Gmail]/Saadetud kirjad", function(error){ console.log(arguments); }) @@ -29,5 +29,5 @@ client.on("connect", function(){ }) }); - + }); diff --git a/examples/xoauth-2lo.js b/examples/xoauth-2lo.js index 0b621b4..6ecb067 100644 --- a/examples/xoauth-2lo.js +++ b/examples/xoauth-2lo.js @@ -1,7 +1,7 @@ var inbox = require(".."), util = require("util"); - + var client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, auth:{ @@ -27,22 +27,22 @@ client.on("connect", function(){ client.openMailbox("INBOX", function(error, mailbox){ if(error) throw error; - + // List newest 10 messages client.listMessages(-10, function(err, messages){ messages.forEach(function(message){ console.log(message.UID+": "+message.title); }); }); - + }); - + // on new messages, print to console client.on("new", function(message){ console.log("New message:"); console.log(util.inspect(message, false, 7)); - + client.createMessageStream(message.UID).pipe(process.stdout, {end: false}); - + }); }); diff --git a/examples/xoauth-3lo.js b/examples/xoauth-3lo.js index b886ab5..bc07dc2 100644 --- a/examples/xoauth-3lo.js +++ b/examples/xoauth-3lo.js @@ -1,6 +1,6 @@ var inbox = require(".."), util = require("util"); - + var client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, auth:{ @@ -25,22 +25,22 @@ client.on("connect", function(){ client.openMailbox("INBOX", function(error, mailbox){ if(error) throw error; - + // List newest 10 messages client.listMessages(-10, function(err, messages){ messages.forEach(function(message){ console.log(message.UID+": "+message.title); }); }); - + }); - + // on new messages, print to console client.on("new", function(message){ console.log("New message:"); console.log(util.inspect(message, false, 7)); - + client.createMessageStream(message.UID).pipe(process.stdout, {end: false}); - + }); }); \ No newline at end of file diff --git a/examples/xoauth2.js b/examples/xoauth2.js index 764d9d1..79af9a4 100644 --- a/examples/xoauth2.js +++ b/examples/xoauth2.js @@ -1,7 +1,7 @@ var inbox = require(".."), util = require("util"); - + var client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, auth:{ @@ -29,22 +29,22 @@ client.on("connect", function(){ client.openMailbox("INBOX", function(error, mailbox){ if(error) throw error; - + // List newest 10 messages client.listMessages(-10, function(err, messages){ messages.forEach(function(message){ console.log(message.UID+": "+message.title); }); }); - + }); - + // on new messages, print to console client.on("new", function(message){ console.log("New message:"); console.log(util.inspect(message, false, 7)); - + client.createMessageStream(message.UID).pipe(process.stdout, {end: false}); - + }); }); diff --git a/lib/client.js b/lib/client.js index 1e84560..bb1809d 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1,3 +1,5 @@ +"use strict"; + /** * @fileOverview Provides an simple API for IMAP mailbox access * @author Andris Reinman @@ -856,10 +858,11 @@ IMAPClient.prototype._handlerTaggedLogin = function(status){ } }else{ if(this._xoauth2 && this._xoauth2UntaggedResponse && this._xoauth2RetryCount && this._xoauth2RetryCount < 3){ - this._xoauth2.generateToken((function(err, token){ + this._xoauth2.generateToken((function(err){ + var error; if(err){ if(typeof err != "object"){ - var error = new Error(err.toString()); + error = new Error(err.toString()); }else{ error = err; } @@ -884,10 +887,8 @@ IMAPClient.prototype._handlerTaggedLogin = function(status){ * Handle ID command. We don't reaaly care if the ID succeeded or * not as it is just some informational data. If it failed we still might be * able to access the mailbox - * - * @param {String} status If "OK" then the command succeeded */ -IMAPClient.prototype._handlerTaggedId = function(status){ +IMAPClient.prototype._handlerTaggedId = function(){ this._postReady(); }; @@ -895,10 +896,8 @@ IMAPClient.prototype._handlerTaggedId = function(status){ * Handle CONDSTORE command. We don't reaaly care if the CONDSTORE succeeded or * not as it is just some informational data. If it failed we still might be * able to access the mailbox - * - * @param {String} status If "OK" then the command succeeded */ -IMAPClient.prototype._handlerTaggedCondstore = function(status){ +IMAPClient.prototype._handlerTaggedCondstore = function(){ var clientData = { name: X_CLIENT_NAME }; @@ -1146,8 +1145,8 @@ IMAPClient.prototype._handlerUntaggedIdle = function(){ * Handle search responses, not yet implemented * TODO: andle search responses */ -IMAPClient.prototype._handlerUntaggedSearch = function(list){ - //console.log(list); +IMAPClient.prototype._handlerUntaggedSearch = function(){ + return false; }; /** @@ -1159,7 +1158,6 @@ IMAPClient.prototype._handlerUntaggedFetch = function(list){ var envelope = (list || [])[3] || [], envelopeData = this._formatEnvelope(envelope), nextUID = Number(this._selectedMailbox.UIDNext) || 0, - nextUID = Number(this._selectedMailbox.UIDNext) || 0, currentUID = Number(envelopeData.UID) || 0; if(!nextUID || nextUID <= currentUID){ @@ -1171,7 +1169,7 @@ IMAPClient.prototype._handlerUntaggedFetch = function(list){ } // emit as new message - if(this._checkForNewMail){ + if(nextUID && nextUID <= currentUID && this._checkForNewMail){ this.emit("new", envelopeData); } }; @@ -1196,9 +1194,11 @@ IMAPClient.prototype._postCapability = function(){ if(this._capabilities.indexOf("AUTH=XOAUTH2")>=0 && this._xoauth2){ this._xoauth2.getToken((function(err, token){ + var error; + if(err){ if(typeof err != "object"){ - var error = new Error(err.toString()); + error = new Error(err.toString()); }else{ error = err; } @@ -1225,7 +1225,7 @@ IMAPClient.prototype._postCapability = function(){ }; } - if(['400', '401'].indexOf((data.status || "").toString().trim())>=0){ + if(['400', '401'].indexOf((data.status || "").toString().trim())>=0){ this._xoauth2RetryCount = (this._xoauth2RetryCount || 0) + 1; } @@ -1302,7 +1302,7 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ return null; } - var dataObject = {}, lastKey = false, headers, i, len; + var dataObject = {}, lastKey = false, i, len; for(i=0, len = envelopeData.length; i=0){ - this._send("IDLE", (function(status){ + this._send("IDLE", (function(){ this.idling = false; this._idleEnd = false; }).bind(this), (function(){ @@ -2203,7 +2203,7 @@ IMAPClient.prototype.idle = function(){ console.log("WARNING: Server does not support IDLE, fallback to NOOP"); } this._idleTimer = setTimeout((function(){ - this._send("NOOP", (function(status){ + this._send("NOOP", (function(){ this.nooping = false; }).bind(this), (function(){ this.nooping = true; diff --git a/lib/lineparser.js b/lib/lineparser.js index d76c5b9..2008bb4 100644 --- a/lib/lineparser.js +++ b/lib/lineparser.js @@ -1,3 +1,5 @@ +"use strict"; + /** * @fileOverview Provides a parser for IMAP line based commands * @author Andris Reinman @@ -423,4 +425,3 @@ IMAPLineParser.prototype._nodeWalker = function(branch, local){ } } }; - diff --git a/lib/mailbox.js b/lib/mailbox.js index 8de53af..fd10bd7 100644 --- a/lib/mailbox.js +++ b/lib/mailbox.js @@ -1,3 +1,5 @@ +"use strict"; + var mailboxNames = require("./names.json"); /** @@ -75,7 +77,7 @@ Mailbox.prototype.listChildren = function(path, callback){ } this.client._send(command+" "+this.client._escapeString(this.client._rootPath) + " " + path + suffix, - (function(status){ + (function(){ this.listSubscribed(path, this.client._mailboxList, callback); }).bind(this), (function(){ @@ -116,7 +118,7 @@ Mailbox.prototype.createChild = function(name, callback){ var path = (this.path ? this.path + this.delimiter + name:name); this.client._send("CREATE "+this.client._escapeString(path), (function(status){ if(status == "OK"){ - this.client._send("SUBSCRIBE "+this.client._escapeString(path), (function(status){ + this.client._send("SUBSCRIBE "+this.client._escapeString(path), (function(){ if(typeof callback == "function"){ callback(null, new this.client.Mailbox({ client: this.client, diff --git a/lib/starttls.js b/lib/starttls.js index 836f0f6..9c0ffe8 100644 --- a/lib/starttls.js +++ b/lib/starttls.js @@ -1,3 +1,5 @@ +"use strict"; + // SOURCE: https://gist.github.com/848444 // Target API: @@ -24,14 +26,14 @@ module.exports.starttls = starttls; /** *

Upgrades a socket to a secure TLS connection

- * + * * @memberOf starttls * @param {Object} socket Plaintext socket to be upgraded * @param {Function} callback Callback function to be run after upgrade */ function starttls(socket, callback) { var sslcontext, pair, cleartext; - + socket.removeAllListeners("data"); sslcontext = require('crypto').createCredentials(); pair = require('tls').createSecurePair(sslcontext, false); @@ -56,16 +58,16 @@ function starttls(socket, callback) { function forwardEvents(events, emitterSource, emitterDestination) { var map = [], name, handler; - + for(var i = 0, len = events.length; i < len; i++) { name = events[i]; handler = forwardEvent.bind(emitterDestination, name); - + map.push(name); emitterSource.on(name, handler); } - + return map; } @@ -84,9 +86,9 @@ function pipe(pair, socket) { socket.pipe(pair.encrypted); pair.fd = socket.fd; - + var cleartext = pair.cleartext; - + cleartext.socket = socket; cleartext.encrypted = pair.encrypted; cleartext.authorized = false; @@ -98,7 +100,7 @@ function pipe(pair, socket) { } var map = forwardEvents(["timeout", "end", "close", "drain", "error"], socket, cleartext); - + function onclose() { socket.removeListener('error', onerror); socket.removeListener('close', onclose); @@ -109,4 +111,4 @@ function pipe(pair, socket) { socket.on('close', onclose); return cleartext; -} \ No newline at end of file +} diff --git a/lib/xoauth.js b/lib/xoauth.js index 4923209..e177ae6 100644 --- a/lib/xoauth.js +++ b/lib/xoauth.js @@ -1,3 +1,5 @@ +"use strict"; + // this module is inspired by xoauth.py // http://code.google.com/p/google-mail-xoauth-tools/ diff --git a/package.json b/package.json index 7215049..3e7b4cd 100644 --- a/package.json +++ b/package.json @@ -1,34 +1,29 @@ { - "name": "inbox", - "version": "1.1.39", - "author" : "Andris Reinman", - "maintainers":[ - { - "name":"andris", - "email":"andris@node.ee" - } - ], - - "main": "lib/client.js", - - "dependencies": { - "mimelib": "*", - "utf7": "~1.0.0", - "xoauth2": "*" - }, - - "devDependencies": { - "nodeunit": "*" - }, - - "scripts":{ - "test": "nodeunit test" - }, - - "repository": { - "type": "git", - "url": "https://github.com/andris9/inbox" - }, - - "license": "MIT" + "name": "inbox", + "version": "1.1.39", + "author": "Andris Reinman", + "maintainers": [ + { + "name": "andris", + "email": "andris@node.ee" + } + ], + "main": "lib/client.js", + "dependencies": { + "mimelib": "*", + "utf7": "~1.0.0", + "xoauth2": "*" + }, + "devDependencies": { + "nodeunit": "*", + "hoodiecrow": "~1.1.16" + }, + "scripts": { + "test": "nodeunit test" + }, + "repository": { + "type": "git", + "url": "https://github.com/andris9/inbox" + }, + "license": "MIT" } diff --git a/test/inbox.js b/test/inbox.js new file mode 100644 index 0000000..2dd4299 --- /dev/null +++ b/test/inbox.js @@ -0,0 +1,61 @@ +var inbox = require(".."), + hoodiecrow = require("hoodiecrow"); + +var IMAP_PORT = 1143; + +module.exports["Inbox tests"] = { + setUp: function(next){ + this.server = hoodiecrow({ + plugins: ["IDLE"], + storage: { + "INBOX":{ + messages: [ + {raw: "Subject: hello 1\r\n\r\nWorld 1!", internaldate: "14-Sep-2013 21:22:28 -0300"}, + {raw: "Subject: hello 2\r\n\r\nWorld 2!", flags: ["\\Seen"]}, + {raw: "Subject: hello 3\r\n\r\nWorld 3!"}, + {raw: "From: sender name \r\n"+ + "To: Receiver name \r\n"+ + "Subject: hello 4\r\n"+ + "Message-Id: \r\n"+ + "Date: Fri, 13 Sep 2013 15:01:00 +0300\r\n"+ + "\r\n"+ + "World 4!"}, + {raw: "Subject: hello 5\r\n\r\nWorld 5!"}, + {raw: "Subject: hello 6\r\n\r\nWorld 6!"} + ] + } + } + }); + + this.server.listen(IMAP_PORT, next); + }, + + tearDown: function(next){ + this.server.close(next); + }, + + "Connect and authenticate": function(test){ + var client = inbox.createConnection(IMAP_PORT, "localhost", { + auth:{ + user: "testuser", + pass: "testpass" + } + }); + + client.connect(); + + client.on("connect", function(){ + client.openMailbox("INBOX", function(err, mailbox){ + test.ifError(err); + test.equal(mailbox.count, 6); + test.equal(mailbox.UIDValidity, "1"); + test.equal(mailbox.UIDNext, "7"); + client.close(); + }); + }); + + client.on("close", function (){ + test.done(); + }); + } +} \ No newline at end of file diff --git a/test/lineparser.js b/test/lineparser.js index 959fd8b..e68a0ee 100644 --- a/test/lineparser.js +++ b/test/lineparser.js @@ -1,221 +1,232 @@ -var testCase = require('nodeunit').testCase, - IMAPLineParser = require("../lib/lineparser"); - +var IMAPLineParser = require("../lib/lineparser"); exports["Type tests"] = { - + "Single atom": function(test){ - var lp = new IMAPLineParser(); - - test.expect(1); - - lp.on("line", function(data){ - test.deepEqual(data, ["TAG1"]); - test.done(); - }); - - lp.end("TAG1"); + var lp = new IMAPLineParser(); + + test.expect(1); + + lp.on("line", function(data){ + test.deepEqual(data, ["TAG1"]); + test.done(); + }); + + lp.end("TAG1"); }, - + "Multiple atoms": function(test){ - var lp = new IMAPLineParser(); - - test.expect(1); - - lp.on("line", function(data){ - test.deepEqual(data, ["TAG1", "UID", "FETCH"]); - test.done(); - }); - - lp.end("TAG1 UID FETCH"); + var lp = new IMAPLineParser(); + + test.expect(1); + + lp.on("line", function(data){ + test.deepEqual(data, ["TAG1", "UID", "FETCH"]); + test.done(); + }); + + lp.end("TAG1 UID FETCH"); }, - + "Single quoted": function(test){ - var lp = new IMAPLineParser(); - - test.expect(1); - - lp.on("line", function(data){ - test.deepEqual(data, ["TAG1"]); - test.done(); - }); - - lp.end("\"TAG1\""); + var lp = new IMAPLineParser(); + + test.expect(1); + + lp.on("line", function(data){ + test.deepEqual(data, ["TAG1"]); + test.done(); + }); + + lp.end("\"TAG1\""); }, - + "Multiword quoted": function(test){ - var lp = new IMAPLineParser(); - - test.expect(1); - - lp.on("line", function(data){ - test.deepEqual(data, ["TAG1 UID FETCH"]); - test.done(); - }); - - lp.end("\"TAG1 UID FETCH\""); + var lp = new IMAPLineParser(); + + test.expect(1); + + lp.on("line", function(data){ + test.deepEqual(data, ["TAG1 UID FETCH"]); + test.done(); + }); + + lp.end("\"TAG1 UID FETCH\""); }, - + "Atom + quoted": function(test){ - var lp = new IMAPLineParser(); - - test.expect(1); - - lp.on("line", function(data){ - test.deepEqual(data, ["TAG1", "UID FETCH"]); - test.done(); - }); - - lp.end("TAG1 \"UID FETCH\""); + var lp = new IMAPLineParser(); + + test.expect(1); + + lp.on("line", function(data){ + test.deepEqual(data, ["TAG1", "UID FETCH"]); + test.done(); + }); + + lp.end("TAG1 \"UID FETCH\""); }, - + "Single literal": function(test){ - var lp = new IMAPLineParser(); - - test.expect(1); - - lp.on("line", function(data){ - test.deepEqual(data, ["TAG1", "ABC DEF\r\nGHI JKL", "TAG2"]); - test.done(); - }); - - lp.write("TAG1 {123}"); - lp.writeLiteral("ABC DEF\r\nGHI JKL"); - lp.end("\"TAG2\""); + var lp = new IMAPLineParser(); + + test.expect(1); + + lp.on("line", function(data){ + test.deepEqual(data, ["TAG1", "ABC DEF\r\nGHI JKL", "TAG2"]); + test.done(); + }); + + lp.write("TAG1 {123}"); + lp.writeLiteral("ABC DEF\r\nGHI JKL"); + lp.end("\"TAG2\""); }, - + "NIL value": function(test){ var lp = new IMAPLineParser(); - + test.expect(1); - + lp.on("line", function(data){ test.deepEqual(data, ["TAG1", null]); test.done(); }); - + lp.end("TAG1 NIL"); }, - + "NIL string": function(test){ var lp = new IMAPLineParser(); - + test.expect(1); - + lp.on("line", function(data){ test.deepEqual(data, ["TAG1", "NIL"]); test.done(); }); - + lp.end("TAG1 \"NIL\""); } } exports["Structure tests"] = { - "Single group": function(test){ - var lp = new IMAPLineParser(); - - test.expect(1); - - lp.on("line", function(data){ - test.deepEqual(data, ["TAG1", "FETCH", ["NAME", "HEADER", "BODY"]]); - test.done(); - }); - - lp.end("TAG1 FETCH (NAME HEADER BODY)"); + "Single group": function(test){ + var lp = new IMAPLineParser(); + + test.expect(1); + + lp.on("line", function(data){ + test.deepEqual(data, ["TAG1", "FETCH", ["NAME", "HEADER", "BODY"]]); + test.done(); + }); + + lp.end("TAG1 FETCH (NAME HEADER BODY)"); }, - + "Nested group": function(test){ - var lp = new IMAPLineParser(); - - test.expect(1); - - lp.on("line", function(data){ - test.deepEqual(data, ["TAG1", "FETCH", ["NAME", "HEADER", "BODY", ["CHARSET", "UTF-8"]]]); - test.done(); - }); - - lp.end("TAG1 FETCH (NAME HEADER BODY (CHARSET \"UTF-8\"))"); + var lp = new IMAPLineParser(); + + test.expect(1); + + lp.on("line", function(data){ + test.deepEqual(data, ["TAG1", "FETCH", ["NAME", "HEADER", "BODY", ["CHARSET", "UTF-8"]]]); + test.done(); + }); + + lp.end("TAG1 FETCH (NAME HEADER BODY (CHARSET \"UTF-8\"))"); }, - + "Single params": function(test){ - var lp = new IMAPLineParser(); - - test.expect(1); - - lp.on("line", function(data){ - test.deepEqual(data, ["TAG1", {value:"BODY", params: ["DATE", "TEXT"]}]); - test.done(); - }); - - lp.end("TAG1 BODY[DATE TEXT]"); + var lp = new IMAPLineParser(); + + test.expect(1); + + lp.on("line", function(data){ + test.deepEqual(data, ["TAG1", {value:"BODY", params: ["DATE", "TEXT"]}]); + test.done(); + }); + + lp.end("TAG1 BODY[DATE TEXT]"); }, - + "Partial data": function(test){ - var lp = new IMAPLineParser(); - - test.expect(1); - - lp.on("line", function(data){ - test.deepEqual(data, ["TAG1", {value:"BODY", partial: [122, 456]}]); - test.done(); - }); - - lp.end("TAG1 BODY[]<122.456>"); + var lp = new IMAPLineParser(); + + test.expect(1); + + lp.on("line", function(data){ + test.deepEqual(data, ["TAG1", {value:"BODY", partial: [122, 456]}]); + test.done(); + }); + + lp.end("TAG1 BODY[]<122.456>"); }, - + "Mixed params and partial": function(test){ - var lp = new IMAPLineParser(); - - test.expect(1); - - lp.on("line", function(data){ - test.deepEqual(data, ["TAG1", {value:"BODY", params: ["HEADER", "FOOTER"], partial: [122, 456]}]); - test.done(); - }); - - lp.end("TAG1 BODY[HEADER FOOTER]<122.456>"); + var lp = new IMAPLineParser(); + + test.expect(1); + + lp.on("line", function(data){ + test.deepEqual(data, ["TAG1", {value:"BODY", params: ["HEADER", "FOOTER"], partial: [122, 456]}]); + test.done(); + }); + + lp.end("TAG1 BODY[HEADER FOOTER]<122.456>"); }, - + "Nested params and groups": function(test){ - var lp = new IMAPLineParser(); - - test.expect(1); - - lp.on("line", function(data){ - test.deepEqual(data, ["TAG1", {value:"BODY", params: ["DATE", "FLAGS", ["\\Seen", "\\Deleted"]]}]); - test.done(); - }); - - lp.end("TAG1 BODY[DATE FLAGS (\\Seen \\Deleted)]"); + var lp = new IMAPLineParser(); + + test.expect(1); + + lp.on("line", function(data){ + test.deepEqual(data, ["TAG1", {value:"BODY", params: ["DATE", "FLAGS", ["\\Seen", "\\Deleted"]]}]); + test.done(); + }); + + lp.end("TAG1 BODY[DATE FLAGS (\\Seen \\Deleted)]"); }, - + "Bound and unbound params": function(test){ var lp = new IMAPLineParser(); - + test.expect(1); - + lp.on("line", function(data){ test.deepEqual(data, ["TAG1", {params: ["ALERT"]}, {value: "BODY", params:["TEXT", "HEADER"]}]); test.done(); }); - + lp.end("TAG1 [ALERT] BODY[TEXT HEADER]"); + }, + + "Escaped list": function(test){ + var lp = new IMAPLineParser(); + + test.expect(1); + + lp.on("line", function(data){ + test.deepEqual(data, ["TAG1", 'abc"', [ 'def' ]]); + test.done(); + }); + + lp.end("TAG1 \"abc\\\"\" (\"def\")"); } } exports["Logging tests"] = { "Simple log": function(test){ var lp = new IMAPLineParser(); - + test.expect(1); - + lp.on("log", function(data){ test.equal(data, "TAG1 FETCH (NAME HEADER BODY)"); test.done(); }); - + lp.write("TAG1 ") lp.end("FETCH (NAME HEADER BODY)"); } -} \ No newline at end of file +}; diff --git a/tools/clientplayground.js b/tools/clientplayground.js index 55001d2..a7d80e7 100644 --- a/tools/clientplayground.js +++ b/tools/clientplayground.js @@ -1,6 +1,6 @@ var inbox = require(".."), util = require("util"); - + var client = inbox.createConnection(false, "imap.gmail.com", { secureConnection: true, auth:{ @@ -15,27 +15,27 @@ client.connect(); client.on("connect", function(){ client.openMailbox("INBOX", function(error, mailbox){ if(error) throw error; - + // List newest 10 messages client.listMessages(-10, function(err, messages){ messages.forEach(function(message){ console.log(message.UID+": "+message.title); }); }); - + /* client.fetchData(52, function(err, message){ - console.log(message); + console.log(message); }); - + //var stream = client.createMessageStream(52); //client.createMessageStream(52).pipe(process.stdout, {end: false}); - + client.updateFlags(52, ["\\Answered", "\\Flagged"], "+", console.log) client.removeFlags(52, ["\\Answered", "\\Flagged"], console.log) client.addFlags(52, ["\\Flagged"], console.log) */ - + function walkMailboxes(name, level, node){ level = level || 0; (node.listChildren || node.listMailboxes).call(node, function(err, list){ @@ -49,10 +49,10 @@ client.on("connect", function(){ } }); } - + console.log(12) client.getMailbox("[Gmail]/Saadetud kirjad", console.log); - + //walkMailboxes("ROOT", 0, client); /* client.listMailboxes(function(error, mailboxes){ @@ -64,17 +64,17 @@ client.on("connect", function(){ }) }); */ - + //client.listChildren(console.log) - + }); - + // on new messages, print to console client.on("new", function(message){ console.log("New message:"); console.log(util.inspect(message, false, 7)); - + client.createMessageStream(message.UID).pipe(process.stdout, {end: false}); - + }); }); diff --git a/tools/proxy.js b/tools/proxy.js index 1f72879..759e7b7 100644 --- a/tools/proxy.js +++ b/tools/proxy.js @@ -13,16 +13,16 @@ var sessionCounter = 0, server = net.createServer(function(client) { //'connection' listener console.log('Client connected'); - + var socket, target, session = ++sessionCounter; - + client.on('end', function() { console.log('Client disconnected'); if(socket && !socket.destroyed){ socket.end(); } }); - + client.on('data', function(chunk) { var str = (chunk || "").toString("utf-8").trim(); if(str){ @@ -32,7 +32,7 @@ var sessionCounter = 0, } } }); - + client.on('error', function(err) { console.log("Client error"); console.log(err); @@ -42,32 +42,32 @@ var sessionCounter = 0, }); console.log(targetPort, targetHost) var params = [targetPort, targetHost, function() { - + console.log("Server connected"); - + socket = targetSecure ? target.socket : target; - + socket.setKeepAlive(true); - + if(client && !client.destroyed){ client.pipe(target); }else{ socket.end(); } - + }]; if(targetSecure){ params.shift(); params.shift(); params.unshift({ - port: targetPort, + port: targetPort, host: targetHost }); } var target = (targetSecure?tls:net).connect.apply((targetSecure?tls:net), params); - + target.on('data', function(chunk) { var str = (chunk || "").toString("utf-8").trim(); if(str){ @@ -76,26 +76,26 @@ var sessionCounter = 0, logStream.write("~~~~~~~~~~~~~~~~~= SERVER ("+session+") =~~~~~~~~~~~~~~~~~~\r\n"+str+"\r\n"); } } - + // do not announce compression support on IMAP if(str.match(/\bCOMPRESS\=[\w]+/)){ chunk = new Buffer(str.replace(/COMPRESS\=[\w]+/g, "").trim()+"\r\n", "utf-8"); } - + client.write(chunk); }); - + target.on('end', function() { console.log("Server disconnected"); client.end(); }); - + target.on('error', function(err) { console.log("Server error"); console.log(err); client.end(); }); - + }); server.listen(proxyPort, function() { //'listening' listener From d8721c35e8515e03a303cd1d6c8d63cd685125f2 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Thu, 28 Nov 2013 14:11:06 +0200 Subject: [PATCH 070/106] Updated travis conf --- .travis.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 00d0f06..993d115 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,12 @@ language: node_js node_js: - - 0.6 - - 0.7 + - 0.8 + - "0.10" + - "0.11" notifications: email: recipients: - - andris@node.ee + - andris@kreata.ee on_success: change on_failure: change From a9ee2e6ff810cb4f37b9c2ca3efb075886d699f5 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Thu, 28 Nov 2013 14:17:36 +0200 Subject: [PATCH 071/106] Bumped version to 1.1.40 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3e7b4cd..6318df9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.39", + "version": "1.1.40", "author": "Andris Reinman", "maintainers": [ { From 8bff6d3053d3fc903ee12977f930f6c02b14ceb2 Mon Sep 17 00:00:00 2001 From: Suwon Chae Date: Mon, 9 Dec 2013 09:26:04 +0900 Subject: [PATCH 072/106] add writable property to override tags value in Mailbox It resolve Issue #49 --- lib/mailbox.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/mailbox.js b/lib/mailbox.js index fd10bd7..699d13a 100644 --- a/lib/mailbox.js +++ b/lib/mailbox.js @@ -26,7 +26,8 @@ function Mailbox(options){ Object.defineProperty(this, "tags", { value: options.tags || [], - enumerable: false + enumerable: false, + writable: true }); this.name = options.name || ""; From 67a5f9c669254142f10f363528ec07bdd9676b82 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 9 Dec 2013 09:01:16 +0200 Subject: [PATCH 073/106] Bumped version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6318df9..e9f3359 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.40", + "version": "1.1.41", "author": "Andris Reinman", "maintainers": [ { From 24ee60773c1a1567ba1551daf8320d60d29f57d6 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 16 Dec 2013 11:02:24 +0200 Subject: [PATCH 074/106] Added CONTRIBUTING file --- CONTRIBUTING.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..cde8175 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,11 @@ +# Contributing + +New features are accepted to the master branch if + + * The feature is complete. It does what is expected by the description, eg. in case of *SEARCH* the method should implement all (or a reasonable amount of) search conditions and it would accept several untagged *SEARCH* responses not just the first one - even though servers tend to respond with only one untagged response, the spec allows several + * The new feature follows the style of existing features + * The feature is properly tested. For tests you can use Nodeunit and Hoodiecrow, see [test/inbox.js](test/inbox.js) for an example. Tests for a complete feature should have its own test file in the [test](test/) folder. + +## Formatting + +Use 4 spaces instead of tabs. Commas last. Use double quotes instead of single quotes where possible. \ No newline at end of file From c73b3ea3757519e75234bc018e50e987179396ca Mon Sep 17 00:00:00 2001 From: Jakob Gillich Date: Tue, 17 Dec 2013 22:15:42 +0100 Subject: [PATCH 075/106] Added additional API tests Covers all documented functions (new event is missing). --- test/inbox.js | 203 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 182 insertions(+), 21 deletions(-) diff --git a/test/inbox.js b/test/inbox.js index 2dd4299..7a1faf5 100644 --- a/test/inbox.js +++ b/test/inbox.js @@ -3,9 +3,14 @@ var inbox = require(".."), var IMAP_PORT = 1143; +var server, client; + module.exports["Inbox tests"] = { setUp: function(next){ - this.server = hoodiecrow({ + server = null; + client = null; + + server = hoodiecrow({ plugins: ["IDLE"], storage: { "INBOX":{ @@ -23,39 +28,195 @@ module.exports["Inbox tests"] = { {raw: "Subject: hello 5\r\n\r\nWorld 5!"}, {raw: "Subject: hello 6\r\n\r\nWorld 6!"} ] + }, + "": { + "separator": "/", + "folders": { + "TRASH": {}, + "SENT": {} + } } } }); - - this.server.listen(IMAP_PORT, next); + server.listen(IMAP_PORT, function(){ + client = inbox.createConnection(IMAP_PORT, "localhost", { + auth:{ + user: "testuser", + pass: "testpass" + }, + debug: false + }); + client.connect(); + client.on("connect", next); + }); }, tearDown: function(next){ - this.server.close(next); + client.close(); + client.on("close", function(){ + server.close(next); + }); + }, + "List mailboxes": function(test){ + client.listMailboxes(function(err, mailboxes){ + test.ifError(err); + test.equal(mailboxes.length, 2); + test.equal(mailboxes[0].path, "TRASH"); + test.equal(mailboxes[1].name, "SENT"); + test.done(); + }); + }, + "Fetch mailbox": function(test){ + client.getMailbox("SENT", function(err, mailbox){ + test.ifError(err); + test.equal(Object.keys(mailbox).length, 4); + test.equal(mailbox.type, "Sent"); + test.equal(mailbox.delimiter, "/"); + test.done(); + }); + }, + "Open mailbox": function(test){ + client.openMailbox("INBOX", function(err, mailbox){ + test.ifError(err); + test.equal(mailbox.count, 6); + test.equal(mailbox.UIDValidity, "1"); + test.equal(mailbox.UIDNext, "7"); + test.done(); + }); + }, + "List messages": function(test){ + client.openMailbox("INBOX", function(err, mailbox){ + test.ifError(err); + client.listMessages(-100, function(err, messages){ + test.ifError(err); + test.equal(messages.length, 6); + for(var i = 0; i < messages.length; i++) { + test.equal(messages[i].UIDValidity, 1); + test.equal(messages[i].UID, i+1); + } + test.equal(messages[3].from.address, "sender@example.com"); + test.done(); + }); + }); }, + "List flags": function(test){ + client.openMailbox("INBOX", function(err, mailbox){ + test.ifError(err); + client.listFlags(-100, function(err, messages){ + test.ifError(err); + test.equal(messages.length, 6); + for(var i = 0; i < messages.length; i++) { + test.equal(messages[i].flags.length, i === 1 ? 1 : 0); + } + test.done(); + }); - "Connect and authenticate": function(test){ - var client = inbox.createConnection(IMAP_PORT, "localhost", { - auth:{ - user: "testuser", - pass: "testpass" - } }); + }, + "Fetch message details": function(test){ + client.openMailbox("INBOX", function(err, mailbox){ + test.ifError(err); + client.fetchData(4, function(err, message){ + test.ifError(err); + test.equal(Object.keys(message).length, 10); + test.equal(message.title, "hello 4") + test.equal(message.from.address, "sender@example.com"); + test.equal(message.to[0].name, "Receiver name"); + test.done(); + }); + }); + }, + "Fetch message contents": function(test){ + client.openMailbox("INBOX", function(err, mailbox){ + test.ifError(err); + var chunks = [], + chunklength = 0, + messageStream = client.createMessageStream(1); + messageStream.on("data", function(chunk){ + chunks.push(chunk); + chunklength += chunk.length; + }); + messageStream.on("end", function(){ + test.equal(Buffer.concat(chunks, chunklength).toString(), "Subject: hello 1\r\n\r\nWorld 1!"); + test.done(); + }); - client.connect(); + }); + }, + "Fetch message flags": function(test){ + client.openMailbox("INBOX", function(err, mailbox){ + test.ifError(err); + client.fetchFlags(2, function(err, flags) { + test.ifError(err); + test.equal(flags.length, 1); + test.equal(flags[0], "\\Seen"); + test.done(); + }); - client.on("connect", function(){ - client.openMailbox("INBOX", function(err, mailbox){ + }); + }, + "Add message flag": function(test){ + client.openMailbox("INBOX", function(err, mailbox){ + test.ifError(err); + client.addFlags(2, ["Test"], function(err, flags){ test.ifError(err); - test.equal(mailbox.count, 6); - test.equal(mailbox.UIDValidity, "1"); - test.equal(mailbox.UIDNext, "7"); - client.close(); + test.equal(flags.length, 2); + test.equal(flags[0], "\\Seen"); + test.equal(flags[1], "Test"); + test.done(); }); }); - - client.on("close", function (){ - test.done(); + }, + "Remove message flag": function(test){ + client.openMailbox("INBOX", function(err, mailbox){ + test.ifError(err); + client.removeFlags(2, ["\\Seen"], function(err, flags) { + test.ifError(err); + test.equal(flags.length, 0); + test.done(); + }); + }); + }, + "Store message": function(test){ + client.openMailbox("INBOX", function(err, mailbox){ + test.ifError(err); + test.equal(mailbox.count, 6); + client.storeMessage("Subject: hello 7\r\n\r\nWorld 7!", ["\\Seen"], function(err, params){ + test.ifError(err); + test.equal(params.UID, mailbox.UIDNext); + client.openMailbox("INBOX", function(err, mailbox){ + test.equal(mailbox.count, 7); + test.done(); + }); + }); + }); + }, + "Copy message": function(test){ + client.openMailbox("INBOX", function(err, mailbox){ + test.ifError(err); + client.copyMessage(3, "TRASH", function(err){ + test.ifError(err); + client.openMailbox("TRASH", function(err, mailbox){ + test.ifError(err); + test.equal(mailbox.count, 1); + test.equal(mailbox.UIDNext, 2); + test.done(); + }); + }) + }); + }, + "Delete message": function(test){ + client.openMailbox("INBOX", function(err, mailbox){ + test.ifError(err); + test.equal(mailbox.count, 6); + client.deleteMessage(6, function(err){ + test.ifError(err); + client.openMailbox("INBOX", function(err, mailbox){ + test.ifError(err); + test.equal(mailbox.count, 5); + test.done(); + }); + }); }); } -} \ No newline at end of file +} From 6399b0fc9bc9d6db7b0b07a3bfd7fe5013d49d77 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Wed, 18 Dec 2013 11:21:46 +0200 Subject: [PATCH 076/106] Added test for 'new', use this.server and this.client in tests, fixed some issues with CONDSTORE listing, found and fixed some bugs in Hoodiecrow as well --- lib/client.js | 22 ++++-- test/inbox.js | 196 +++++++++++++++++++++++++++++++++++--------------- 2 files changed, 155 insertions(+), 63 deletions(-) diff --git a/lib/client.js b/lib/client.js index bb1809d..e133bc8 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1516,7 +1516,10 @@ IMAPClient.prototype._checkNewMail = function(){ return; } - this._send("UID FETCH "+this._selectedMailbox.UIDNext+":* (FLAGS ENVELOPE" +(this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"")+ ")", (function(){ + this._send("UID FETCH " + this._selectedMailbox.UIDNext + ":* (UID FLAGS ENVELOPE" + + (this._capabilities.indexOf("X-GM-EXT-1") >= 0 ? " X-GM-LABELS X-GM-THRID" : "") + + (this._capabilities.indexOf("CONDSTORE") >= 0 ? " MODSEQ" : "") + + ")", (function(){ this._checkForNewMail = false; }).bind(this), (function(){ @@ -1639,9 +1642,11 @@ IMAPClient.prototype.listMessages = function(from, limit, extendedOptions, callb this._mailList = []; this._send( - "FETCH "+from+":"+to+ + "FETCH " + from + ":" + to + " (UID FLAGS ENVELOPE" + - (this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"")+ ")" + + (this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"") + + (this._capabilities.indexOf("CONDSTORE") >= 0 ? " MODSEQ" : "") + + ")" + (extendedOptions ? " "+extendedOptions : ""), (function(status){ this._collectMailList = false; @@ -1702,7 +1707,7 @@ IMAPClient.prototype.listFlags = function(from, limit, callback){ this._collectMailList = true; this._mailList = []; - this._send("FETCH "+from+":"+to+" (UID FLAGS)", (function(status){ + this._send("FETCH " + from + ":" + to + " (UID FLAGS)", (function(status){ this._collectMailList = false; if(typeof callback != "function"){ @@ -1920,7 +1925,7 @@ IMAPClient.prototype.fetchFlags = function(uid, callback){ return; } - this._send("UID FETCH "+uid+":"+uid+" (FLAGS)", (function(status){ + this._send("UID FETCH " + uid + " (UID FLAGS)", (function(status){ this._collectMailList = false; if(typeof callback != "function"){ @@ -1969,7 +1974,10 @@ IMAPClient.prototype.fetchData = function(uid, callback){ return; } - this._send("UID FETCH "+uid+":"+uid+" (FLAGS ENVELOPE" +(this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"")+ ")", (function(status){ + this._send("UID FETCH " + uid + " (UID FLAGS ENVELOPE" + + (this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"") + + (this._capabilities.indexOf("CONDSTORE") >= 0 ? " MODSEQ" : "") + + ")", (function(status){ this._collectMailList = false; if(typeof callback != "function"){ @@ -2015,7 +2023,7 @@ IMAPClient.prototype.createMessageStream = function(uid){ return; } - this._send("UID FETCH "+uid+":"+uid+" BODY.PEEK[]", (function(status){ + this._send("UID FETCH " + uid + " BODY.PEEK[]", (function(status){ this._collectMailList = false; this._literalStreaming = false; diff --git a/test/inbox.js b/test/inbox.js index 7a1faf5..676d4cf 100644 --- a/test/inbox.js +++ b/test/inbox.js @@ -1,16 +1,13 @@ +"use strict"; + var inbox = require(".."), hoodiecrow = require("hoodiecrow"); var IMAP_PORT = 1143; -var server, client; - module.exports["Inbox tests"] = { setUp: function(next){ - server = null; - client = null; - - server = hoodiecrow({ + this.server = hoodiecrow({ plugins: ["IDLE"], storage: { "INBOX":{ @@ -38,27 +35,29 @@ module.exports["Inbox tests"] = { } } }); - server.listen(IMAP_PORT, function(){ - client = inbox.createConnection(IMAP_PORT, "localhost", { + + this.server.listen(IMAP_PORT, (function(){ + this.client = inbox.createConnection(IMAP_PORT, "localhost", { auth:{ user: "testuser", pass: "testpass" }, debug: false }); - client.connect(); - client.on("connect", next); - }); + this.client.connect(); + this.client.on("connect", next); + }).bind(this)); }, tearDown: function(next){ - client.close(); - client.on("close", function(){ - server.close(next); - }); + this.client.close(); + this.client.on("close", (function(){ + this.server.close(next); + }).bind(this)); }, + "List mailboxes": function(test){ - client.listMailboxes(function(err, mailboxes){ + this.client.listMailboxes(function(err, mailboxes){ test.ifError(err); test.equal(mailboxes.length, 2); test.equal(mailboxes[0].path, "TRASH"); @@ -66,8 +65,9 @@ module.exports["Inbox tests"] = { test.done(); }); }, + "Fetch mailbox": function(test){ - client.getMailbox("SENT", function(err, mailbox){ + this.client.getMailbox("SENT", function(err, mailbox){ test.ifError(err); test.equal(Object.keys(mailbox).length, 4); test.equal(mailbox.type, "Sent"); @@ -75,8 +75,9 @@ module.exports["Inbox tests"] = { test.done(); }); }, + "Open mailbox": function(test){ - client.openMailbox("INBOX", function(err, mailbox){ + this.client.openMailbox("INBOX", function(err, mailbox){ test.ifError(err); test.equal(mailbox.count, 6); test.equal(mailbox.UIDValidity, "1"); @@ -84,10 +85,11 @@ module.exports["Inbox tests"] = { test.done(); }); }, + "List messages": function(test){ - client.openMailbox("INBOX", function(err, mailbox){ + this.client.openMailbox("INBOX", (function(err){ test.ifError(err); - client.listMessages(-100, function(err, messages){ + this.client.listMessages(-100, function(err, messages){ test.ifError(err); test.equal(messages.length, 6); for(var i = 0; i < messages.length; i++) { @@ -97,12 +99,13 @@ module.exports["Inbox tests"] = { test.equal(messages[3].from.address, "sender@example.com"); test.done(); }); - }); + }).bind(this)); }, + "List flags": function(test){ - client.openMailbox("INBOX", function(err, mailbox){ + this.client.openMailbox("INBOX", (function(err){ test.ifError(err); - client.listFlags(-100, function(err, messages){ + this.client.listFlags(-100, function(err, messages){ test.ifError(err); test.equal(messages.length, 6); for(var i = 0; i < messages.length; i++) { @@ -111,27 +114,29 @@ module.exports["Inbox tests"] = { test.done(); }); - }); + }).bind(this)); }, + "Fetch message details": function(test){ - client.openMailbox("INBOX", function(err, mailbox){ + this.client.openMailbox("INBOX", (function(err){ test.ifError(err); - client.fetchData(4, function(err, message){ + this.client.fetchData(4, function(err, message){ test.ifError(err); - test.equal(Object.keys(message).length, 10); - test.equal(message.title, "hello 4") + test.equal(Object.keys(message).length, 11); + test.equal(message.title, "hello 4"); test.equal(message.from.address, "sender@example.com"); test.equal(message.to[0].name, "Receiver name"); test.done(); }); - }); + }).bind(this)); }, + "Fetch message contents": function(test){ - client.openMailbox("INBOX", function(err, mailbox){ + this.client.openMailbox("INBOX", (function(err){ test.ifError(err); var chunks = [], chunklength = 0, - messageStream = client.createMessageStream(1); + messageStream = this.client.createMessageStream(1); messageStream.on("data", function(chunk){ chunks.push(chunk); chunklength += chunk.length; @@ -141,82 +146,161 @@ module.exports["Inbox tests"] = { test.done(); }); - }); + }).bind(this)); }, + "Fetch message flags": function(test){ - client.openMailbox("INBOX", function(err, mailbox){ + this.client.openMailbox("INBOX", (function(err){ test.ifError(err); - client.fetchFlags(2, function(err, flags) { + this.client.fetchFlags(2, function(err, flags) { test.ifError(err); test.equal(flags.length, 1); test.equal(flags[0], "\\Seen"); test.done(); }); - }); + }).bind(this)); }, + "Add message flag": function(test){ - client.openMailbox("INBOX", function(err, mailbox){ + this.client.openMailbox("INBOX", (function(err){ test.ifError(err); - client.addFlags(2, ["Test"], function(err, flags){ + this.client.addFlags(2, ["Test"], function(err, flags){ test.ifError(err); test.equal(flags.length, 2); test.equal(flags[0], "\\Seen"); test.equal(flags[1], "Test"); test.done(); }); - }); + }).bind(this)); }, + "Remove message flag": function(test){ - client.openMailbox("INBOX", function(err, mailbox){ + this.client.openMailbox("INBOX", (function(err){ test.ifError(err); - client.removeFlags(2, ["\\Seen"], function(err, flags) { + this.client.removeFlags(2, ["\\Seen"], function(err, flags) { test.ifError(err); test.equal(flags.length, 0); test.done(); }); - }); + }).bind(this)); }, + + "Store message": function(test){ - client.openMailbox("INBOX", function(err, mailbox){ + this.client.openMailbox("INBOX", (function(err, mailbox){ test.ifError(err); test.equal(mailbox.count, 6); - client.storeMessage("Subject: hello 7\r\n\r\nWorld 7!", ["\\Seen"], function(err, params){ + this.client.storeMessage("Subject: hello 7\r\n\r\nWorld 7!", ["\\Seen"], (function(err, params){ test.ifError(err); test.equal(params.UID, mailbox.UIDNext); - client.openMailbox("INBOX", function(err, mailbox){ + this.client.openMailbox("INBOX", function(err, mailbox){ test.equal(mailbox.count, 7); test.done(); }); - }); - }); + }).bind(this)); + }).bind(this)); }, + "Copy message": function(test){ - client.openMailbox("INBOX", function(err, mailbox){ + this.client.openMailbox("INBOX", (function(err){ test.ifError(err); - client.copyMessage(3, "TRASH", function(err){ + this.client.copyMessage(3, "TRASH", (function(err){ test.ifError(err); - client.openMailbox("TRASH", function(err, mailbox){ + this.client.openMailbox("TRASH", function(err, mailbox){ test.ifError(err); test.equal(mailbox.count, 1); test.equal(mailbox.UIDNext, 2); test.done(); }); - }) - }); + }).bind(this)); + }).bind(this)); }, + "Delete message": function(test){ - client.openMailbox("INBOX", function(err, mailbox){ + this.client.openMailbox("INBOX", (function(err, mailbox){ test.ifError(err); test.equal(mailbox.count, 6); - client.deleteMessage(6, function(err){ + this.client.deleteMessage(6, (function(err){ test.ifError(err); - client.openMailbox("INBOX", function(err, mailbox){ + this.client.openMailbox("INBOX", function(err, mailbox){ test.ifError(err); test.equal(mailbox.count, 5); test.done(); }); - }); + }).bind(this)); + }).bind(this)); + }, + + "New message": function(test){ + this.client.on("new", function(message){ + test.ok(message); + test.done(); }); + + this.client.openMailbox("INBOX", (function(err){ + this.client.storeMessage("Subject: hello 8\r\n\r\nWorld 8!", function(){}); + }).bind(this)); } -} +}; + +module.exports["Condstore tests"] = { + setUp: function(next){ + this.server = hoodiecrow({ + plugins: ["ENABLE", "CONDSTORE"], + storage: { + "INBOX":{ + messages: [ + {raw: "Subject: hello 1\r\n\r\nWorld 1!", internaldate: "14-Sep-2013 21:22:28 -0300"}, + {raw: "Subject: hello 2\r\n\r\nWorld 2!", flags: ["\\Seen"]} + ] + }, + "": { + "separator": "/", + "folders": { + "TRASH": {}, + "SENT": {} + } + } + }, + debug: false + }); + + this.server.listen(IMAP_PORT, (function(){ + this.client = inbox.createConnection(IMAP_PORT, "localhost", { + auth:{ + user: "testuser", + pass: "testpass" + }, + debug: false + }); + this.client.connect(); + this.client.on("connect", next); + }).bind(this)); + }, + + tearDown: function(next){ + this.client.close(); + this.client.on("close", (function(){ + this.server.close(next); + }).bind(this)); + }, + + "MODSEQ updates": function(test){ + this.client.openMailbox("INBOX", (function(err, mailbox){ + var modseq = mailbox.highestModSeq; + test.ok(Number(modseq)); + this.client.addFlags(2, ["Test"], (function(err, flags){ + test.ifError(err); + this.client.listMessages(0, 0, "(CHANGEDSINCE " + modseq + ")", function(err, messages){ + test.ifError(err); + test.equal(messages.length, 1); + test.equal(messages[0].UID, 2); + test.equal(messages[0].modSeq, 3); + test.done(); + }); + }).bind(this)); + }).bind(this)); + } +}; + From f20b7355a48e2fdb7acdfe6c41fd2728ba77d415 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Wed, 18 Dec 2013 11:22:09 +0200 Subject: [PATCH 077/106] Bumped version to 1.1.42 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e9f3359..da92a12 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.41", + "version": "1.1.42", "author": "Andris Reinman", "maintainers": [ { From bb7bf7083a40d5bd5435c259208c3d9d4505dddf Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Wed, 18 Dec 2013 13:21:21 +0200 Subject: [PATCH 078/106] move condstore tests to separate file --- test/condstore.js | 68 +++++++++++++++++++++++++++++++++++++++++++++++ test/inbox.js | 61 ------------------------------------------ 2 files changed, 68 insertions(+), 61 deletions(-) create mode 100644 test/condstore.js diff --git a/test/condstore.js b/test/condstore.js new file mode 100644 index 0000000..75d2437 --- /dev/null +++ b/test/condstore.js @@ -0,0 +1,68 @@ +"use strict"; + +var inbox = require(".."), + hoodiecrow = require("hoodiecrow"); + +var IMAP_PORT = 1143; + +module.exports["Condstore"] = { + setUp: function(next){ + this.server = hoodiecrow({ + plugins: ["ENABLE", "CONDSTORE"], + storage: { + "INBOX":{ + messages: [ + {raw: "Subject: hello 1\r\n\r\nWorld 1!"}, + {raw: "Subject: hello 2\r\n\r\nWorld 2!", flags: ["\\Seen"]}, + {raw: "Subject: hello 3\r\n\r\nWorld 3!"} + ] + }, + "": { + "separator": "/", + "folders": { + "TRASH": {}, + "SENT": {} + } + } + }, + debug: false + }); + + this.server.listen(IMAP_PORT, (function(){ + this.client = inbox.createConnection(IMAP_PORT, "localhost", { + auth:{ + user: "testuser", + pass: "testpass" + }, + debug: false + }); + this.client.connect(); + this.client.on("connect", next); + }).bind(this)); + }, + + tearDown: function(next){ + this.client.close(); + this.client.on("close", (function(){ + this.server.close(next); + }).bind(this)); + }, + + "Fetch messages since last modseq change": function(test){ + this.client.openMailbox("INBOX", (function(err, mailbox){ + var modseq = mailbox.highestModSeq; + test.equal(modseq, 3); + this.client.addFlags(2, ["Test"], (function(err, flags){ + test.ifError(err); + this.client.listMessages(0, 0, "(CHANGEDSINCE " + modseq + ")", function(err, messages){ + test.ifError(err); + test.equal(messages.length, 1); + test.equal(messages[0].UID, 2); + test.equal(messages[0].modSeq, 4); + test.done(); + }); + }).bind(this)); + }).bind(this)); + } +}; + diff --git a/test/inbox.js b/test/inbox.js index 676d4cf..793b0ea 100644 --- a/test/inbox.js +++ b/test/inbox.js @@ -243,64 +243,3 @@ module.exports["Inbox tests"] = { }).bind(this)); } }; - -module.exports["Condstore tests"] = { - setUp: function(next){ - this.server = hoodiecrow({ - plugins: ["ENABLE", "CONDSTORE"], - storage: { - "INBOX":{ - messages: [ - {raw: "Subject: hello 1\r\n\r\nWorld 1!", internaldate: "14-Sep-2013 21:22:28 -0300"}, - {raw: "Subject: hello 2\r\n\r\nWorld 2!", flags: ["\\Seen"]} - ] - }, - "": { - "separator": "/", - "folders": { - "TRASH": {}, - "SENT": {} - } - } - }, - debug: false - }); - - this.server.listen(IMAP_PORT, (function(){ - this.client = inbox.createConnection(IMAP_PORT, "localhost", { - auth:{ - user: "testuser", - pass: "testpass" - }, - debug: false - }); - this.client.connect(); - this.client.on("connect", next); - }).bind(this)); - }, - - tearDown: function(next){ - this.client.close(); - this.client.on("close", (function(){ - this.server.close(next); - }).bind(this)); - }, - - "MODSEQ updates": function(test){ - this.client.openMailbox("INBOX", (function(err, mailbox){ - var modseq = mailbox.highestModSeq; - test.ok(Number(modseq)); - this.client.addFlags(2, ["Test"], (function(err, flags){ - test.ifError(err); - this.client.listMessages(0, 0, "(CHANGEDSINCE " + modseq + ")", function(err, messages){ - test.ifError(err); - test.equal(messages.length, 1); - test.equal(messages[0].UID, 2); - test.equal(messages[0].modSeq, 3); - test.done(); - }); - }).bind(this)); - }).bind(this)); - } -}; - From b307c86d9ca08843bdd17fa6a12a099c0da28ee2 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Thu, 19 Dec 2013 09:29:18 +0200 Subject: [PATCH 079/106] Do not allow using invalid paths in openMailbox --- lib/client.js | 4 +++- test/inbox.js | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/client.js b/lib/client.js index e133bc8..2f54d6c 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1564,7 +1564,9 @@ IMAPClient.prototype.openMailbox = function(path, options, callback){ path = path.path; } - path = path || this._inboxName || "INBOX"; + if(!path){ + return callback(new Error("Invalid or missing mailbox path provided")); + } this._selectedMailbox = { path: path diff --git a/test/inbox.js b/test/inbox.js index 793b0ea..0da827e 100644 --- a/test/inbox.js +++ b/test/inbox.js @@ -86,6 +86,22 @@ module.exports["Inbox tests"] = { }); }, + "Try to open invalid mailbox": function(test){ + this.client.openMailbox(undefined, function(err, mailbox){ + test.ok(err); + test.ok(!mailbox); + test.done(); + }); + }, + + "Try to open missing mailbox": function(test){ + this.client.openMailbox("NON-EXISTENT", function(err, mailbox){ + test.ok(err); + test.ok(!mailbox); + test.done(); + }); + }, + "List messages": function(test){ this.client.openMailbox("INBOX", (function(err){ test.ifError(err); From 27bc2267df5ae4aca201ebd1673f9aaaa92ce1ac Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Fri, 3 Jan 2014 09:48:00 +0200 Subject: [PATCH 080/106] Fallback to LIST on empty LSUB --- lib/mailbox.js | 7 +++++- package.json | 4 +-- test/inbox.js | 66 +++++++++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 70 insertions(+), 7 deletions(-) diff --git a/lib/mailbox.js b/lib/mailbox.js index 699d13a..d755d26 100644 --- a/lib/mailbox.js +++ b/lib/mailbox.js @@ -103,7 +103,12 @@ Mailbox.prototype.listSubscribed = function(path, xinfo, callback){ xinfo = xinfo || []; this.client._send("LSUB "+this.client._escapeString(this.client._rootPath)+" "+path, - this.client._handlerTaggedLsub.bind(this.client, xinfo, callback), + (function(status){ + if(!this.client._mailboxList.length){ + this.client._mailboxList = [].concat(xinfo); + } + this.client._handlerTaggedLsub(xinfo, callback, status); + }).bind(this), (function(){ this.client._mailboxList = []; }).bind(this)); diff --git a/package.json b/package.json index da92a12..aec2856 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.42", + "version": "1.1.43", "author": "Andris Reinman", "maintainers": [ { @@ -16,7 +16,7 @@ }, "devDependencies": { "nodeunit": "*", - "hoodiecrow": "~1.1.16" + "hoodiecrow": "~1.1.19" }, "scripts": { "test": "nodeunit test" diff --git a/test/inbox.js b/test/inbox.js index 0da827e..a32f892 100644 --- a/test/inbox.js +++ b/test/inbox.js @@ -30,7 +30,10 @@ module.exports["Inbox tests"] = { "separator": "/", "folders": { "TRASH": {}, - "SENT": {} + "SENT": {}, + "Unsubscribed": { + subscribed: false + } } } } @@ -59,9 +62,10 @@ module.exports["Inbox tests"] = { "List mailboxes": function(test){ this.client.listMailboxes(function(err, mailboxes){ test.ifError(err); - test.equal(mailboxes.length, 2); - test.equal(mailboxes[0].path, "TRASH"); - test.equal(mailboxes[1].name, "SENT"); + test.equal(mailboxes.length, 3); + test.equal(mailboxes[0].path, "INBOX"); + test.equal(mailboxes[1].path, "TRASH"); + test.equal(mailboxes[2].name, "SENT"); test.done(); }); }, @@ -259,3 +263,57 @@ module.exports["Inbox tests"] = { }).bind(this)); } }; + +module.exports["Empty LSUB"] = { + setUp: function(next){ + this.server = hoodiecrow({ + storage: { + "INBOX":{ + subscribed: false + }, + "": { + "separator": "/", + "folders": { + "TRASH": { + subscribed: false + }, + "SENT": { + subscribed: false + } + } + } + }, + debug: false + }); + + this.server.listen(IMAP_PORT, (function(){ + this.client = inbox.createConnection(IMAP_PORT, "localhost", { + auth:{ + user: "testuser", + pass: "testpass" + }, + debug: false + }); + this.client.connect(); + this.client.on("connect", next); + }).bind(this)); + }, + + tearDown: function(next){ + this.client.close(); + this.client.on("close", (function(){ + this.server.close(next); + }).bind(this)); + }, + + "List mailboxes with empty LSUB": function(test){ + this.client.listMailboxes(function(err, mailboxes){ + test.ifError(err); + test.equal(mailboxes.length, 3); + test.equal(mailboxes[0].path, "INBOX"); + test.equal(mailboxes[1].path, "TRASH"); + test.equal(mailboxes[2].name, "SENT"); + test.done(); + }); + } +}; From 5419ff988c2f1bcb485254533bccf0cea6bc29cc Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Fri, 3 Jan 2014 10:22:07 +0200 Subject: [PATCH 081/106] Added NPM version badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 446a2b8..63d5443 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ The project consists of two major parts * IMAP control for accessing mailboxes (under construction) [![Build Status](https://secure.travis-ci.org/andris9/inbox.png)](http://travis-ci.org/andris9/inbox) +[![NPM version](https://badge.fury.io/js/inbox.png)](http://badge.fury.io/js/inbox) + ## Installation From fbe196b7ebdf5ab10f26a3cc8007ad969642395e Mon Sep 17 00:00:00 2001 From: Jakob Gillich Date: Fri, 3 Jan 2014 10:21:39 +0100 Subject: [PATCH 082/106] Fix repository url --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index aec2856..3ac237d 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,10 @@ "scripts": { "test": "nodeunit test" }, + "homepage": "https://github.com/andris9/inbox", "repository": { "type": "git", - "url": "https://github.com/andris9/inbox" + "url": "https://github.com/andris9/inbox.git" }, "license": "MIT" } From 59bcd874064a1508f7d145b4fb305e8c2a743d30 Mon Sep 17 00:00:00 2001 From: Felix Hammerl Date: Sat, 11 Jan 2014 23:15:21 +0100 Subject: [PATCH 083/106] add bodystructure information --- lib/client.js | 132 +++++++++++++++++++++++++++++++++++++++++++++++++- test/inbox.js | 43 ++++++++++++---- 2 files changed, 164 insertions(+), 11 deletions(-) diff --git a/lib/client.js b/lib/client.js index 2f54d6c..5f11114 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1334,6 +1334,10 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ message.modSeq = Number(dataObject.MODSEQ); } + if(dataObject["BODYSTRUCTURE"]){ + message.bodystructure = this._parseBodystructure(dataObject["BODYSTRUCTURE"]); + } + var messageTypes = [], messageTypeMap = { "Drafts": "Draft", @@ -1501,6 +1505,130 @@ IMAPClient.prototype._formatEnvelopeAddress = function(address){ }; }; +/** + * Parses bodystructure of an IMAP message according to http://tools.ietf.org/html/rfc3501#section-7.4.2 + * parsed bodystructures will look something like this + * { + * 'type': 'multipart/mixed', + * 1: { + * 'type': 'multipart/alternative', + * 1: { + * 'type': 'text/plain', + * params: { + * 'charset': 'utf-8' + * }, + * encoding: '7bit', + * size: 50, + * }, + * 2: { + * 'type': 'text/html', + * params: { + * 'charset': 'utf-8' + * }, + * encoding: '7bit', + * size: 158, + * } + * }, + * 2: { + * 'type': 'application/octet-stream', + * params: { + * 'name': 'foobar.md' + * }, + * encoding: 'base64', + * size: 286, + * disposition: [{ + * 'attachment': { + * 'filename': 'foobar.md' + * } + * }] + * } + * } + * + * @param {Array} bs Array containing the raw bodystructure array + * @return {Object} Parsed bodystructure, see comment below + */ +IMAPClient.prototype._parseBodystructure = function(bs, parentBodypart) { + var self = this; + + if (typeof bs === 'string') { + // type information for multipart/alternative or multipart/mixed + return 'multipart/' + bs.toLowerCase(); + } + + if (!Array.isArray(bs)) { + // if it is not the information on which type of multipart/* + // we've got here, or an array containing valuable information, + // it is just imap noise + return; + } + + if (!Array.isArray(bs[0]) && typeof bs[0] === 'string' && bs.length >= 10) { + // we've got a single part, usually a text/plain, text/html or attachment part + var currentPart = {}; + currentPart.part = parentBodypart || '1'; + currentPart.type = (bs[0] + '/' + bs[1]).toLowerCase(); + currentPart.parameters = {}; + if (bs[2]) { + // the parameters are a key/value list + var parametersIndex = 0; + while(parametersIndex < bs[2].length) { + currentPart.parameters[bs[2][parametersIndex].toLowerCase()] = bs[2][parametersIndex + 1].toLowerCase(); + parametersIndex += 2; + } + } + currentPart.encoding = bs[5].toLowerCase(); + currentPart.size = parseInt(bs[6], 10); + if (bs[8]) { + currentPart.disposition = []; + if (Array.isArray(bs[8][0])) { + bs[8].forEach(function(rawAttachment){ + if (!rawAttachment) { + return; + } + + currentPart.disposition.push(parseAttachment(rawAttachment)); + }); + } else { + currentPart.disposition.push(parseAttachment(bs[8])); + } + } + + return currentPart; + } + + if (Array.isArray(bs[0])) { + // we have got a multipart/* message + var bodypartsCounter = 1, parsedBodystructure = {}, + parsedPart, partIdentifier; + + bs.forEach(function(rawPart) { + partIdentifier = (parentBodypart ? (parentBodypart + '.') : '') + bodypartsCounter; + parsedPart = self._parseBodystructure(rawPart, partIdentifier); + if (typeof parsedPart === 'string') { + parsedBodystructure.type = parsedPart; + } else if (typeof parsedPart === 'object') { + parsedBodystructure[bodypartsCounter] = parsedPart; + bodypartsCounter++; + } + }); + + return parsedBodystructure; + } + + // helper function to parse attachments + function parseAttachment(rawAttachment) { + var parsedAttachment = {}; + + parsedAttachment.type = rawAttachment[0].toLowerCase(); + if (rawAttachment[1]) { + // attachment filename, not present in inline attachments + parsedAttachment[rawAttachment[1][0].toLowerCase()] = rawAttachment[1][1]; + } + + return parsedAttachment; + } +}; + /** * Convert from IMAP UTF7 to UTF-8 - useful for mailbox names */ @@ -1516,7 +1644,7 @@ IMAPClient.prototype._checkNewMail = function(){ return; } - this._send("UID FETCH " + this._selectedMailbox.UIDNext + ":* (UID FLAGS ENVELOPE" + + this._send("UID FETCH " + this._selectedMailbox.UIDNext + ":* (UID BODYSTRUCTURE FLAGS ENVELOPE" + (this._capabilities.indexOf("X-GM-EXT-1") >= 0 ? " X-GM-LABELS X-GM-THRID" : "") + (this._capabilities.indexOf("CONDSTORE") >= 0 ? " MODSEQ" : "") + ")", (function(){ @@ -1645,7 +1773,7 @@ IMAPClient.prototype.listMessages = function(from, limit, extendedOptions, callb this._send( "FETCH " + from + ":" + to + - " (UID FLAGS ENVELOPE" + + " (UID BODYSTRUCTURE FLAGS ENVELOPE" + (this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"") + (this._capabilities.indexOf("CONDSTORE") >= 0 ? " MODSEQ" : "") + ")" + diff --git a/test/inbox.js b/test/inbox.js index a32f892..76745d2 100644 --- a/test/inbox.js +++ b/test/inbox.js @@ -23,7 +23,9 @@ module.exports["Inbox tests"] = { "\r\n"+ "World 4!"}, {raw: "Subject: hello 5\r\n\r\nWorld 5!"}, - {raw: "Subject: hello 6\r\n\r\nWorld 6!"} + {raw: "Subject: hello 6\r\n\r\nWorld 6!"}, + {raw: "Content-Type: text/plain; charset=utf-8\r\nContent-Transfer-Encoding: quoted-printable\r\nMIME-Version: 1.0\r\n\r\nwow. very mail. such bodystructure."}, + {raw: "Content-Type: multipart/alternative;\r\n boundary=\"=_BOUNDARY_BOUNDARY_BOUNDARY_\";\r\n charset=\"UTF-8\"\r\nMIME-Version: 1.0\r\nSender: \"FOOBAR\" \r\n\r\nThis is a multi-part message in MIME format\r\n\r\n--=_BOUNDARY_BOUNDARY_BOUNDARY_\r\nContent-Type: text/plain\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\nFOOFOOFOOFOO\r\n\r\n\r\n--=_BOUNDARY_BOUNDARY_BOUNDARY_\r\nContent-Type: text/html\r\nContent-Transfer-Encoding: quoted-printable\r\n\r\n\r\n\r\n \r\n \r\n STUFF\r\n \r\n \r\n

stuff

\r\n \r\n\r\n\r\n--=_BOUNDARY_BOUNDARY_BOUNDARY_--"} ] }, "": { @@ -83,9 +85,9 @@ module.exports["Inbox tests"] = { "Open mailbox": function(test){ this.client.openMailbox("INBOX", function(err, mailbox){ test.ifError(err); - test.equal(mailbox.count, 6); + test.equal(mailbox.count, 8); test.equal(mailbox.UIDValidity, "1"); - test.equal(mailbox.UIDNext, "7"); + test.equal(mailbox.UIDNext, "9"); test.done(); }); }, @@ -111,12 +113,35 @@ module.exports["Inbox tests"] = { test.ifError(err); this.client.listMessages(-100, function(err, messages){ test.ifError(err); - test.equal(messages.length, 6); + test.equal(messages.length, 8); for(var i = 0; i < messages.length; i++) { test.equal(messages[i].UIDValidity, 1); test.equal(messages[i].UID, i+1); } test.equal(messages[3].from.address, "sender@example.com"); + test.deepEqual(messages[6].bodystructure, { part: '1', + type: 'text/plain', + parameters: { charset: 'utf-8' }, + encoding: 'quoted-printable', + size: 35 + }); + test.deepEqual(messages[7].bodystructure, { + '1': { + part: '1', + type: 'text/plain', + parameters: {}, + encoding: 'quoted-printable', + size: 16 + }, + '2': { + part: '2', + type: 'text/html', + parameters: {}, + encoding: 'quoted-printable', + size: 248 + }, + type: 'multipart/alternative' + }); test.done(); }); }).bind(this)); @@ -127,7 +152,7 @@ module.exports["Inbox tests"] = { test.ifError(err); this.client.listFlags(-100, function(err, messages){ test.ifError(err); - test.equal(messages.length, 6); + test.equal(messages.length, 8); for(var i = 0; i < messages.length; i++) { test.equal(messages[i].flags.length, i === 1 ? 1 : 0); } @@ -210,12 +235,12 @@ module.exports["Inbox tests"] = { "Store message": function(test){ this.client.openMailbox("INBOX", (function(err, mailbox){ test.ifError(err); - test.equal(mailbox.count, 6); + test.equal(mailbox.count, 8); this.client.storeMessage("Subject: hello 7\r\n\r\nWorld 7!", ["\\Seen"], (function(err, params){ test.ifError(err); test.equal(params.UID, mailbox.UIDNext); this.client.openMailbox("INBOX", function(err, mailbox){ - test.equal(mailbox.count, 7); + test.equal(mailbox.count, 9); test.done(); }); }).bind(this)); @@ -240,12 +265,12 @@ module.exports["Inbox tests"] = { "Delete message": function(test){ this.client.openMailbox("INBOX", (function(err, mailbox){ test.ifError(err); - test.equal(mailbox.count, 6); + test.equal(mailbox.count, 8); this.client.deleteMessage(6, (function(err){ test.ifError(err); this.client.openMailbox("INBOX", function(err, mailbox){ test.ifError(err); - test.equal(mailbox.count, 5); + test.equal(mailbox.count, 7); test.done(); }); }).bind(this)); From c311766a999ef501d49da809ae335206ea35acb0 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Fri, 17 Jan 2014 10:10:14 +0200 Subject: [PATCH 084/106] Added SEARCH support --- README.md | 426 +++++++++++++++++++++++++++---------------------- lib/client.js | 96 ++++++++++- test/search.js | 146 +++++++++++++++++ 3 files changed, 466 insertions(+), 202 deletions(-) create mode 100644 test/search.js diff --git a/README.md b/README.md index 63d5443..82603e9 100644 --- a/README.md +++ b/README.md @@ -23,13 +23,13 @@ Install from npm Use **inbox** module ```javascript - var inbox = require("inbox"); +var inbox = require("inbox"); ``` ### Create new IMAP connection Create connection object with ```javascript - inbox.createConnection(port, host, options) +inbox.createConnection(port, host, options) ``` where @@ -48,76 +48,76 @@ where Example: ```javascript - var client = inbox.createConnection(false, "imap.gmail.com", { - secureConnection: true, - auth:{ - user: "test.nodemailer@gmail.com", - pass: "Nodemailer123" - } - }); +var client = inbox.createConnection(false, "imap.gmail.com", { + secureConnection: true, + auth:{ + user: "test.nodemailer@gmail.com", + pass: "Nodemailer123" + } +}); ``` Or for login with XOAUTH2 (see examples/xoauth2) ```javascript - // XOAUTH2 - var client = inbox.createConnection(false, "imap.gmail.com", { - secureConnection: true, - auth:{ - XOAuth2:{ - user: "example.user@gmail.com", - clientId: "8819981768.apps.googleusercontent.com", - clientSecret: "{client_secret}", - refreshToken: "1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI", - accessToken: "vF9dft4qmTc2Nvb3RlckBhdHRhdmlzdGEuY29tCg==", - timeout: 3600 - } +// XOAUTH2 +var client = inbox.createConnection(false, "imap.gmail.com", { + secureConnection: true, + auth:{ + XOAuth2:{ + user: "example.user@gmail.com", + clientId: "8819981768.apps.googleusercontent.com", + clientSecret: "{client_secret}", + refreshToken: "1/xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI", + accessToken: "vF9dft4qmTc2Nvb3RlckBhdHRhdmlzdGEuY29tCg==", + timeout: 3600 } - }); + } +}); ``` Or for login with XOAUTH (see examples/xoauth-3lo.js and examples/xoauth-2lo.js) ```javascript - // 3-legged- oauth - var client = inbox.createConnection(false, "imap.gmail.com", { - secureConnection: true, - auth:{ - XOAuthToken: inbox.createXOAuthGenerator({ - user: "test.nodemailer@gmail.com", - token: "1/Gr2OVA2Ol64fNyjZCns-bkRau5eLisbdlEa_HSuTaEk", - tokenSecret: "ymFpseHtEnrIsuL8Ppbfnnk3" - }) - } - }); +// 3-legged- oauth +var client = inbox.createConnection(false, "imap.gmail.com", { + secureConnection: true, + auth:{ + XOAuthToken: inbox.createXOAuthGenerator({ + user: "test.nodemailer@gmail.com", + token: "1/Gr2OVA2Ol64fNyjZCns-bkRau5eLisbdlEa_HSuTaEk", + tokenSecret: "ymFpseHtEnrIsuL8Ppbfnnk3" + }) + } +}); ``` With 2-legged OAuth, consumerKey and consumerSecret need to have proper values, vs 3-legged OAuth where both default to "anonymous". ```javascript - // 2-legged- oauth - var client = inbox.createConnection(false, "imap.gmail.com", { - secureConnection: true, - auth:{ - XOAuthToken: inbox.createXOAuthGenerator({ - user: "test.nodemailer@gmail.com", - requestorId: "test.nodemailer@gmail.com", - consumerKey: "1/Gr2OVA2Ol64fNyjZCns-bkRau5eLisbdlEa_HSuTaEk", - consumerSecret: "ymFpseHtEnrIsuL8Ppbfnnk3" - }) - } - }); +// 2-legged- oauth +var client = inbox.createConnection(false, "imap.gmail.com", { + secureConnection: true, + auth:{ + XOAuthToken: inbox.createXOAuthGenerator({ + user: "test.nodemailer@gmail.com", + requestorId: "test.nodemailer@gmail.com", + consumerKey: "1/Gr2OVA2Ol64fNyjZCns-bkRau5eLisbdlEa_HSuTaEk", + consumerSecret: "ymFpseHtEnrIsuL8Ppbfnnk3" + }) + } +}); ``` Once the connection object has been created, use connect() to create the actual connection. ```javascript - client.connect(); +client.connect(); ``` When the connection has been successfully established a 'connect' event is emitted. ```javascript - client.on("connect", function(){ - console.log("Successfully connected to server"); - }); +client.on("connect", function(){ + console.log("Successfully connected to server"); +}); ``` ### Logout and disconnect @@ -125,19 +125,17 @@ When the connection has been successfully established a 'connect' event is emitt Logout from IMAP and close NET connection. ```javascript - client.close(); - - client.on('close', function (){ +client.close(); +client.on('close', function (){ console.log('DISCONNECTED!'); - }); - +}); ``` ### List available mailboxes To list the available mailboxes use ```javascript - client.listMailboxes(callback) +client.listMailboxes(callback) ``` Where @@ -159,15 +157,15 @@ Additionally mailboxes have the following methods Example: ```javascript - client.listMailboxes(function(error, mailboxes){ - for(var i=0, len = mailboxes.length; i', - inReplyTo: '<4FB16D5A.30808@gmail.com>', - references: ['<4FB16D5A.30808@gmail.com>','<1299323903.19454@foo.bar>'] +[ + { + // if uidvalidity changes, all uid values are void! + UIDValidity: '664399135', + + // uid value of the message + UID: 52, + + // message flags (Array) + flags: [ '\\Flagged', '\\Seen' ], + + // date of the message (Date object) + date: Wed, 25 Apr 2012 12:23:05 GMT, + + title: 'This is a message, may contain unicode symbols', + + // single "from:" address + from: { + name: 'Andris Reinman', + address: 'andris.reinman@gmail.com' }, - ... - ] + + // an array of "to:" addresses + to: [ + { + name: 'test nodemailer', + address: 'test.nodemailer@gmail.com' + } + ], + + // an array of "cc:" addresses + cc: [ + { + name: 'test nodemailer', + address: 'test.nodemailer@gmail.com' + } + ], + + messageId: '<04541AB5-9FBD-4255-81AA-18FE67CB97E5@gmail.com>', + inReplyTo: '<4FB16D5A.30808@gmail.com>', + references: ['<4FB16D5A.30808@gmail.com>','<1299323903.19454@foo.bar>'] + }, + ... +] ``` **NB!** If some properties are not present in a message, it may be not included @@ -295,7 +293,7 @@ there is no "cc" field in the message object. As a shorthand listing, you can also list only UID and Flags pairs ```javascript - client.listFlags(from[, limit], callback) +client.listFlags(from[, limit], callback) ``` Where @@ -306,36 +304,36 @@ Where Example ```javascript - // list flags for newest 10 messages - client.listFlags(-10, function(err, messages){ - messages.forEach(function(message){ - console.log(message.UID, message.flags); - }); +// list flags for newest 10 messages +client.listFlags(-10, function(err, messages){ + messages.forEach(function(message){ + console.log(message.UID, message.flags); }); +}); ``` Example output for a message listing ```javascript - [ - { - // if uidvalidity changes, all uid values are void! - UIDValidity: '664399135', +[ + { + // if uidvalidity changes, all uid values are void! + UIDValidity: '664399135', - // uid value of the message - UID: 52, + // uid value of the message + UID: 52, - // message flags (Array) - flags: [ '\\Flagged', '\\Seen' ] - }, - ... - ] + // message flags (Array) + flags: [ '\\Flagged', '\\Seen' ] + }, + ... +] ``` ### Fetch message details To fetch message data (flags, title, etc) for a specific message, use ```javascript - client.fetchData(uid, callback) +client.fetchData(uid, callback) ``` Where @@ -345,9 +343,9 @@ Where Example ```javascript - client.fetchData(123, function(error, message){ - console.log(message.flags); - }); +client.fetchData(123, function(error, message){ + console.log(message.flags); +}); ``` ### Fetch message contents @@ -355,7 +353,7 @@ Example Message listing only retrieves the envelope part of the message. To get the full RFC822 message body you need to fetch the message. ```javascript - var messageStream = client.createMessageStream(uid) +var messageStream = client.createMessageStream(uid) ``` Where @@ -364,19 +362,59 @@ Where Example (output message contents to console) ```javascript - client.createMessageStream(123).pipe(process.stdout, {end: false}); +client.createMessageStream(123).pipe(process.stdout, {end: false}); ``` **NB!** If the opened mailbox is not in read-only mode, the message will be automatically marked as read (\Seen flag is set) when the message is fetched. +### Searching for messages + +You can search for messages with + +```javascript +client.search(query[, isUID], callback) +``` + +Where + + * **query** is the search term as an object + * **isUID** is an optional boolean value - if set to true perform `UID SEARCH` instead of `SEARCH` + * **callback** is the callback function with error object and an array of matching seq or UID numbers + +**Queries** + +Queries are composed as objects where keys are search terms and values are term arguments. +Only strings, numbers and Dates are used. If the value is an array, the members of it are processed separately +(use this for terms that require multiple params). If the value is a Date, it is converted to the form of "01-Jan-1970". +Subqueries (OR, NOT) are made up of objects + +Examples: + +```javascript +// SEARCH UNSEEN +query = {unseen: true} +// SEARCH KEYWORD "flagname" +query = {keyword: "flagname"} +// SEARCH HEADER "subject" "hello world" +query = {header: ["subject", "hello world"]}; +// SEARCH UNSEEN HEADER "subject" "hello world" +query = {unseen: true, header: ["subject", "hello world"]}; +// SEARCH OR UNSEEN SEEN +query = {or: {unseen: true, seen: true}}; +// SEARCH UNSEEN NOT SEEN +query = {unseen: true, not: {seen: true}} +``` + +Returned list is already sorted and all values are numbers. + ### Message flags You can add and remove message flags like `\Seen` or `\Answered` with `client.addFlags()` and `client.removeFlags()` **List flags** ```javascript - client.fetchFlags(uid, callback) +client.fetchFlags(uid, callback) ``` Where @@ -386,7 +424,7 @@ Where **Add flags** ```javascript - client.addFlags(uid, flags, callback) +client.addFlags(uid, flags, callback) ``` Where @@ -397,7 +435,7 @@ Where **Remove flags** ```javascript - client.removeFlags(uid, flags, callback) +client.removeFlags(uid, flags, callback) ``` Where @@ -408,22 +446,22 @@ Where Example ```javascript - // add \Seen and \Flagged flag to a message - client.addFlags(123, ["\\Seen", "\\Flagged"], function(err, flags){ - console.log("Current flags for a message: ", flags); - }); +// add \Seen and \Flagged flag to a message +client.addFlags(123, ["\\Seen", "\\Flagged"], function(err, flags){ + console.log("Current flags for a message: ", flags); +}); - // remove \Flagged flag from a message - client.removeFlags(123, ["\\Flagged"], function(err, flags){ - console.log("Current flags for a message: ", flags); - }); +// remove \Flagged flag from a message +client.removeFlags(123, ["\\Flagged"], function(err, flags){ + console.log("Current flags for a message: ", flags); +}); ``` ### Upload a message You can upload a message to current mailbox with `client.storeMessage()` ```javascript - client.storeMessage(message[, flags], callback) +client.storeMessage(message[, flags], callback) ``` Where @@ -434,9 +472,9 @@ Where Example ```javascript - client.storeMessage("From: ....", ["\\Seen"], function(err, params){ - console.log(err || params.UIDValidity +", "+ params.UID); - }); +client.storeMessage("From: ....", ["\\Seen"], function(err, params){ + console.log(err || params.UIDValidity +", "+ params.UID); +}); ``` When adding a message to the mailbox, the new message event is also raised after @@ -446,7 +484,7 @@ the mail has been stored. You can copy a message from the current mailbox to a selected one with `client.copyMessage()` ```javascript - client.copyMessage(uid, destination, callback) +client.copyMessage(uid, destination, callback) ``` Where @@ -457,16 +495,16 @@ Where Example ```javascript - client.copyMessage(123, "[GMail]/Junk", function(err){ - console.log(err || "success, copied to junk"); - }); +client.copyMessage(123, "[GMail]/Junk", function(err){ + console.log(err || "success, copied to junk"); +}); ``` ### Move a message You can move a message from current mailbox to a selected one with `client.moveMessage()` ```javascript - client.moveMessage(uid, destination, callback) +client.moveMessage(uid, destination, callback) ``` Where @@ -477,16 +515,16 @@ Where Example ```javascript - client.moveMessage(123, "[GMail]/Junk", function(err){ - console.log(err || "success, moved to junk"); - }); +client.moveMessage(123, "[GMail]/Junk", function(err){ + console.log(err || "success, moved to junk"); +}); ``` ### Delete a message You can delete a message from current mailbox with `client.deleteMessage()` ```javascript - client.deleteMessage(uid, callback) +client.deleteMessage(uid, callback) ``` Where @@ -496,48 +534,48 @@ Where Example ```javascript - client.deleteMessage(123, function(err){ - console.log(err || "success, message deleted"); - }); +client.deleteMessage(123, function(err){ + console.log(err || "success, message deleted"); +}); ``` ### Wait for new messages You can listen for new incoming e-mails with event "new" ```javascript - client.on("new", function(message){ - console.log("New incoming message " + message.title); - }); +client.on("new", function(message){ + console.log("New incoming message " + message.title); +}); ``` ## Complete example Listing newest 10 messages: ```javascript - var inbox = require("inbox"); +var inbox = require("inbox"); - var client = inbox.createConnection(false, "imap.gmail.com", { - secureConnection: true, - auth:{ - user: "test.nodemailer@gmail.com", - pass: "Nodemailer123" - } - }); +var client = inbox.createConnection(false, "imap.gmail.com", { + secureConnection: true, + auth:{ + user: "test.nodemailer@gmail.com", + pass: "Nodemailer123" + } +}); - client.connect(); +client.connect(); - client.on("connect", function(){ - client.openMailbox("INBOX", function(error, info){ - if(error) throw error; +client.on("connect", function(){ + client.openMailbox("INBOX", function(error, info){ + if(error) throw error; - client.listMessages(-10, function(err, messages){ - messages.forEach(function(message){ - console.log(message.UID + ": " + message.title); - }); + client.listMessages(-10, function(err, messages){ + messages.forEach(function(message){ + console.log(message.UID + ": " + message.title); }); - }); + }); +}); ``` ## License diff --git a/lib/client.js b/lib/client.js index 5f11114..da912d5 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1141,14 +1141,6 @@ IMAPClient.prototype._handlerUntaggedIdle = function(){ this._processCommandQueue(); }; -/** - * Handle search responses, not yet implemented - * TODO: andle search responses - */ -IMAPClient.prototype._handlerUntaggedSearch = function(){ - return false; -}; - /** * Handle untagged FETCH responses, these have data about individual messages. * @@ -1174,6 +1166,17 @@ IMAPClient.prototype._handlerUntaggedFetch = function(list){ } }; +/** + * Handle untagged SERACH responses, this is a list of seq or uid values + * + * @param {Array} list Params about a message + */ +IMAPClient.prototype._handlerUntaggedSearch = function(list){ + if(this._collectMailList){ + this._mailList = this._mailList.concat(list.map(Number)); + } +}; + /** * Timeout function for idle mode - if sufficient time has passed, break the * idle and run NOOP. After this, re-enter IDLE @@ -2350,6 +2353,83 @@ IMAPClient.prototype.idle = function(){ } }; +/** + * Lists seq or uid values for a search. Query is an object where keys are query terms and + * values are params. Use arrays for multiple terms or true for just the key. + * + * connection.search({new: true, header: ["subject", "test"]}, function(err, list)) + * + * @param {Object} Search query + * @param {Boolean} [isUID] If true perform an UID search + * @param {Function} callback Callback function to run with the listed envelopes + */ +IMAPClient.prototype.search = function(query, isUid, callback){ + if(!callback && typeof isUid == "function"){ + callback = isUid; + isUid = undefined; + } + + var queryType = isUid ? "UID SEARCH" : "SEARCH", + self = this, + + buildTerm = function(query){ + return Object.keys(query).map(function(key){ + var term = key.toUpperCase(), + params = [], + escapeDate = function(date){ + return self._escapeString(date.toUTCString().replace(/^\w+, (\d+) (\w+) (\d+).*/, "$1-$2-$3")); + }, + escapeParam = function(param){ + var list = []; + if(typeof param == "number"){ + list.push(String(param)); + }else if(typeof param == "string"){ + list.push(self._escapeString(param)); + }else if(Object.prototype.toString.call(param) == "[object Date]"){ + list.push(escapeDate(param)); + }else if(Array.isArray(param)){ + param.map(escapeParam).forEach(function(p){ + if(typeof p == "string"){ + list.push(p); + } + }); + }else if(typeof param == "object"){ + return buildTerm(param); + } + return list.join(" "); + }; + + [].concat(query[key] || []).forEach(function(param){ + var param = escapeParam(param); + if(param){ + params.push(param); + } + }); + + return term + (params.length ? " " + params.join(" ") : ""); + }).join(" "); + }, + + queryTerm = buildTerm(query); + + this._collectMailList = true; + this._mailList = []; + + this._send(queryType + (queryTerm ? " " + queryTerm : ""), (function(status){ + this._collectMailList = false; + + if(typeof callback != "function"){ + return; + } + + if(status == "OK"){ + callback(null, this._mailList.sort(function(a, b){return a-b;})); + }else{ + callback(new Error("Error searching messages")); + } + }).bind(this)); +}; + /** * Closes the socket to the server * // FIXME - should LOGOUT first! diff --git a/test/search.js b/test/search.js new file mode 100644 index 0000000..f9423c6 --- /dev/null +++ b/test/search.js @@ -0,0 +1,146 @@ +"use strict"; + +var inbox = require(".."), + hoodiecrow = require("hoodiecrow"); + +var IMAP_PORT = 1143; + +module.exports = { + setUp: function(next){ + this.server = hoodiecrow({ + storage: { + "INBOX":{ + messages: [ + {raw: "Subject: hello 1\r\n\r\nWorld 1!", uid: 45, internaldate: new Date("2009-1-1")}, + {raw: "Subject: hello 2\r\n\r\nWorld 2!", flags: ["test", "\\Seen"], uid: 48}, + {raw: "Subject: hello 3\r\n\r\nWorld 3!", flags: ["test"], uid: 49}, + {raw: "Subject: test\r\n\r\nWorld 1!", flags: ["\\Seen"], uid: 50, internaldate: new Date("2009-1-1")}, + ] + } + }, + debug: false + }); + this.server.listen(IMAP_PORT, (function(){ + this.client = inbox.createConnection(IMAP_PORT, "localhost", { + auth:{ + user: "testuser", + pass: "testpass" + }, + debug: false + }); + this.client.connect(); + this.client.on("connect", next); + }).bind(this)); + }, + + tearDown: function(next){ + this.client.close(); + this.client.on("close", (function(){ + this.server.close(next); + }).bind(this)); + }, + + "Boolean search": function(test){ + this.client.openMailbox("INBOX", (function(err, mailbox){ + this.client.search({ + unseen: true + }, function(err, messages){ + test.ifError(err); + test.deepEqual(messages, [1, 3]); + test.done(); + }); + }).bind(this)); + }, + + "String search": function(test){ + this.client.openMailbox("INBOX", (function(err, mailbox){ + this.client.search({ + keyword: "test" + }, function(err, messages){ + test.ifError(err); + test.deepEqual(messages, [2, 3]); + test.done(); + }); + }).bind(this)); + }, + + "String UID search": function(test){ + this.client.openMailbox("INBOX", (function(err, mailbox){ + this.client.search({ + keyword: "test" + }, true, function(err, messages){ + test.ifError(err); + test.deepEqual(messages, [48, 49]); + test.done(); + }); + }).bind(this)); + }, + + "Array search": function(test){ + this.client.openMailbox("INBOX", (function(err, mailbox){ + this.client.search({ + header: ["subject", "hello"] + }, function(err, messages){ + test.ifError(err); + test.deepEqual(messages, [1, 2, 3]); + test.done(); + }); + }).bind(this)); + }, + + "Date search": function(test){ + this.client.openMailbox("INBOX", (function(err, mailbox){ + this.client.search({ + sentsince: new Date("2010-01-01") + }, function(err, messages){ + test.ifError(err); + test.deepEqual(messages, [2, 3]); + test.done(); + }); + }).bind(this)); + }, + + "AND search": function(test){ + this.client.openMailbox("INBOX", (function(err, mailbox){ + this.client.search({ + senton: new Date("2009-01-01"), + unseen: true + }, function(err, messages){ + test.ifError(err); + test.deepEqual(messages, [1]); + test.done(); + }); + }).bind(this)); + }, + + "OR search": function(test){ + this.client.openMailbox("INBOX", (function(err, mailbox){ + this.client.search({ + or: { + senton: new Date("2009-01-01"), + unseen: true + } + }, function(err, messages){ + test.ifError(err); + test.deepEqual(messages.sort(function(a,b){return a - b}), [1, 3, 4]); + test.done(); + }); + }).bind(this)); + }, + + "NOT search": function(test){ + this.client.openMailbox("INBOX", (function(err, mailbox){ + this.client.search({ + unseen: true, + not: { + senton: new Date("2009-01-01") + } + }, function(err, messages){ + test.ifError(err); + test.deepEqual(messages, [3]); + test.done(); + }); + }).bind(this)); + } +}; + From 9cfbdee6990d4697746b8c0777ef0173af8cdb5f Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Fri, 17 Jan 2014 10:10:46 +0200 Subject: [PATCH 085/106] bumped version to 1.1.44 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3ac237d..4014422 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.43", + "version": "1.1.44", "author": "Andris Reinman", "maintainers": [ { From e4bfbf8d6ccd6875a420a51a7c57cc9839ebe973 Mon Sep 17 00:00:00 2001 From: Felix Hammerl Date: Mon, 13 Jan 2014 11:39:38 +0100 Subject: [PATCH 086/106] git merged a35670e from whiteout-io --- README.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 82603e9..61f3fdc 100644 --- a/README.md +++ b/README.md @@ -239,6 +239,7 @@ client.listMessages(-10, function(err, messages){ Example output for a message listing ```javascript +<<<<<<< HEAD [ { // if uidvalidity changes, all uid values are void! @@ -279,7 +280,26 @@ Example output for a message listing messageId: '<04541AB5-9FBD-4255-81AA-18FE67CB97E5@gmail.com>', inReplyTo: '<4FB16D5A.30808@gmail.com>', - references: ['<4FB16D5A.30808@gmail.com>','<1299323903.19454@foo.bar>'] + references: ['<4FB16D5A.30808@gmail.com>','<1299323903.19454@foo.bar>'], + + // bodystructure of the message + bodystructure: { + '1': { + part: '1', + type: 'text/plain', + parameters: {}, + encoding: 'quoted-printable', + size: 16 + }, + '2': { + part: '2', + type: 'text/html', + parameters: {}, + encoding: 'quoted-printable', + size: 248 + }, + type: 'multipart/alternative' + } }, ... ] From 0fa12c7161a27af84fd82f871c33b5d67794b5f2 Mon Sep 17 00:00:00 2001 From: Felix Hammerl Date: Tue, 14 Jan 2014 13:40:20 +0100 Subject: [PATCH 087/106] fix bodystructure parser for text/*, ignore message/rfc822 --- lib/client.js | 30 ++++++++++++++++++++++++------ test/inbox.js | 9 ++++++--- 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/lib/client.js b/lib/client.js index da912d5..781330e 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1522,6 +1522,7 @@ IMAPClient.prototype._formatEnvelopeAddress = function(address){ * }, * encoding: '7bit', * size: 50, + * lines: 10, * }, * 2: { * 'type': 'text/html', @@ -1567,9 +1568,13 @@ IMAPClient.prototype._parseBodystructure = function(bs, parentBodypart) { if (!Array.isArray(bs[0]) && typeof bs[0] === 'string' && bs.length >= 10) { // we've got a single part, usually a text/plain, text/html or attachment part - var currentPart = {}; + var dispositionIndex = 8, + currentPart = {}, type, subtype; + currentPart.part = parentBodypart || '1'; - currentPart.type = (bs[0] + '/' + bs[1]).toLowerCase(); + type = bs[0].toLowerCase(); + subtype = bs[1].toLowerCase(); + currentPart.type = type + '/' + subtype; currentPart.parameters = {}; if (bs[2]) { // the parameters are a key/value list @@ -1581,10 +1586,23 @@ IMAPClient.prototype._parseBodystructure = function(bs, parentBodypart) { } currentPart.encoding = bs[5].toLowerCase(); currentPart.size = parseInt(bs[6], 10); - if (bs[8]) { + + if (type === 'message' && subtype === 'rfc822') { + // parsing of envelope and body structure information for message/rfc882 mails is not supported, + // because there are IMAP servers which violate rfc 3501 for message/rfc882, for example gmail. + return currentPart; + } + + if (type === 'text') { + // text/* body parts have an additional field for the body size in lines in its content transfer encoding. + currentPart.lines = parseInt(bs[7], 10); + dispositionIndex = 9; + } + + if (bs[dispositionIndex]) { currentPart.disposition = []; - if (Array.isArray(bs[8][0])) { - bs[8].forEach(function(rawAttachment){ + if (Array.isArray(bs[dispositionIndex][0])) { + bs[dispositionIndex].forEach(function(rawAttachment){ if (!rawAttachment) { return; } @@ -1592,7 +1610,7 @@ IMAPClient.prototype._parseBodystructure = function(bs, parentBodypart) { currentPart.disposition.push(parseAttachment(rawAttachment)); }); } else { - currentPart.disposition.push(parseAttachment(bs[8])); + currentPart.disposition.push(parseAttachment(bs[dispositionIndex])); } } diff --git a/test/inbox.js b/test/inbox.js index 76745d2..202be03 100644 --- a/test/inbox.js +++ b/test/inbox.js @@ -123,7 +123,8 @@ module.exports["Inbox tests"] = { type: 'text/plain', parameters: { charset: 'utf-8' }, encoding: 'quoted-printable', - size: 35 + size: 35, + lines: 1 }); test.deepEqual(messages[7].bodystructure, { '1': { @@ -131,14 +132,16 @@ module.exports["Inbox tests"] = { type: 'text/plain', parameters: {}, encoding: 'quoted-printable', - size: 16 + size: 16, + lines: 3 }, '2': { part: '2', type: 'text/html', parameters: {}, encoding: 'quoted-printable', - size: 248 + size: 248, + lines: 12 }, type: 'multipart/alternative' }); From f2149c3c84e0a2052a8f4eceaf5052d0976abe8c Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Fri, 17 Jan 2014 10:19:01 +0200 Subject: [PATCH 088/106] bumped version to 1.1.45 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4014422..f8bddcc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.44", + "version": "1.1.45", "author": "Andris Reinman", "maintainers": [ { From 7d7cfcf2fb9ba1881a202688bf34c427097844c0 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Fri, 17 Jan 2014 11:14:58 +0200 Subject: [PATCH 089/106] Added listMessagesByUID --- README.md | 15 +++++++++++- lib/client.js | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 +- test/inbox.js | 16 +++++++++++++ 4 files changed, 94 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 61f3fdc..632670f 100644 --- a/README.md +++ b/README.md @@ -239,7 +239,6 @@ client.listMessages(-10, function(err, messages){ Example output for a message listing ```javascript -<<<<<<< HEAD [ { // if uidvalidity changes, all uid values are void! @@ -309,6 +308,20 @@ Example output for a message listing in the message object - for example, if there are no "cc:" addresses listed, there is no "cc" field in the message object. +### Listing messages by UID + +You can list messages by UID with + +```javascript +client.listMessages(firstUID, lastUID, callback) +``` + +Where + + * **firstUI** is the UID value to start listing from + * **lastUID** is the UID value to end listing with, can be a number or "*" + * **callback** is the same as with `listMessage` + ### Listing flags As a shorthand listing, you can also list only UID and Flags pairs diff --git a/lib/client.js b/lib/client.js index 781330e..b89a57b 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1741,6 +1741,7 @@ IMAPClient.prototype.getCurrentMailbox = function(){ * * @param {Number} from List from position (0 based) * @param {Number} limit How many messages to fetch, defaults to all from selected position + * @param {String} [extendedOptions] Additional string to add to the FETCH query * @param {Function} callback Callback function to run with the listed envelopes */ IMAPClient.prototype.listMessages = function(from, limit, extendedOptions, callback){ @@ -1813,6 +1814,68 @@ IMAPClient.prototype.listMessages = function(from, limit, extendedOptions, callb }).bind(this)); }; +/** + * Lists message envelopes for selected range. Similar to listMessages but uses UID values + * + * @param {Number} from First UID value + * @param {Number} to Last UID value or "*" + * @param {String} [extendedOptions] Additional string to add to the FETCH query + * @param {Function} callback Callback function to run with the listed envelopes + */ +IMAPClient.prototype.listMessagesByUID = function(from, to, extendedOptions, callback){ + var to; + + from = Number(from) || 0; + + if(typeof extendedOptions == "function" && !callback){ + callback = extendedOptions; + extendedOptions = undefined; + } + + if(typeof to == "function" && !callback){ + callback = to; + to = undefined; + } + + extendedOptions = extendedOptions || ""; + to = Number(to) || "*"; + + if(this._currentState != this.states.SELECTED){ + if(typeof callback == "function"){ + callback(new Error("No mailbox selected")); + } + return; + } + + // Nothing to retrieve + if(!this._selectedMailbox.count){ + return callback(null, []); + } + + this._collectMailList = true; + this._mailList = []; + + this._send( + "UID FETCH " + from + ":" + to + + " (UID BODYSTRUCTURE FLAGS ENVELOPE" + + (this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"") + + (this._capabilities.indexOf("CONDSTORE") >= 0 ? " MODSEQ" : "") + + ")" + + (extendedOptions ? " "+extendedOptions : ""), (function(status){ + this._collectMailList = false; + + if(typeof callback != "function"){ + return; + } + + if(status == "OK"){ + callback(null, this._mailList); + }else{ + callback(new Error("Error fetching list")); + } + }).bind(this)); +}; + /** * Lists flags for selected range. Negative numbers can be used to * count from the end of the list (most recent messages). diff --git a/package.json b/package.json index f8bddcc..eccbf03 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.45", + "version": "1.1.46", "author": "Andris Reinman", "maintainers": [ { diff --git a/test/inbox.js b/test/inbox.js index 202be03..a52dbbe 100644 --- a/test/inbox.js +++ b/test/inbox.js @@ -150,6 +150,22 @@ module.exports["Inbox tests"] = { }).bind(this)); }, + "List messages by UID": function(test){ + this.client.openMailbox("INBOX", (function(err){ + test.ifError(err); + this.client.listMessagesByUID(2, 4, function(err, messages){ + test.ifError(err); + test.equal(messages.length, 3); + for(var i = 0; i < messages.length; i++) { + test.equal(messages[i].UIDValidity, 1); + test.equal(messages[i].UID, i + 2); + } + test.equal(messages[2].from.address, "sender@example.com"); + test.done(); + }); + }).bind(this)); + }, + "List flags": function(test){ this.client.openMailbox("INBOX", (function(err){ test.ifError(err); From ce2a1bf419519bbd1c1499de15e293cf8ee180f7 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Sun, 19 Jan 2014 12:48:27 +0200 Subject: [PATCH 090/106] fix readme docs fir listMessagesByUID --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 632670f..fc8542d 100644 --- a/README.md +++ b/README.md @@ -313,7 +313,7 @@ there is no "cc" field in the message object. You can list messages by UID with ```javascript -client.listMessages(firstUID, lastUID, callback) +client.listMessagesByUID(firstUID, lastUID, callback) ``` Where From 3657d76613f6e4ddb039a03a84b94a83ad9105f7 Mon Sep 17 00:00:00 2001 From: Felix Hammerl Date: Mon, 27 Jan 2014 16:05:28 +0100 Subject: [PATCH 091/106] end stream in command callback --- lib/client.js | 8 +++----- test/inbox.js | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/lib/client.js b/lib/client.js index b89a57b..08e2738 100644 --- a/lib/client.js +++ b/lib/client.js @@ -450,11 +450,6 @@ IMAPClient.prototype._onData = function(chunk){ this._remainder = data; } - if(this._literalStreaming){ - this._messageStream.emit("end"); - this._messageStream.removeAllListeners(); - } - this._currentMode = this.modes.COMMAND; return this._onData(); // rerun with the remainder @@ -2249,6 +2244,9 @@ IMAPClient.prototype.createMessageStream = function(uid){ } } + this._messageStream.emit("end"); + this._messageStream.removeAllListeners(); + this._messageStream = null; }).bind(this), diff --git a/test/inbox.js b/test/inbox.js index a52dbbe..10c47e9 100644 --- a/test/inbox.js +++ b/test/inbox.js @@ -213,6 +213,23 @@ module.exports["Inbox tests"] = { }).bind(this)); }, + "Stream should not be ended prematurely": function(test){ + this.client.openMailbox("INBOX", (function(err){ + test.ifError(err); + + var messageStream = this.client.createMessageStream(1); + messageStream.on("data", function(){}); + messageStream.on("end", (function(){ + this.client.listMessagesByUID(2, 2, function(err, messages){ + test.ifError(err); + + test.equal(messages[0].UID, 2); + test.done(); + }); + }).bind(this)); + }).bind(this)); + }, + "Fetch message flags": function(test){ this.client.openMailbox("INBOX", (function(err){ test.ifError(err); From db1727633deed772e8911f7ff762c2d6166472fb Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 27 Jan 2014 19:27:00 +0200 Subject: [PATCH 092/106] bumped version to 1.1.47 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index eccbf03..7d2eac6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.46", + "version": "1.1.47", "author": "Andris Reinman", "maintainers": [ { From c5580c4d024a8bf445359240e9c43d080ba95022 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Wed, 12 Mar 2014 16:52:10 +0200 Subject: [PATCH 093/106] force utf7 arg to be a string --- lib/client.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/client.js b/lib/client.js index 08e2738..02a2cb1 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1649,7 +1649,7 @@ IMAPClient.prototype._parseBodystructure = function(bs, parentBodypart) { * Convert from IMAP UTF7 to UTF-8 - useful for mailbox names */ IMAPClient.prototype._convertFromUTF7 = function(str){ - return utf7.decode(str); + return utf7.decode((str || "").toString()); }; /** diff --git a/package.json b/package.json index 7d2eac6..93ea049 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.47", + "version": "1.1.48", "author": "Andris Reinman", "maintainers": [ { From 6d95ab3363ef55dabb05a1f09b060f0e64c29940 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 14 Apr 2014 21:46:49 +0300 Subject: [PATCH 094/106] added new opts for listmailboxes --- lib/client.js | 10 +++++----- lib/mailbox.js | 11 ++++++++--- package.json | 2 +- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/client.js b/lib/client.js index 02a2cb1..6002c85 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1542,7 +1542,7 @@ IMAPClient.prototype._formatEnvelopeAddress = function(address){ * }] * } * } - * + * * @param {Array} bs Array containing the raw bodystructure array * @return {Object} Parsed bodystructure, see comment below */ @@ -1556,7 +1556,7 @@ IMAPClient.prototype._parseBodystructure = function(bs, parentBodypart) { if (!Array.isArray(bs)) { // if it is not the information on which type of multipart/* - // we've got here, or an array containing valuable information, + // we've got here, or an array containing valuable information, // it is just imap noise return; } @@ -1608,7 +1608,7 @@ IMAPClient.prototype._parseBodystructure = function(bs, parentBodypart) { currentPart.disposition.push(parseAttachment(bs[dispositionIndex])); } } - + return currentPart; } @@ -1678,8 +1678,8 @@ IMAPClient.prototype._checkNewMail = function(){ * * @param {Function} callback Callback function to run with the mailbox list */ -IMAPClient.prototype.listMailboxes = function(callback){ - this._rootMailbox.listChildren(callback); +IMAPClient.prototype.listMailboxes = function(path, all, callback){ + this._rootMailbox.listChildren(path, all, callback); }; /** diff --git a/lib/mailbox.js b/lib/mailbox.js index d755d26..4169765 100644 --- a/lib/mailbox.js +++ b/lib/mailbox.js @@ -60,15 +60,20 @@ Mailbox.prototype.detectType = function(){ * @param {String} [path] If set, list only selected path info but not the children * @param {Function} callback Callback function to run with the mailbox list */ -Mailbox.prototype.listChildren = function(path, callback){ +Mailbox.prototype.listChildren = function(path, all, callback){ + if(!callback && typeof all == "function"){ + callback = all; + all = undefined; + } + if(!callback && typeof path == "function"){ callback = path; path = undefined; } - var command = "LIST", suffix = ""; + var command = "LIST", suffix = "", wildcard = all ? "*" : "%"; - path = this.client._escapeString(path || (this.path ? this.path + this.delimiter + "%":"%")); + path = this.client._escapeString(path || (this.path ? this.path + this.delimiter + wildcard : wildcard)); if(this.client._capabilities.indexOf("SPECIAL-USE")>=0){ command = "LIST"; diff --git a/package.json b/package.json index 93ea049..b5334be 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.48", + "version": "1.1.49", "author": "Andris Reinman", "maintainers": [ { From 1598e1aef51f68d77a8ba13c2c9a5aed2023cac8 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 14 Apr 2014 22:29:43 +0300 Subject: [PATCH 095/106] include Important --- lib/client.js | 4 +++- package.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/client.js b/lib/client.js index 6002c85..0cf807e 100644 --- a/lib/client.js +++ b/lib/client.js @@ -969,8 +969,10 @@ IMAPClient.prototype._handlerTaggedLsub = function(xinfo, callback, status){ this._mailboxList[j].type = "Sent"; }else if(curXinfo.tags.indexOf("\\Junk")>=0 || curXinfo.tags.indexOf("\\Spam")>=0){ this._mailboxList[j].type = "Junk"; - }else if(curXinfo.tags.indexOf("\\Flagged")>=0 || curXinfo.tags.indexOf("\\Starred")>=0 || curXinfo.tags.indexOf("\\Important")>=0){ + }else if(curXinfo.tags.indexOf("\\Flagged")>=0 || curXinfo.tags.indexOf("\\Starred")>=0){ this._mailboxList[j].type = "Flagged"; + }else if(curXinfo.tags.indexOf("\\Important")>=0){ + this._mailboxList[j].type = "Important"; }else if(curXinfo.tags.indexOf("\\Trash")>=0){ this._mailboxList[j].type = "Trash"; } diff --git a/package.json b/package.json index b5334be..144b68d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.49", + "version": "1.1.50", "author": "Andris Reinman", "maintainers": [ { From a12aab0f409795db154c564be6276fb1a1f531fb Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 14 Apr 2014 22:31:00 +0300 Subject: [PATCH 096/106] include Important --- lib/client.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/client.js b/lib/client.js index 0cf807e..28fe824 100644 --- a/lib/client.js +++ b/lib/client.js @@ -969,7 +969,7 @@ IMAPClient.prototype._handlerTaggedLsub = function(xinfo, callback, status){ this._mailboxList[j].type = "Sent"; }else if(curXinfo.tags.indexOf("\\Junk")>=0 || curXinfo.tags.indexOf("\\Spam")>=0){ this._mailboxList[j].type = "Junk"; - }else if(curXinfo.tags.indexOf("\\Flagged")>=0 || curXinfo.tags.indexOf("\\Starred")>=0){ + }else if(curXinfo.tags.indexOf("\\Flagged")>=0 || curXinfo.tags.indexOf("\\Starred")>=0)){ this._mailboxList[j].type = "Flagged"; }else if(curXinfo.tags.indexOf("\\Important")>=0){ this._mailboxList[j].type = "Important"; diff --git a/package.json b/package.json index 144b68d..c4d38b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.50", + "version": "1.1.51", "author": "Andris Reinman", "maintainers": [ { From 8cd212e74ae9d8b8a7ffe9c1eb0ca6ddc613f25a Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 14 Apr 2014 22:32:00 +0300 Subject: [PATCH 097/106] include Important --- lib/client.js | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/client.js b/lib/client.js index 28fe824..0cf807e 100644 --- a/lib/client.js +++ b/lib/client.js @@ -969,7 +969,7 @@ IMAPClient.prototype._handlerTaggedLsub = function(xinfo, callback, status){ this._mailboxList[j].type = "Sent"; }else if(curXinfo.tags.indexOf("\\Junk")>=0 || curXinfo.tags.indexOf("\\Spam")>=0){ this._mailboxList[j].type = "Junk"; - }else if(curXinfo.tags.indexOf("\\Flagged")>=0 || curXinfo.tags.indexOf("\\Starred")>=0)){ + }else if(curXinfo.tags.indexOf("\\Flagged")>=0 || curXinfo.tags.indexOf("\\Starred")>=0){ this._mailboxList[j].type = "Flagged"; }else if(curXinfo.tags.indexOf("\\Important")>=0){ this._mailboxList[j].type = "Important"; diff --git a/package.json b/package.json index c4d38b1..6ff3392 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.51", + "version": "1.1.52", "author": "Andris Reinman", "maintainers": [ { From 63d7d6435b023f27d765a2ce1086060a78b71c63 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Mon, 14 Apr 2014 22:40:50 +0300 Subject: [PATCH 098/106] include labels --- lib/client.js | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/client.js b/lib/client.js index 0cf807e..1322351 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1347,6 +1347,7 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ messageTypePreferenceOrder = ["Sent", "Draft", "Starred", "Junk", "Trash"]; if(dataObject["X-GM-LABELS"]){ + message.labels = [].concat(dataObject["X-GM-LABELS"] || []); message.folders = (dataObject["X-GM-LABELS"] || []).map((function(label){ var type; diff --git a/package.json b/package.json index 6ff3392..83fb891 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.52", + "version": "1.1.53", "author": "Andris Reinman", "maintainers": [ { From 53e2ba5ad65133a67ed149d88dbb8c5e9027f046 Mon Sep 17 00:00:00 2001 From: Jerome Touffe-Blin Date: Sat, 26 Apr 2014 10:00:42 +1000 Subject: [PATCH 099/106] Expose mailbox creation on IMAP client and add ability to delete mailbox --- lib/client.js | 14 ++++++++++++++ lib/mailbox.js | 19 ++++++++++++++++++- test/inbox.js | 32 ++++++++++++++++++++++++++++++++ tools/clientplayground.js | 5 +++++ 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/lib/client.js b/lib/client.js index 1322351..48cdce0 100644 --- a/lib/client.js +++ b/lib/client.js @@ -2408,6 +2408,20 @@ IMAPClient.prototype.getMailbox = function(path, callback){ }); }; +/** + * Create a new mailbox + */ +IMAPClient.prototype.createMailbox = function(path, callback){ + this._rootMailbox.createChild(path, callback); +}; + +/** + * Delete a mailbox + */ +IMAPClient.prototype.deleteMailbox = function(path, callback){ + this._rootMailbox.deleteChild(path, callback); +}; + /** * Enter IDLE mode */ diff --git a/lib/mailbox.js b/lib/mailbox.js index 4169765..bb4b873 100644 --- a/lib/mailbox.js +++ b/lib/mailbox.js @@ -131,7 +131,7 @@ Mailbox.prototype.createChild = function(name, callback){ if(status == "OK"){ this.client._send("SUBSCRIBE "+this.client._escapeString(path), (function(){ if(typeof callback == "function"){ - callback(null, new this.client.Mailbox({ + callback(null, new Mailbox({ client: this.client, path: path, name: name, @@ -146,6 +146,23 @@ Mailbox.prototype.createChild = function(name, callback){ }).bind(this)); }; +/** + * Deletes a mailbox + * + * @param {String} name Name of the mailbox + * @param {Function} callback Callback function to run with the status of the operation + */ +Mailbox.prototype.deleteChild = function(name, callback){ + var path = (this.path ? this.path + this.delimiter + name:name); + this.client._send("DELETE "+this.client._escapeString(path), (function(status){ + if(status == "OK"){ + callback(null, status); + }else{ + callback(new Error("Deleting mailbox failed")); + } + }).bind(this)); +}; + /** * Returns mailbox type detected by the name of the mailbox * diff --git a/test/inbox.js b/test/inbox.js index 10c47e9..dc1cab1 100644 --- a/test/inbox.js +++ b/test/inbox.js @@ -322,6 +322,38 @@ module.exports["Inbox tests"] = { this.client.openMailbox("INBOX", (function(err){ this.client.storeMessage("Subject: hello 8\r\n\r\nWorld 8!", function(){}); }).bind(this)); + }, + + "Create mailbox": function(test){ + var self = this; + this.client.createMailbox("NEW-MAILBOX", function(err, mailbox){ + test.ifError(err); + test.equal(mailbox.path, "NEW-MAILBOX"); + test.equal(mailbox.type, "Normal"); + test.equal(mailbox.delimiter, "/"); + self.client.openMailbox("NEW-MAILBOX", function(err, mailbox){ + test.ifError(err); + test.equal(mailbox.count, 0); + test.equal(mailbox.UIDValidity, "1"); + test.equal(mailbox.UIDNext, "1"); + test.done(); + }); + }); + }, + + "Delete mailbox": function(test){ + var self = this; + this.client.createMailbox("NEW-MAILBOX", function(err, mailbox){ + self.client.deleteMailbox("NEW-MAILBOX", function(err, status){ + test.ifError(err); + test.equal(status, "OK"); + self.client.openMailbox("NEW-MAILBOX", function(err, mailbox){ + test.ok(err); + test.ok(!mailbox); + test.done(); + }); + }); + }); } }; diff --git a/tools/clientplayground.js b/tools/clientplayground.js index a7d80e7..34fe6a7 100644 --- a/tools/clientplayground.js +++ b/tools/clientplayground.js @@ -67,6 +67,11 @@ client.on("connect", function(){ //client.listChildren(console.log) + client.createMailbox('test/foobar', function (err, mailbox) { + console.log(err, mailbox); + client.deleteMailbox('test/foobar', console.log); + }); + }); // on new messages, print to console From cb8e9b63526cd1db21fc1547f494a78b11b6bf86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Garm=C3=B3n?= Date: Tue, 29 Apr 2014 21:03:47 +0200 Subject: [PATCH 100/106] Prevent problems with \ in the line parser --- lib/client.js | 12 ------------ lib/lineparser.js | 11 ++++++++++- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/lib/client.js b/lib/client.js index d38eb81..8c00446 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1459,18 +1459,6 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ function(mimeWord){ return mimelib.decodeMimeWord(mimeWord); }); - - if( dataObject.ENVELOPE[2] && typeof dataObject.ENVELOPE[2] === 'string' ){ - - dataObject.ENVELOPE[2] = [ [ dataObject.ENVELOPE[2], null, dataObject.ENVELOPE[4], dataObject.ENVELOPE[6] ] ]; - dataObject.ENVELOPE[3] = [ [ dataObject.ENVELOPE[8], null, dataObject.ENVELOPE[10], dataObject.ENVELOPE[12] ] ]; - dataObject.ENVELOPE[4] = [ [ dataObject.ENVELOPE[14], null, dataObject.ENVELOPE[16], dataObject.ENVELOPE[18] ] ]; - - dataObject.ENVELOPE[5] = null; - dataObject.ENVELOPE[6] = null; - dataObject.ENVELOPE[8] = null; - - } if(dataObject.ENVELOPE[2] && dataObject.ENVELOPE[2].length){ message.from = dataObject.ENVELOPE[2].map(this._formatEnvelopeAddress); diff --git a/lib/lineparser.js b/lib/lineparser.js index 2008bb4..9fbb3bb 100644 --- a/lib/lineparser.js +++ b/lib/lineparser.js @@ -211,7 +211,16 @@ IMAPLineParser.prototype._parseLine = function(line){ if(this._escapedChar || this._state == this.states.ATOM){ this.currentNode.value += curchar; }else if(this._state == this.states.QUOTED){ - this._escapedChar = true; + + if( line.charAt(i+1) === this._quoteMark ){ + this._escapedChar = true; + + // This is a modified copy the default case + }else if(this._state == this.states.ATOM || this._state == this.states.QUOTED){ + this.currentNode.value += curchar; + this.currentNode.value += line.charAt(++i); + } + }else if(this._state == this.states.DEFAULT){ this._state = this.states.ATOM; this._createNode(curchar); From 3a3208932f0634f77a063d2aebac94d87ba4a421 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Garm=C3=B3n?= Date: Tue, 29 Apr 2014 21:04:20 +0200 Subject: [PATCH 101/106] Prevent problems with strings not surrounded by quotes --- lib/lineparser.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/lineparser.js b/lib/lineparser.js index 9fbb3bb..1c2a6d2 100644 --- a/lib/lineparser.js +++ b/lib/lineparser.js @@ -240,11 +240,19 @@ IMAPLineParser.prototype._parseLine = function(line){ this._addToBranch(); this._state = this.states.DEFAULT; this._createNode(); - }else if(this._state == this.states.ATOM){ - this._addToBranch(); - this._quoteMark = curchar; - this._state = this.states.QUOTED; - this._createNode(); + }else if(this._state == this.states.ATOM ){ + + if( [ ' ', '\t', '"', "'", '[', '(', '<' ].indexOf( line.charAt(i-1) ) > -1 ){ + this._addToBranch(); + this._quoteMark = curchar; + this._state = this.states.QUOTED; + this._createNode(); + + // This is a copy the default case + }else if(this._state == this.states.ATOM || this._state == this.states.QUOTED){ + this.currentNode.value += curchar; + } + } break; From 7cd7e3a130622d9f5f6658e30fb9c4d0d08d9509 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Javier=20Garm=C3=B3n?= Date: Wed, 30 Apr 2014 01:58:02 +0200 Subject: [PATCH 102/106] Prevent problems with strings surrounded by quotes at the beginning --- lib/lineparser.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/lineparser.js b/lib/lineparser.js index 1c2a6d2..3cf72a3 100644 --- a/lib/lineparser.js +++ b/lib/lineparser.js @@ -242,13 +242,13 @@ IMAPLineParser.prototype._parseLine = function(line){ this._createNode(); }else if(this._state == this.states.ATOM ){ - if( [ ' ', '\t', '"', "'", '[', '(', '<' ].indexOf( line.charAt(i-1) ) > -1 ){ + if( i === 0 || [ ' ', '\t', '"', "'", '[', '(', '<' ].indexOf( line.charAt(i-1) ) > -1 ){ this._addToBranch(); this._quoteMark = curchar; this._state = this.states.QUOTED; this._createNode(); - // This is a copy the default case + // This is a modified copy the default case }else if(this._state == this.states.ATOM || this._state == this.states.QUOTED){ this.currentNode.value += curchar; } From 223fd6b1766ff1034270a5a7594ee86cea90430a Mon Sep 17 00:00:00 2001 From: Yazhong Liu Date: Fri, 9 May 2014 16:26:22 +0800 Subject: [PATCH 103/106] add INTERNALDATE parsing --- lib/client.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/client.js b/lib/client.js index 8c00446..cc68b4c 100644 --- a/lib/client.js +++ b/lib/client.js @@ -1326,6 +1326,10 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ message.flags = dataObject.FLAGS || []; } + if(dataObject.INTERNALDATE){ + message.internalDate = new Date(dataObject.INTERNALDATE || Date.now()); + } + if(dataObject["X-GM-THRID"]){ message.xGMThreadId = dataObject["X-GM-THRID"]; } @@ -1664,7 +1668,7 @@ IMAPClient.prototype._checkNewMail = function(){ return; } - this._send("UID FETCH " + this._selectedMailbox.UIDNext + ":* (UID BODYSTRUCTURE FLAGS ENVELOPE" + + this._send("UID FETCH " + this._selectedMailbox.UIDNext + ":* (UID BODYSTRUCTURE FLAGS ENVELOPE INTERNALDATE" + (this._capabilities.indexOf("X-GM-EXT-1") >= 0 ? " X-GM-LABELS X-GM-THRID" : "") + (this._capabilities.indexOf("CONDSTORE") >= 0 ? " MODSEQ" : "") + ")", (function(){ @@ -1794,7 +1798,7 @@ IMAPClient.prototype.listMessages = function(from, limit, extendedOptions, callb this._send( "FETCH " + from + ":" + to + - " (UID BODYSTRUCTURE FLAGS ENVELOPE" + + " (UID BODYSTRUCTURE FLAGS ENVELOPE INTERNALDATE" + (this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"") + (this._capabilities.indexOf("CONDSTORE") >= 0 ? " MODSEQ" : "") + ")" + @@ -1856,7 +1860,7 @@ IMAPClient.prototype.listMessagesByUID = function(from, to, extendedOptions, cal this._send( "UID FETCH " + from + ":" + to + - " (UID BODYSTRUCTURE FLAGS ENVELOPE" + + " (UID BODYSTRUCTURE FLAGS ENVELOPE INTERNALDATE" + (this._capabilities.indexOf("X-GM-EXT-1")>=0?" X-GM-LABELS X-GM-THRID":"") + (this._capabilities.indexOf("CONDSTORE") >= 0 ? " MODSEQ" : "") + ")" + From 4770e85bf3b41b6ae634ccd64f7c5ba944503991 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Fri, 9 May 2014 23:53:08 +0300 Subject: [PATCH 104/106] remove default ID values --- lib/client.js | 20 +++++++------------- tools/proxy.js | 10 +++++----- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/lib/client.js b/lib/client.js index 8c00446..d06ef78 100644 --- a/lib/client.js +++ b/lib/client.js @@ -893,17 +893,7 @@ IMAPClient.prototype._handlerTaggedId = function(){ * able to access the mailbox */ IMAPClient.prototype._handlerTaggedCondstore = function(){ - var clientData = { - name: X_CLIENT_NAME - }; - - if(packageData.version){ - clientData.version = packageData.version; - } - - if(X_CLIENT_URL){ - clientData["support-url"] = X_CLIENT_URL; - } + var clientData = {}; if(this.options.clientId){ Object.keys(this.options.clientId).forEach((function(key){ @@ -916,7 +906,11 @@ IMAPClient.prototype._handlerTaggedCondstore = function(){ }).bind(this)).join(" "); if(this._capabilities.indexOf("ID")>=0){ - this._send("ID (" + clientData + ")", this._handlerTaggedId.bind(this)); + if(clientData.length){ + this._send("ID (" + clientData + ")", this._handlerTaggedId.bind(this)); + }else{ + this._send("ID NIL", this._handlerTaggedId.bind(this)); + } }else{ this._postReady(); } @@ -1459,7 +1453,7 @@ IMAPClient.prototype._formatEnvelope = function(envelopeData){ function(mimeWord){ return mimelib.decodeMimeWord(mimeWord); }); - + if(dataObject.ENVELOPE[2] && dataObject.ENVELOPE[2].length){ message.from = dataObject.ENVELOPE[2].map(this._formatEnvelopeAddress); if(message.from.length == 1){ diff --git a/tools/proxy.js b/tools/proxy.js index 759e7b7..1bcaceb 100644 --- a/tools/proxy.js +++ b/tools/proxy.js @@ -2,20 +2,20 @@ var net = require('net'), tls = require('tls'), fs = require("fs"); -var targetHost = "imap.gmail.com", - targetPort = "993", +var targetHost = "imap.gmail.com",//"imap-mail.outlook.com", + targetPort = 993, targetSecure = true, - logfile = "log.txt", + logfile = "log_", proxyPort = 143; var sessionCounter = 0, - logStream = fs.createWriteStream(logfile), server = net.createServer(function(client) { //'connection' listener console.log('Client connected'); - var socket, target, session = ++sessionCounter; + var logStream = fs.createWriteStream(logfile + session + ".txt"); + client.on('end', function() { console.log('Client disconnected'); if(socket && !socket.destroyed){ From b0402cc194af756ff6a9edb3e955129273c4250c Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Fri, 9 May 2014 23:53:40 +0300 Subject: [PATCH 105/106] bumped version to v1.1.54 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 83fb891..eb8a4df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.53", + "version": "1.1.54", "author": "Andris Reinman", "maintainers": [ { From 84b8c63b37e5888703ff59c0dc4d1b3e0bc0ab67 Mon Sep 17 00:00:00 2001 From: Andris Reinman Date: Wed, 28 May 2014 12:42:39 +0300 Subject: [PATCH 106/106] fix escaping chars in quoted strings --- lib/lineparser.js | 7 ++----- package.json | 2 +- test/lineparser.js | 11 +++++++++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/lineparser.js b/lib/lineparser.js index 3cf72a3..ee6f964 100644 --- a/lib/lineparser.js +++ b/lib/lineparser.js @@ -211,16 +211,13 @@ IMAPLineParser.prototype._parseLine = function(line){ if(this._escapedChar || this._state == this.states.ATOM){ this.currentNode.value += curchar; }else if(this._state == this.states.QUOTED){ - - if( line.charAt(i+1) === this._quoteMark ){ + if( line.charAt(i+1) === this._quoteMark || this._state == this.states.QUOTED){ this._escapedChar = true; - // This is a modified copy the default case - }else if(this._state == this.states.ATOM || this._state == this.states.QUOTED){ + }else if(this._state == this.states.ATOM){ this.currentNode.value += curchar; this.currentNode.value += line.charAt(++i); } - }else if(this._state == this.states.DEFAULT){ this._state = this.states.ATOM; this._createNode(curchar); diff --git a/package.json b/package.json index eb8a4df..f87164a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "inbox", - "version": "1.1.54", + "version": "1.1.55", "author": "Andris Reinman", "maintainers": [ { diff --git a/test/lineparser.js b/test/lineparser.js index e68a0ee..a752775 100644 --- a/test/lineparser.js +++ b/test/lineparser.js @@ -212,6 +212,17 @@ exports["Structure tests"] = { }); lp.end("TAG1 \"abc\\\"\" (\"def\")"); + }, + + "Escaped label": function(test){ + var input = 'X-GM-LABELS ("\\\\Draft")'; + var lp = new IMAPLineParser(); + lp.on("line", function(data){ + test.deepEqual(data, [ 'X-GM-LABELS', [ '\\Draft' ] ]); + test.done(); + }); + + lp.end(input); } }