Coder Social home page Coder Social logo

emailjs-imap-client's Introduction

emailjs-imap-client

HELP WANTED

Felix is not actively maintaining this library anymore. But this is the only IMAP client for JS that I am aware of, so I feel this library still has its value. Please let me know if you're interested in helping out, either via email or open an issue about that.

The work that's on the horizon is:

  • Removing the tcp-socket shim and use node's net and tls directly
  • Adding features as per requests
  • Refactor to allow streaming and cut down memory consumption
  • Stay up to date with developments in the IMAP protocol
  • Maintenance of the other related emailjs libraries
  • Maintenance and update of emailjs.org

npm Greenkeeper badge Build Status JavaScript Style Guide ES6+ License: MIT

Low-level IMAP client for all your IMAP needs.

Usage

npm install --save emailjs-imap-client
import ImapClient from 'emailjs-imap-client'

// Use this instead for CommonJS modules (Node.js)
var ImapClient = require('emailjs-imap-client').default

API

var client = new ImapClient(host[, port][, options])

Please note that instances cannot be reused! After terminating a connection or encountering an error, please create a new ImapClient instance!

Where

  • host is the hostname to connect to
  • port (optional) is the port to connect to (defaults to 143)
  • options (optional) is the options object
    • logLevel is the verbosity, can be set to error, warn, info, debug. See src/common.js
    • auth is the authentication information object
      • user is the username of the user (also applies to Oauth2)
      • pass is the password of the user
      • xoauth2 is the OAuth2 access token to be used instead of password
    • id (optional) is the identification object for RFC2971 (ex. {name: 'myclient', version: '1'})
    • useSecureTransport (optional) enables TLS
    • ignoreTLS – if set to true, never uses STARTTLS before authentication even if the host advertises support for it
    • requireTLS – if set to true, always uses STARTTLS before authentication even if the host does not advertise it. If STARTTLS fails, do not try to authenticate the user
    • enableCompression - if set to true then use IMAP COMPRESS extension (rfc4978) if the server supports it (Gmail does). All data sent and received in this case is compressed with deflate

Default STARTTLS support is opportunistic – if the server advertises STARTTLS capability, the client tries to use it. If STARTTLS is not advertised, the clients sends passwords in the plain. You can use ignoreTLS and requireTLS to change this behavior by explicitly enabling or disabling STARTTLS usage.

Example

var client = new ImapClient('localhost', 143, {
    auth: {
        user: 'testuser',
        pass: 'testpass'
    }
});

Use of web workers with compression: If you use compression, we can spin up a Web Worker to handle the TLS-related computation off the main thread. To do this, you need to browserify emailjs-imap-client-compressor-worker.js, specify the path via options.compressionWorkerPath

client.onerror = function(error){}

Get server capability

Call client.openConnection() and client.close() without authentication to connect to server and get server capability before logging in:

var client = new ImapClient('localhost', 143);
client.openConnection().then(capability => {
  client.close()
  /* check capability too see, for example, if server is a gmail server and thereby decide on how to authenticate when connecting */
});

Initiate connection

Call client.connect() to establish an IMAP connection:

client.connect().then(() => { /* ready to roll */ });

Close connection

There are two ways to close the connection.

The IMAP way is to send the LOGOUT command with logout().

client.logout().then(() => { /* connection terminated */ });

This method doesn't actually terminate the connection, it sends LOGOUT command to the server, to which the server responds by closing the connection.

The better way is to force-close the connection with close(). This closes the TCP socket and is independent of the network status.

client.close().then(() => { /* connection terminated */ });

List mailboxes

List all mailboxes with listMailboxes() method

client.listMailboxes().then((mailboxes) => { ... })

Mailbox object is with the following structure

  • root (boolean) true if the node is root
  • name (string) unicode decoded name of the mailbox
  • path (string) full path to the mailbox
  • delimiter (string) path delimiting symbol. In the event the server returns NIL for this (some servers do this for the INBOX), it will be coerced to a '/' at this time, but the behavior may be changed in the future depending on how the folder creation API is implemented.
  • listed (boolean) mailbox was found in the LIST response
  • subscribed (boolean) mailbox was found in the LIST (SUBSCRIBED) response if LIST-EXTENDED response otherwise in LSUB response
  • specialUse (string) mailbox was identified as a special use mailbox ('\Trash', '\Sent', '\Junk' etc. see RFC6154)
  • specialUseFlag (string) the same as specialUse but without using folder name based heuristics
  • flags (array) a list of flags
  • children (array) a list of child mailboxes

Example mailboxes object:

{
  "root": true,
  "children": [
    {
      "name": "INBOX",
      "delimiter": "/",
      "path": "INBOX",
      "children": [],
      "flags": ["\\HasNoChildren"],
      "listed": true,
      "subscribed": true
    },
    {
      "name": "[Gmail]",
      "delimiter": "/",
      "path": "[Gmail]",
      "flags": ["\\Noselect","\\HasChildren"],
      "listed": true,
      "subscribed": true,
      "children": [
        {
          "name": "All Mail",
          "delimiter": "/",
          "path": "[Gmail]/All Mail",
          "children": [],
          "flags": ["\\HasNoChildren","\\All"],
          "listed": true,
          "specialUse": "\\All",
          "specialUseFlag": "\\All",
          "subscribed": true
        }
      ]
    }
  ]
}

Root level INBOX is case insensitive, so all subfolders of INBOX, Inbox etc. are mapped together. The first occurence of INBOX defines the name property for the parent element. path values remain as listed.

For example the following IMAP response lists different INBOX names:

    * LIST () "INBOX"
    * LIST () "Inbox/test"

These different INBOX names are mapped to the following object:

{
  "root": true,
  "children": [
    {
      "name": "INBOX",
      "delimiter": "/",
      "path": "INBOX",
      "children": [
        {
          "name": "test",
          "delimiter": "/",
          "path": "Inbox/test",
        }
      ]
    }
  ]
}

List namespaces

List available namespaces with listNamespaces(). If NAMESPACE extension is not supported, the method is a no-op.

Namespace object is with the following structure

  • personal is an array of namespace elements or false for Personal Namespace
  • users is an array of namespace elements or false for Other Users' Namespace
  • shared is an array of namespace elements or false for Shared Namespace

Namespace element object has the following structure

  • prefix is the prefix string
  • delimiter is the hierarchy delimiter. This can be null for some servers but will usually be a string.

NB! Namespace_Response_Extensions are not supported (extension data is silently skipped)

Namespaces should be checked before attempting to create new mailboxes - most probably creating mailboxes outside personal namespace fails. For example when the personal namespace is prefixed with 'INBOX.' you can create 'INBOX.Sent Mail' but you can't create 'Sent Mail'.

Example:

client.listNamespaces().then((namespaces) => { ... })
{
    "personal": [
        {
            "prefix": "",
            "delimiter": "/"
        }
    ],
    "users": false,
    "shared": false
}

Subscribe to mailbox

Subscribe to a mailbox with the given path with subscribeMailbox(path).

Subscribing to a mailbox that is already subscribed is redundant and does return an OK just as when subscribing to unsubscribed mailbox.

Command: SUBSCRIBE

Example

// On a server with unsubscribed Sent mailbox
client.subscribeMailbox('Sent').then(() => { ... });

Unsubscribe from mailbox

Unsubscribe from a mailbox with the given path with unsubscribeMailbox(path).

Unsubscribing from a mailbox that is already unsubscribed is redundant and does return an OK just as when unsubscribing from a subscribed mailbox.

Command: UNSUBSCRIBE

Example

// On a server with subscribed Sent mailbox
client.unsubscribeMailbox('Sent').then(() => { ... });

Create mailbox

Create a folder with the given path with createMailbox(path). You currently need to manually build the path string yourself.

If the server indicates a failure that the folder already exists, but responds with the ALREADYEXISTS response code, the request will be treated as a success.

Command: CREATE

Example

// On a server with a personal namesapce of INBOX and a delimiter of '/',
// create folder Foo.  Note that folders using a non-empty personal namespace
// may automatically assume the personal namespace.
client.createMailbox('INBOX/Foo').then(() => { ... });

// Do the same on a server where the personal namespace is ''
client.createMailbox('Foo').then(() => { ... });

Delete mailbox

Delete a folder with the given path with deleteMailbox(path).

Command: DELETE

Example

client.deleteMailbox('Foo').then(() => { ... });

Select mailbox

Select specific mailbox by path with selectMailbox(path, options)

Where

  • path is the full path to the mailbox (see path property with listMailboxes)
  • options optional options object with the following properties
    • condstore if set to true adds (CONDSTORE) option when selecting
    • readOnly if set to true uses EXAMINE instead of SELECT

Resolves with

  • mailboxInfo is an object with mailbox properties
    • exists (number) the count of messages in the selected mailbox
    • flags (array) an array of flags used in the selected mailbox
    • permanentFlags (array) an array of permanent flags available to use in the selected mailbox
    • readOnly (boolean) true if the mailbox is in read only mode
    • uidValidity (number) UIDValidity value
    • uidNext (number) predicted next UID value
    • highestModseq (string) (with CONDSTORE only) highest modseq value (javascript can't handle 64bit uints so this is a string)

Example

client.selectMailbox('INBOX').then((mailbox) => { ... });
{
    "readOnly": false,
    "exists": 6596,
    "flags": [
        "\\Answered",
        "\\Flagged"
    ],
    "permanentFlags": [
        "\\Answered",
        "\\Flagged"
    ],
    "uidValidity": 2,
    "uidNext": 38361,
    "highestModseq": "3682918"
}

List messages

List messages with listMessages(path, sequence, query[, options])

Where

  • path is the path for the mailbox which should be selected for the command. Selects mailbox prior to executing FETCH if not already selected.
  • sequence defines the range of sequence numbers or UID values (if byUid option is set to true). Example: '1', '1:*', '1,2:3,4' etc.
  • query is an array of keys that need to be fetched. Example: ['uid', 'flags', 'body.peek[headers (date)]']
  • options is an optional options object
    • byUid if true executes UID FETCH instead of FETCH
    • changedSince is the modseq filter. Only messages with higher modseq value will be returned
    • valueAsString LITERAL and STRING values are returned as strings rather than Uint8Array objects. Defaults to true.

Resolves with

  • messages is an array of messages from the provided sequence range

A note about sequence ranges: This method does not stream the values, so using * as a range selector might be a really bad idea. If the mailbox contains thousands of messages and you are running a 1:* query, it might choke your application. Additionally, remember that * stands for the sequence number of the last message in the mailbox. This means that if you have 10 messages in a mailbox and you run a query for a range of 5000:* you still get a match as the query is treated as 10:5000 by the server

IMAP Commands: FETCH, CHANGEDSINCE

Example

client.listMessages('INBOX', '1:10', ['uid', 'flags', 'body[]']).then((messages) => {
    messages.forEach((message) => console.log('Flags for ' + message.uid + ': ' + message.flags.join(', ')));
});

Message item

A listed message item includes (but is not limited to), the selected fields from the query argument (all keys are lowercase). Additionally the argument order and even argument names might not match. For example, when requesting for body.peek you get body back instead. Additionally the message includes a special key # which stands for the sequence number of the message.

Most arguments return strings (eg. body[]) and numbers (eg. uid) while flags return an array, envelope and bodystructure return a processed object.

{
    "#": 123,
    "uid": 456,
    "flags": ["\\Seen", "$MyFlag"],
    "envelope": {
        "date": "Fri, 13 Sep 2013 15:01:00 +0300",
        "subject": "hello 4",
        "from": [{"name": "sender name", "address": "[email protected]"}],
        "to": [{"name": "Receiver name", "address": "[email protected]"}],
        "message-id": "<abcde>"
    }
}

Special keys - if a special key is used, eg. BODY.PEEK[HEADER (Date Subject)], the response key is lowercase and in the form how the server responded it, eg. body[header (date subject)]

Envelope object

An envelope includes the following fields (a value is only included in the response if it is set).

  • date is a date (string) of the message
  • subject is the subject of the message
  • from is an array of addresses from the from header
  • sender is an array of addresses from the sender header
  • reply-to is an array of addresses from the reply-to header
  • to is an array of addresses from the to header
  • cc is an array of addresses from the cc header
  • bcc is an array of addresses from the bcc header
  • in-reply-to is the message-id of the message is message is replying to
  • message-id is the message-id of the message

All address fields are in the following format:

[{
    "name": "MIME decoded name",
    "address": "email@address"
}]

Bodystructure object

A bodystructure object includes the following fields (all values are lowercase, unless the value might be case sensitive, eg. Content-Id value):

  • part is the sub-part selector for BODY[x.x.x], eg. '4.1.1' (this value is not set for the root object)
  • type is the Content-Type of the body part
  • parameters is an object defining extra arguments for Content-Type, example: {border: 'abc'}
  • disposition is the Content-Disposition value (without arguments)
  • dispositionParameters is an object defining extra arguments for Content-Disposition, example: {filename: 'foo.gif'}
  • language is an array of language codes (hardly ever used)
  • location is a string for body content URI (hardly ever used)
  • id is the Content-Id value
  • description is the Content-Description value
  • encoding is the Content-Transfer-Encoding value
  • size is the body size in octets
  • lineCount (applies to text/* and message/rfc822) is the count of lines in the body
  • envelope (applies to message/rfc822) is the envelope object of the sub-part
  • md5 is the MD5 hash of the message (hardly ever used)
  • childNodes (applies to multipart/* and message/rfc822) is an array of embedded bodystructure objects

Example

Bodystructure for the following sample message structure:

multipart/mixed
    text/plain
    multipart/alternative
        text/plain
{
    "type": "multipart/mixed",
    "childNodes": [
        {
            "part": "1",
            "type": "text/plain",
            "encoding": "7bit",
            "size": 8,
            "lineCount": 1
        },
        {
            "part": "2",
            "type": "multipart/alternative",
            "childNodes": [
                {
                    "part": "2.1",
                    "type": "text/plain",
                    "encoding": "7bit",
                    "size": 8,
                    "lineCount": 1
                }
            ]
        }
    ]
}

Searching

Search for messages with search(path, query[, options])

Where

  • path is the path for the mailbox which should be selected for the command. Selects mailbox prior to executing SEARCH if not already selected.
  • query defines the search terms, see below
  • options is an optional options object
    • byUid if true executes UID SEARCH instead of SEARCH

Resolves with

* **results** is an array of sorted and unique message sequence numbers or UID numbers that match the specified search query

Queries are composed as objects where keys are search terms and values are term arguments. Only strings, numbers and Date values are used as arguments. 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 '1-Jan-1970'. Subqueries (OR, NOT) are made up of objects.

Command: SEARCH

Examples:

// 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}}
// SINCE 2011-11-23
query = {since: new Date(2011, 11, 23, 0, 0, 0)}

Example

client.search('INBOX', {unseen: true}, {byUid: true}).then((result) => {
    result.forEach((uid) => console.log('Message ' + uid + ' is unread'));
});

Update flags

Update message flags with setFlags(path, sequence, flags[, options]). This is a wrapper around store()

Where

  • path is the path for the mailbox which should be selected for the command. Selects mailbox prior to executing if not already selected.
  • sequence defines the range of sequence numbers or UID values (if byUid option is set to true). Example: '1', '1:*', '1,2:3,4' etc.
  • flags is an object defining flag updates, see below for details
  • options is an optional options object
    • byUid if true executes UID SEARCH instead of SEARCH
    • silent if true does not return anything. Useful when updating large range of messages at once ('1:*')

Resolves with

* **messages** is an array of messages from the provided sequence range (or empty when `silent:true` option is set). Includes `flags` property and `uid` if `byUid:true` option was used.

Reading flags

You can check the flags for a message or a range of messages with listMessages - use ['flags'] as the query object.

Flag update object

  • { set: arrFlags } for setting flags
  • { add: arrFlags } for adding new flags
  • { remove: arrFlags } for removing specified flags

Where arrFlags is an array containing flag strings, ie. ['\\Seen', '$MyFlag']

client.setFlags('INBOX', '1:10', {set: ['\\Seen']}).then((messages) => { ... })

client.setFlags('INBOX', '1:10', {remove: ['\\Seen']}).then((messages) => { ... })

client.setFlags('INBOX', '1:10', {add: ['\\Seen']}).then((messages) => { ... })

Store Command

The client also allows direct access to the STORE command, but please use setFlags() for convenience. Anyway, store flags or labels with store(path, sequence, action, flags[, options]).

Where

  • path is the path for the mailbox which should be selected for the command. Selects mailbox prior to executing if not already selected.
  • sequence defines the range of sequence numbers or UID values (if byUid option is set to true). Example: '1', '1:*', '1,2:3,4' etc.
  • action is the STORE argument, eg 'FLAGS' for setting flags
  • flags is an array of flags or labels
  • options is an optional options object
    • byUid if true executes UID SEARCH instead of SEARCH
    • silent if true does not return anything. Useful when updating large range of messages at once ('1:*')

Resolves with

  • messages is an array of messages from the provided sequence range (or empty when silent:true option is set). Includes flags property and uid if byUid:true option was used.

Possible actions

  • FLAGS - overwrite message flags with provided ones
  • +FLAGS - add provided flags to message flags
  • -FLAGS - remove provided flags from message flags
  • X-GM-LABELS - GMail-only IMAP extension to overwrite message labels with provided ones
  • +X-GM-LABELS - GMail-only IMAP extension to add provided labels to message labels
  • -X-GM-LABELS - GMail-only IMAP extension to remove provided labels from message labels

Command: STORE

client.store('INBOX', '1:*', '+X-GM-LABELS', ['\\Sent']).then((messages) => { ... }); // adds GMail `\Sent` label to messages

Delete messages

Delete messages with deleteMessages(path, sequence[, options])

Where

  • path is the path for the mailbox which should be selected for the command. Selects mailbox prior to executing if not already selected.
  • sequence defines the range of sequence numbers or UID values (if byUid option is set to true). Example: '1', '1:*', '1,2:3,4' etc.
  • options is an optional options object
    • byUid if true uses UID values instead of sequence numbers to define the range

Resolves when IMAP server completed the command.

If possible (byUid:true is set and UIDPLUS extension is supported by the server) uses UID EXPUNGE otherwise falls back to EXPUNGE to delete the messages – which means that this method might be destructive. If EXPUNGE is used, then any messages with \Deleted flag set are deleted even if these messages are not included in the specified sequence range.

Commands: EXPUNGE, UID EXPUNGE

Example

client.deleteMessages('INBOX', '1:5').then(() => { ... });

Copy messages

Copy messages with copyMessages(sequence, destination[, options])

Where

  • path is the path for the mailbox which should be selected for the command. Selects mailbox prior to executing if not already selected.
  • sequence defines the range of sequence numbers or UID values (if byUid option is set to true). Example: '1', '1:*', '1,2:3,4' etc.
  • destination is the destination folder path. Example: '[Gmail]/Trash'
  • options is an optional options object
    • byUid if true uses UID values instead of sequence numbers to define the range

Resolves with an object which contains uid sets of source and destination uids if the server supports UIDPLUS.

  • srcSeqSet is the uid set of the copied messages in the source mailbox
  • destSeqSet is the uid set of the copied messages in the destination mailbox

Command: COPY

Example

client.copyMessages('INBOX', '1:5', '[Gmail]/Trash').then(({srcSeqSet, destSeqSet}) => { ... });

Upload a message

Upload a message with upload(destination, message, [, options])

Where

  • destination is the destination folder path. Example: '[Gmail]/Trash'
  • message is the message to be uploaded
  • options is an optional options object
    • flags is an array of flags you want to set on the uploaded message. Defaults to [\Seen]. (optional)

Resolves with the new uid of the message in the destination folder if the server supports UIDPLUS.

Command: APPEND

Example

client.upload('INBOX', message).then((uid) => { ... });

Move messages

Move messages with moveMessages(path, sequence, destination[, options])

Where

  • path is the path for the mailbox which should be selected for the command. Selects mailbox prior to executing if not already selected.
  • sequence defines the range of sequence numbers or UID values (if byUid option is set to true). Example: '1', '1:*', '1,2:3,4' etc.
  • destination is the destination folder path. Example: '[Gmail]/Trash'
  • options is an optional options object
    • byUid if true uses UID values instead of sequence numbers to define the range

Resolves when IMAP server completed the command.

If possible (MOVE extension is supported by the server) uses MOVE or UID MOVE otherwise falls back to COPY + EXPUNGE.

Command: MOVE

Example

client.moveMessages('INBOX', '1:5', '[Gmail]/Trash').then(() => { ... });

Events

Keeping synchronization with your IMAP server

It is recommended to set up some sort of local caching for the messages. Please note that IMAP relies on a mixture of mailbox-unique identifiers (UID) and sequence numbers, so a mapping between both is definitely recommended.

There are two kinds of updates: 1) When something happens in the currently selected mailbox, and 2) when you select a mailbox

Updates for the selected mailbox

Your IMAP server sends you updates when something happens in the mailbox you have currently selected. Message updates can be listened for by setting the onupdate handler. First argument for the callback is the path, the second is the update type, and the third one is the new value.

Example

client.onupdate = function(path, type, value){
    if (type === 'expunge') {
      // untagged EXPUNGE response, e.g. "* EXPUNGE 123"
      // value is the sequence number of the deleted message prior to deletion, so adapt your cache accordingly
    } else if (type === 'exists') {
      // untagged EXISTS response, e.g. "* EXISTS 123"
      // value is new EXISTS message count in the selected mailbox
    } else if (type === 'fetch') {
      // untagged FETCH response, e.g. "* 123 FETCH (FLAGS (\Seen))"
      // add a considerable amount of input tolerance here!
      // probably some flag updates, a message or messages have been altered in some way
      // UID is probably not listed, probably includes only the sequence number `#` and `flags` array
    }
}

Mailbox change notifications

For your everyday tasks, this client doesn't really require you to explicitly select a mailbox, even though having an eye on which mailbox is selected is useful to receive untagged updates. When a mailbox is opened or closed, the onselectmailbox and onclosemailbox handlers are called.

For onselectmailbox handler the first argument is the path of the selected mailbox and the second argument is the mailbox information object (see selectMailbox).

For onclosemailbox handler the argument is the path of the selected mailbox.

Example

client.onselectmailbox = function(path, mailbox){
    console.log('Opened %s with %s messages', path, mailbox.exists);
}

client.onclosemailbox = function(path){
    console.log('Closed %s', path);
}

The IMAP client has several events you can attach to by setting a listener

Handling fatal error event

The invocation of onerror indicates an irrecoverable error. When onerror is fired, the connection is already closed, hence there's no need for further cleanup.

Get your hands dirty

$ git clone [email protected]:emailjs/emailjs-imap-client.git
$ cd emailjs-imap-client
$ npm install
$ npm test

License

Copyright (c) 2014 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 above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

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.

emailjs-imap-client's People

Contributors

andris9 avatar asutherland avatar austein avatar c-f-h avatar crowecawcaw avatar felixhammerl avatar gabrielcastro avatar greenkeeper[bot] avatar gudmundurg74 avatar gudmundurmar avatar half-shell avatar heralden avatar lacivert avatar ltgorm avatar marcocatalan avatar nifgraup avatar phunehehe avatar realetive avatar si458 avatar smartmatt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

emailjs-imap-client's Issues

Support use of X-GM-THRID and X-GM-MSGID unencoded 64-bit integer values in SEARCH commands

Gmail defines its X-GM-MSGID and X-GM-THRID identifiers to be unsigned 64-bit integer values (https://developers.google.com/gmail/imap_extensions). Since JS Numbers are not capable of expressing the entire range of values, the only real option is to deal in strings, which was done in the similar case of the modseq value.

The problem right now is that browserbox's _buildSEARCHCommand does a typeof check on the parameter, sees it is a string, and pushes a { type: "string" } which emits the string as a quoted value. Gmail's server responds with "BAD Could not parse command". With the quotes removed, it works.

There seem to be two options:

  1. Just have search specifically recognize the keys in question and explicitly push a { type: "atom }
  2. Add some type of tagging convention for uint64 values in general.

Given that _buildSEARCHCommand already has a special-case for "uid" and adding additional special-casing is most likely to work on the first try for most callers, I'm going to provide a patch to implement the first case, but am filing this issue separately so you can close that PR if you don't like it and implement a better approach. (I'll also revamp my approach as requested, but in the event what you want is architecturally more significant and would like to do it yoursef, I won't stand in your way! :)

Is there way to control verbosity/log level?

I'm using module(pretty awesome, if you ask me) to fetch email during tests and I would like to keep test report clean from things like this:
[2014-07-25T07:31:12.988Z][browserbox IMAP] sending request: W1 CAPABILITY
[2014-07-25T07:31:13.043Z][browserbox IMAP] received response: * CAPABILITY
[2014-07-25T07:31:13.044Z][browserbox IMAP] received response: W1 OK

Is there any way to do it?
May be I'm missing something - if I am, sorry for silly questions :)

browserbox coerces HIGHESTMODSEQ to JS Number, but JS Number can't express the full range

The RFC 4551 grammar (https://tools.ietf.org/html/rfc4551#section-4) calls for the modseq to be an unsigned 64-bit integer but JS Numbers only can go up to 2^53 - 1, so browserbox doing:

 mailbox.highestModseq = Number(ok.highestmodseq) || 0;

will likely end in sadness depending on the server and the century/millenium.

The gaia email app has some 64-bit mathy stuff at https://github.com/mozilla-b2g/gaia-email-libs-and-more/blob/eb0094338fcbc6e5bd6302998e072068b2d358e3/data/lib/mailapi/a64.js if there isn't somehow already better code available. (We parse the unsigned int 64 into a base64-encoded string that has lexicographical ordering properties, and we run with that. It's all a bit odd but it made sense at the time.)

This doesn't affect the Gaia email app at this time because we aren't clever enough to use CONDSTORE/QRESYNC. (Our efforts stem from a precursor IMAP client that wanted to do QRESYNC.) cc'ing @mcav for awareness.

Grammar:

   status-att-val      =/ "HIGHESTMODSEQ" SP mod-sequence-valzer
                          ;; extends non-terminal defined in [IMAPABNF].
                          ;; Value 0 denotes that the mailbox doesn't
                          ;; support persistent mod-sequences
                          ;; as described in Section 3.1.2

   mod-sequence-value  = 1*DIGIT
                          ;; Positive unsigned 64-bit integer
                          ;; (mod-sequence)
                          ;; (1 <= n < 18,446,744,073,709,551,615)

   mod-sequence-valzer = "0" / mod-sequence-value

Problems retrieving maillboxes object when listing mailboxes

Hi, I'm trying to use the listMailboxes() method of the imap client. Here is what I'm trying to do:

client = new ImapClient(host, port, options);
client.connect().then(() => {
    client.listMailboxes().then((mailboxes) => {
        console.dir(mailboxes);
    });
    client.close();
});

It runs fine, outputs the server responses in the terminal saying I've connected and it's listing mailboxes, and closes up without errors. The problem I'm having is that I can't seem to actually use the mailboxes object once it has gotten a response from the server.

It may be because of this notation in the readme:

client.listMailboxes().then((mailboxes) => { ... });

I am really unfamiliar with what's going on here. I understand then() is supposed to run a function once it's done, but I have never seen the () => { ... } notation before. It looks like it may just be shorthand for function(){ } but I'm just not used to that. Maybe that isn't what's causing the problems for me.

Add STARTTLS support.

For Gaia Email, we monkeypatched STARTTLS support in bug 885110. Andris said the following in that thread:

We are already working on STARTTLS both for Browserbox and smtpclient but we have to implement upgradeToSecure in the TCPSocket Chrome shim first. Once we have the upgrade working, we'll add STARTTLS as well.

Opening this here only for tracking purposes. Our corresponding tracking bug for this is bug 1060558.

Can't require ImapClient

Hey guys! I am not sure if I am that idiot or if there is a bug.
What I did:

npm install emailjs-imap-client

and then in my main.js:

var ImapClient = require('emailjs-imap-client')

Sadly I get the error: Uncaught Error: Cannot find module "emailjs-imap-client-imap"

Every other NPM module works :(

Is there an obvious mistake?
Thanks in advance!

Unable to call any command because of 'Uknown command <something>'

So, the code is:

var BrowserBox = require('browserbox'),
    email = '[email protected]',
    password = 'some.password',
    mailbox = 'INBOX';

var client = new BrowserBox('imap.gmail.com', 993, {
    auth: {
        user: email,
        pass: password
    },
    useSSL: true
});

client.connect();
client.listMailboxes(function (err, mailboxes) {
    console.log(err || mailboxes);
});
client.selectMailbox(mailbox, function (err, mailbox) {
    console.log(err || 'Mailbox was successfully changed to ' + mailbox);
});

When I'm trying to execute it, I'm getting something like this:

[2014-08-01T15:24:24.564Z][browserbox] connecting to imap.gmail.com:993
[2014-08-01T15:24:24.566Z][browserbox] entering state: false
[Error: Unknown command j1mb3779317lae]
[Error: Unknown command j1mb3779317lae]
[Error: Unknown command j1mb3779317lae]

Note, that this value j1mb3779317lae changes all the time.
May be, it should give at least more friendly response?

UPD:
May be it will by useful. Executing this one:

    client.listMessages('1:10', ['uid', 'body[]'], function (err, messages) {
        console.log(err);
        messages.forEach(function (message) { // this is 30 line;
                ...
        });
    });

gives me following stackstrace:

TypeError: Cannot call method 'forEach' of undefined
at /path/nodeEmails.js:30:18
    at BrowserBox.<anonymous> (/Users/AK/ncryptedcloud/NccWebBuildout/node_modules/browserbox/src/browserbox.js:680:17)
    at BrowserBox.<anonymous> (/Users/AK/ncryptedcloud/NccWebBuildout/node_modules/browserbox/src/browserbox.js:280:17)
    at ImapClient._processServerResponse (/Users/AK/ncryptedcloud/NccWebBuildout/node_modules/browserbox/src/browserbox-imap.js:529:45)
    at ImapClient._processServerQueue (/Users/AK/ncryptedcloud/NccWebBuildout/node_modules/browserbox/src/browserbox-imap.js:472:14)
    at ImapClient._addToServerQueue (/Users/AK/ncryptedcloud/NccWebBuildout/node_modules/browserbox/src/browserbox-imap.js:419:14)
    at ImapClient._onData (/Users/AK/ncryptedcloud/NccWebBuildout/node_modules/browserbox/src/browserbox-imap.js:363:22)
    at TCPSocket._emit (/Users/AK/ncryptedcloud/NccWebBuildout/node_modules/browserbox/node_modules/tcp-socket/src/tcp-socket.js:332:9)
    at CleartextStream.<anonymous> (/Users/AK/ncryptedcloud/NccWebBuildout/node_modules/browserbox/node_modules/tcp-socket/src/tcp-socket.js:90:22)
    at CleartextStream.EventEmitter.emit (events.js:95:17)

Skip initial, insecure capability check if requireTLS is set

In the event the server does not provide its capabilities in the greeting, browserbox will issue a CAPABILITY request even when requireTLS is going to force a STARTTLS anyways.

The control flow in _onReady and updateCapability are already non-trivial, so I figured I'd check what the preferred way to address this would be.

Note that this isn't particularly high priority since it's just a wasted roundtrip and many servers do include a CAPABILITY in their greeting, but it seems worth addressing since we would like to keep the initial connection setup as fast as possible in all cases.

Browserbox 2.*

Hi everyone,

here's a couple of things I wanna propose: ES6/2015 is here to stay and I'd suggest we embrace it. It offers some significant advantages over the old ways of doing things, specifically talking about generators and promises, as well as stuff as arrow functions, ...

Meta stuff

Code

  • Remove optional callback API in favor of Promises
  • Refactor browserbox.js, use Promises internally
  • Refactor browserbox-imap.js, use Promises internally (where useful)
  • Remove next()
  • Explore usage of Streams in Browserbox
  • Remove axe
  • Pass logging function as option, defaults to console.log/error
  • Remove browser globals
  • Throw out phantomjs
  • Return from connect() when client is up and running, i.e. when onauth() is done, remove onauth() callback from api
  • Return from close() when connection is closed, i.e. when onclose() is done
  • Remove onclose() callback from api: connection unexpectedly is an error and should be handled as such.
  • Update documentation
  • Enhance error reporting with more detailed failure codes
  • Avoid dissociation of errors in _processServerQueue, see #25

Error parsing imap command!

after i list emails with FETCH then ask for the email to be loaded with flags i get the following error

[DEBUG][2016-02-09T12:35:38.374Z][undefined] Client started idling
[DEBUG][2016-02-09T12:35:38.374Z][undefined] Entering idle with IDLE
[DEBUG][2016-02-09T12:35:38.374Z][undefined] C: W43 IDLE
[DEBUG][2016-02-09T12:35:38.410Z][undefined] S: * BAD Protocol Error: Empty command.
{ folder: 'INBOX', uid: 898 }
asked for folder INBOX and email 898
[DEBUG][2016-02-09T12:35:43.260Z][undefined] Fetching messages 898 from INBOX ...
[DEBUG][2016-02-09T12:35:43.261Z][undefined] Idle terminated
[ERROR][2016-02-09T12:35:43.276Z][undefined] Error parsing imap command! 
[ERROR][2016-02-09T12:35:43.277Z][undefined] Error: Unexpected whitespace at position 0
onerror
[Error: Unexpected whitespace at position 0]

Message parser

Thank you for this amazing module :)

Juste a short question. Is there another module to parse the return value of the function listMessages() ?
I just found your other module "mailparser" but i don' get behind how to use ist :D.

Kind Regards

after idle sent, a blank command being sent too

for some reason when the client issues the idle command, it is also sending a blank command right after

SERVER:

  • Tue, 09 Feb 2016 14:54:18 -> 10.0.0.222 -> Success: Action=[Recv Command], Details=[W41 IDLE]
  • Tue, 09 Feb 2016 14:54:18 -> 10.0.0.222 -> Success: Action=[Sent Command], Details=[+ IDLE accepted, awaiting DONE]
  • Tue, 09 Feb 2016 14:54:18 -> 10.0.0.222 -> Success: Action=[Start IDLE State]
  • Tue, 09 Feb 2016 14:54:18 -> 10.0.0.222 -> Success: Action=[Recv Command]
  • Tue, 09 Feb 2016 14:54:18 -> 10.0.0.222 -> Success: Action=[Sent Command], Details=[* BAD Protocol Error: Empty command.]
  • Tue, 09 Feb 2016 14:55:18 -> 10.0.0.222 -> Success: Action=[Recv Command], Details=[DONE]
  • Tue, 09 Feb 2016 14:55:18 -> 10.0.0.222 -> Success: Action=[Sent Command], Details=[ OK IDLE completed.]

CLIENT:

  • [DEBUG][2016-02-09T16:19:40.971Z][undefined] Client started idling
  • [DEBUG][2016-02-09T16:19:40.972Z][undefined] Entering idle with IDLE
  • [DEBUG][2016-02-09T16:19:40.972Z][undefined] C: W44 IDLE
  • [DEBUG][2016-02-09T16:19:41.043Z][undefined] S: * BAD Protocol Error: Empty command.

Implement native STARTTLS support on Chrome

Hi. I've been playing around with getting an IMAP client working as a Chrome App, using this library. I've had success with connecting through a true SSL socket, but, as documented somewhere, STARTTLS doesn't work. It also seems that the current codebase doesn't even fall back to Forge in this setting but simply fails with an ERR_INVALID_ARGUMENT error.

Now I'm aware of all the discussion surrounding the setPaused() bug and Google's slow response to that. However, using the legacy chrome.socket API, I have managed to get native STARTTLS support going using a small hack.

I'm opening this issue to start a discussion about implementing this properly in the library and might later submit pull requests if we can agree on a way to proceed.


First, let's summarize the problem. Let's assume we are in a Chrome app, have connected to an IMAP server on a non-secure socket and want to start a STARTTLS handshake. This is basically what happens:

  1. We send a message to the server saying "hi, I'd like to do TLS with you".
  2. The server replies with "sure thing, TLS handshake please".
  3. Crucially, at this point, emailjs-tcp-socket immediately calls _readLegacySocket() again.
  4. We then call chrome.socket.secure(), but it's already too late because the pending read call will eat the initial part of the server's TLS handshake response, and we get the ERR_INVALID_ARGUMENT error from secure().

Now, the hack that I've been using. This is purely as a proof of concept that native STARTTLS is possible using chrome.socket and is in no way supposed to go into the library. Basically, as an experiment, I patched the "data received" callback to only call _readLegacySocket() if the message we got from the server is not the "STARTTLS OK" response:

        TCPSocket.prototype._readLegacySocket = function() {
            var self = this;

            if (self._socketId === 0) {
                // the socket is closed. omit read and stop further reads
                return;
            }

            // don't read from chrome.socket if we have chrome.socket.secure a handshake in progress!
            if ((self._useSTARTTLS || self._useTLS) && !self.ssl) {
                return;
            }

            chrome.socket.read(self._socketId, function(readInfo) {
                // socket closed remotely or broken
                if (readInfo.resultCode <= 0) {
                    self._socketId = 0;
                    self.close();
                    return;
                }

                // process the data available on the socket
                self._onData(readInfo.data);

                // queue the next read
                // HACK HACK HACK - pseudocode
                if (readInfo.data does not contain the "STARTTLS OK" response) {
                    self._readLegacySocket();
                }
            });
        };

Obviously, this is a big fat ugly hack and all kinds of wrong. But if you do this, native STARTTLS suddenly works fine.


So with this proof of concept working, how can we get this into the library cleanly? I was thinking the following approach:

  1. The TCPSocket shim should provide the suspend() and resume() methods that are part of the Mozilla TCPSocket spec. On all other platforms, these could be no-ops for now.
  2. The Chrome shim can implement these methods by a simple this._isSuspended boolean flag. If this flag is true, the read callback shown above will not call the next _readLegacySocket().
  3. The IMAP stack has to call suspend() on the socket when it receives the "STARTTLS OK" response. Crucially, this has to happen in the _onData callback somewhere because once we call the read method, it's already too late. I was thinking it would work here in upgradeConnection in emailjs-imap-client:
        this.logger.debug('Encrypting connection...');
        return this.exec('STARTTLS').then(() => {
            this.client.suspendSocket();
            this._capability = [];
            this.client.upgrade();
            return this.updateCapability();
        });

Actually, this is probably too high up in the stack, but you get the idea.

After the TLS handshake was successful, we have to call resume() on the socket again which will simply queue a new _readLegacySocket() call.

Somewhere along the way, it would probably be necessary to convert the whole upgradeToSecure procedure to a proper promise rather than the fire and forget thing it seems to be right now.

Based on my experiments, this approach would work. Feedback so far?

logout timeout

i change it like this in emailjs-imap-client-imap.js, and resolve

    Imap.prototype.logout = function() {
        return new Promise((resolve, reject) => {
            this.socket.onclose = this.socket.onerror = () => {
                this.close().then(resolve).catch(reject);
            };

            this.enqueueCommand('LOGOUT').then(()=>{
                this.socket.close()
            });
        });
    };

COMPRESS=DEFLATE support

RFC4978 defines additional IMAP command COMPRESS to send and receive compressed data. This extension is supported by Gmail which makes it desirable to support in BrowserBox as well.

The extensions relies on zlib deflate and inflate methods. In Node this can be done with the zlib module. In browser there is pako (supports Node as well). What makes the extension different from other compression using implementations is that a) it is a stream b) it should flush the compressed/decompressed data very often as commands and responses in most cases are very short.

So far I have tested it against Gmail with not too much success:

C: W4 COMPRESS DEFLATE
S: W4 OK Success
C: W5 IDLE
- Compressing 9 bytes
- Sending 2 bytes of compressed data ("W5 IDLE\r\n")
- <Buffer 78 9c>
- Sending 15 bytes of compressed data
- <Buffer 0a 37 55 f0 74 f1 71 e5 e5 02 00 00 00 ff ff>
S: <Buffer 03 00>
- connection closed

Got the same response (0x03, 0x00) both with pako and Node zlib. Sent data also seemed to be the same both for pako and Node zlib.

Maybe I have misunderstood something but nevertheless this issue needs to be investigated when we have time. Having compressed traffic seems something that we should support.

How to use refresh_token along with access_token when xoauth2 authentication

It seems when I use my access_token its working properly but how I can use my already saved in DB refresh_token?

req.gmail_client = new ImapClient("imap.gmail.com", 993, {
                useSecureTransport: true,
                auth:{
                    user: loggedInUser.gmail_address,
                    xoauth2:loggedInUser.access_token
                },
                enableCompression: true
            });

I need it because when I use newly created access_token it seems working fine but later when I try again its showing me console error like:

[DEBUG][2016-05-03T03:51:53.292Z][undefined] S: W2 OK Success d64mb243647028itb
[DEBUG][2016-05-03T03:51:53.295Z][undefined] Server id updated! [object Object]
[DEBUG][2016-05-03T03:51:53.301Z][undefined] Logging in...
[DEBUG][2016-05-03T03:51:53.302Z][undefined] C: W3 AUTHENTICATE XOAUTH2 "(* value hidden *)"
[DEBUG][2016-05-03T03:51:54.098Z][undefined] S: W3 NO [AUTHENTICATIONFAILED] Invalid credentials (Failure)
[ERROR][2016-05-03T03:51:54.104Z][undefined] Could not connect to server Error: Invalid credentials (Failure)
[DEBUG][2016-05-03T03:51:54.110Z][undefined] Entering state: 5
[DEBUG][2016-05-03T03:51:54.111Z][undefined] Closing connection...

Thanks in advance

FETCH should accept NO responses

If the client runs a FETCH command after another session has deleted some messages and the EXPUNGE notification is not yet delivered to the current session, the server might return a NO according to RFC2180 even though all messages were listed with the response. BrowserBox should either accept the listed messages and treat NO as OK or alternatively, run a NOOP to get the pending EXPUNGE notifications and re-run FETCH.

Any work planned for "advanced" gmail login

Hi,

do you have an example on how to login into a Gmail IMAP mailbox with default "advanced" authentication enabled? Or is this currently not supported, and in this case, is there any work done to do this in the future?
Otherwise this is a really nice piece of code !

Kr,

Wim

SSL against Exchange IMAP not working

Using emailjs-imap-client i cannot establish a TLS connection to an exchange IMAP server via TLS.

I get:

[DEBUG][2016-12-08T07:44:26.738Z][1] Connecting to 192.168.1.101 : 993
[DEBUG][2016-12-08T07:44:26.747Z][1] Entering state: 1
[ERROR][2016-12-08T07:44:26.843Z][1] Could not connect to server Error: Could not open socket: 101057795:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:openssl\ssl\s23_clnt.c:794:

[DEBUG][2016-12-08T07:44:26.843Z][1] Entering state: 5
[DEBUG][2016-12-08T07:44:26.847Z][1] Closing connection...
(node:31136) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Could not open socket: 101057795:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:openssl\ssl\s23_clnt.c:794:

connecting without tls to the default 143 port works. Connecting to the secure port with other imap libraries such as 'imap-client' also works. Is there anything not implemented in emailjs-imap-client with respect to secure imap or am i doing something wrong ?

Enhance error reporting with more detailed failure codes.

The errors thrown from the BrowserBox and SmtpClient libraries don't provide many contextual details, making it difficult to report a proper error status to the user. For our implementation, we monkeypatched some changes to pass along details, such as logged-in state and IMAP/SMTP error codes, in the following files:

https://github.com/mozilla-b2g/gaia-email-libs-and-more/blob/master/js/imap/client.js
https://github.com/mozilla-b2g/gaia-email-libs-and-more/blob/master/js/smtp/client.js

It would be helpful to have native support of some format (the format needn't be identical to ours, just the information is important); in addition to passing along the errors, it may be worth inferring certain categories of errors (such as our "bad-user-or-pass" generic errors and the like).

IDLE on multiple folders

I'm sorry if I open an issue, actually this is a question:

Please, could you suggest us what is the best practice to listen for changes on multiple mailboxes?
At the moment I created 2 imap clients, one always over INBOX and the other one is the "pointer" which has been selected by the user.
In this way I can idle always over the INBOX for new messages.

If I want to get notified when I receive new spam or when a new message has been sent (from other client) is this the right way? Do I have to open 2 new clients listening on every folder?

I even tried to open and close a new imap connection on Sent, Junk and Trash every 15, 30, 45 minutes... but after some connection gmail seems to block new connections

Unable to connect via TLS (self signed cert)

Apparently my certificate chain has a self signed cert or maybe my email provider does. Either way, is there a way to instruct Browserbox to ignore the trust chain on certs and use them anyhow?

Authentication problem

Hi,

I think i found a problem in the authentication problem, it occur when the password contains the character "\":

auth: {
    user: '[email protected]',
    pass: 'password\password'
}
Solution (escape the "\" char)
auth: {
    user: '[email protected]',
    pass: 'password\\password'
}

Gmail Idle doesn't work

Hello, I've just updated browserbox to this new module changin every callback into promise and so on..
Everything works fine (mailbox selection, email reading, ecc) except the idle cycle for gmail (xauth token authentication), indeed I do not receive any update event (flags, new email, ecc).

Debugging I see

["Client started idling"]
["Entering idle with IDLE"]
["C:", "W8 IDLE"]
["S:", "W8 BAD Could not parse command"]
and after some time...
["Idle terminated"]
 ["S:", "* BAD [CLIENTBUG] Invalid tag"]

PS: the same script is working with hotmail (user/pass auth)

Am I missing anything?

Provide STORE support for ANNOTATION (RFC 5257), X-GM-LABELS (gmail extension), etc.

STORE-command-wise, browserbox currently has setFlags(sequence, flags, options, callback) which assumes FLAGS.

Gmail supports an additional (proprietary) type, X-GM-LABELS (https://developers.google.com/gmail/imap_extensions#access_to_gmail_labels_x-gm-labels). They give the example of:

a011 STORE 1 +X-GM-LABELS (foo)
* 1 FETCH (X-GM-LABELS (\Inbox \Sent Important "Muy Importante" foo))
a011 OK STORE (Success)

RFC 5257 (https://tools.ietf.org/html/rfc5257) has an example of:

C: a STORE 1 ANNOTATION (/comment (value.priv "My new comment"))
S: a OK Store complete

Given that most clients only need flags support and for backwards compatibility, it makes sense for setFlags to stay around. But perhaps a more generic store() command could be added that follows the lead of setFlags. Ex:

conn.store(
  uidSet,
  {
    'X-GM-LABELS': {
      add / remove / set: ['foo', 'bar']
    }
  },
  { byUid: true });

Of course, one wrinkle is that a dictionary implies you can issue X-GM-LABELS and an ANNOTATION and maybe FLAGS all at the same time. But STORE of course only can do one thing per tag, and pretending otherwise complicates error handling. So store() could alternately be more low level, like:

store(uidSet, '+X-GM-LABELS', ['foo', 'bar'], { byUid: true });

The value argument that holds the labels to add would be transformed into imap-handler tagged types by default, but the options dict could take something like "raw" to allow it to be passed through to imap-handler directly.

And the other other option is just to create custom methods for each STORE type: setGmailLabels, setAnnotation, etc.

Authentication failed, when connected with self signed certificate

When I run the following code I get authentication failed. The server is running on a self signed certificate. The same code works on node-imap

Any idea on how I can connect to a server that has a self-signed certificate?


var ImapClient = require('emailjs-imap-client')

var client = new ImapClient('localhost', 143, {
auth: {
user: '[email protected]',
password: 'password',
},

ignoreTLS : true,
requireTLS: false,
});

[DEBUG][2016-10-06T17:32:18.568Z][undefined] Connecting to localhost : 143
[DEBUG][2016-10-06T17:32:18.576Z][undefined] Entering state: 1
[DEBUG][2016-10-06T17:32:18.584Z][undefined] Socket opened, waiting for greeting from the server...
[DEBUG][2016-10-06T17:32:18.615Z][undefined] S: * OK [CAPABILITY IMAP4rev1 LITERAL+ SASL-IR LOGIN-REFERRALS ID ENABLE IDLE STARTTLS AUTH=PLAIN AUTH=LOGIN] Dovecot ready.
[DEBUG][2016-10-06T17:32:18.618Z][undefined] Entering state: 2
[DEBUG][2016-10-06T17:32:18.618Z][undefined] Updating id...
[DEBUG][2016-10-06T17:32:18.619Z][undefined] C: W1 ID NIL
[DEBUG][2016-10-06T17:32:18.621Z][undefined] S: * ID ("name" "Dovecot")
[DEBUG][2016-10-06T17:32:18.621Z][undefined] S: W1 OK ID completed.
[DEBUG][2016-10-06T17:32:18.622Z][undefined] Server id updated! [object Object]
[DEBUG][2016-10-06T17:32:18.623Z][undefined] Logging in...
[DEBUG][2016-10-06T17:32:18.623Z][undefined] C: W2 login "[email protected]" "(* value hidden *)"
[DEBUG][2016-10-06T17:32:20.127Z][undefined] S: W2 NO [AUTHENTICATIONFAILED] Authentication failed.
[ERROR][2016-10-06T17:32:20.128Z][undefined] Could not connect to server Error: Authentication failed.
[DEBUG][2016-10-06T17:32:20.129Z][undefined] Entering state: 5

[DEBUG][2016-10-06T17:32:20.129Z][undefined] Closing connection...

Disable logger

Correct me if i'm wrong, but there is no way to disable the logger? I don't wan't to log every action on my production environment ;)

secondary uids are not recognized

I tried chrome app with my key 0x4512c22a. But could not complete setup because of uid mismatch. Secret key was exported using gpg --export-secret-key --armour

selectMailbox not working

For some reason selectMailbox returns this error every so often. It may be nothing, just sharing incase it is an actual issue.
screen shot 2015-07-22 at 12 15 39 pm

SyntaxError: Unexpected token >

Is there anyone who can explain me why I am facing this issue:

.\node_modules\emailjs-imap-client\src\emailjs-imap-client.js:57
        this.client.oncert = (cert) => (this.oncert && this.oncert(cert)); //
                                     ^
SyntaxError: Unexpected token >
    at Module._compile (module.js:439:25)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Module.require (module.js:364:17)
    at require (module.js:380:17)
    at Object.<anonymous> (.\api\policies\imap.js:7:18)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Module.require (module.js:364:17)
    at require (module.js:380:17)
    at .\node_modules\include-all\index.js:129:29
    at Array.forEach (native)
    at requireAll (.\node_modules\include-all\index.js:44:9)
    at buildDictionary (.\node_modules\sails-build-dictionary\index.js:68:14)
    at Function.module.exports.optional (.\node_modules\sails-build-dictionary\index.js:160:9)

I am not sure but I think it causing by below like code syntax:

client.logout().then(() => { /* connection terminated */ });

But if I use this way its safe:

client.logout().then(function(){ /* connection terminated */ });

Thanks in advance

README.md : The listMessages() example is missing an argument

client.listMessages('1:10', ['uid', 'flags', 'body[]']).then((messages) => {
    messages.forEach((message) => console.log('Flags for ' + message.uid + ': ' + message.flags.join(', ')));
});

I think this example is missing the PATH argument which comes first, as described in the README file

  • path is the path for the mailbox which should be selected for the command. Selects mailbox prior to executing FETCH if not already selected.
    • sequence defines the range of sequence numbers or UID values (if byUid option is set to true). Example: '1', '1:*', '1,2:3,4' etc.
    • query is an array of keys that need to be fetched. Example: ['uid', 'flags', 'body.peek[headers (date)]']
    • options is an optional options object
      • byUid if true executes UID FETCH instead of FETCH
      • changedSince is the modseq filter. Only messages with higher modseq value will be returned

Unable to verify first Certificate

[DEBUG][2016-09-30T20:29:04.465Z][undefined] Entering state: 1
[ERROR][2016-09-30T20:29:04.725Z][undefined] Could not connect to server Error: Could not open socket: unable to verify the first certificate
[DEBUG][2016-09-30T20:29:04.725Z][undefined] Entering state: 5
[DEBUG][2016-09-30T20:29:04.725Z][undefined] Closing connection...

Any Ideas?

Certificate for other providers

I was wondering if the Google CA certificate is to allow ssl connection from a chrome packaged app tls socket or if a separate CA certificate is needed for each email provider used (eg Yahoo, gmail, icloud etc.) Thanks!

Attachments?

How do I read off attachments and save them to a file?

STARTTLS support is only opportunistic, can't be required

Like for smtpclient in emailjs/emailjs-smtp-client#13 although #23 implemented STARTTLS support, it only implemented opportunistic support. An active/MITM attack that strips STARTTLS as a capability will maintain a cleartext connection without generating an error, etc.

And note that there are IMAP/SMTP servers out there that only support STARTTLS and not initial-SSL/TLS, so this is a relevant thing.

How to use

Hi thanks for creating this, i have been trying to figure out how to use this client side only (no node, but purely in browser) but couldn't. I looked at the example code and tried changing it around but still couldn't make it work.

I thought about posting on Stackoverflow but saw that someone else had asked the same question and got no answer so decided to ask here.

Could you give me a pointer on how to get started? Thank you!

Consider handling inconsistent case-handling of "Inbox" by gmail (and maybe others)

In the Gaia email app bug https://bugzil.la/925480 we encountered a situation where a user had a folder list with the following folders present:

* XLIST (\HasChildren \Inbox) "/" "Inbox"
* XLIST (\HasNoChildren) "/" "INBOX/anyfolder"

browserbox's _ensurePath mechanism ensures that it does not throw an exception and die (like the Gaia email app does in that current/pre-browserbox bug), but I believe browserbox will deviate from the ideal here. Namely, it would be best if there's only a single folder in the constructed hierarchy rather than both an "Inbox" folder and an "INBOX" folder.

Note that the Gaia email app was using XLIST when this happened to us, but I doubt it changes when going back to LIST.

(Context: I'm in the process of reviewing the patch to move us to using the email.js libraries and I'm trying to make sure I file any relevant bugs/concerns in the correct upstream repositories. I may be filing some others and we're a bit swamped with the review right now and some other scheduling things, but we are of course planning to contribute actual patches upstream too in the future! Although just like we do on bugzilla.mozilla.org, for non-trivial patches we'll probably want to discuss what the correct fix is here in issues or on the email.js mailing lists.)

Use of setTimeout with _processServerQueue dissociates thrown exceptions from data events

first, apologies about being overly verbose here; I'm still wrapping my head around browserbox and I find it helps if I spell out what I'm thinking so my confusion can be addressed directly, but do feel free to skim/skip as appropriate :)

Context

In the Gaia email app we have a very limited attempt to imitate node.js's domain mechanism (http://nodejs.org/api/domain.html). We were planning to be fancier (see https://bugzil.la/1043516 and note that mcav's approach is a much simplified approach per discussion on IRC that is unfortunately not included in the bug), but we just ended up making sure that in our TCPSocket wrapper that if an exception gets thrown by the ondata handler that we kill the connection and we perform cleanup with the account that owns the connection.

We would like to maintain our fancy error handling at all times.

Analysis

Why setTimeout

It's not clear setTimeout is actually needed at this time. Its use seems to be an outgrowth of ImapClient.prototype._processServerQueue having callees that have async signatures but in practice are synchronous and call their callbacks before they return. So if setTimeout weren't used, we would expect that it's possible to blow the stack if _serverQueue has a lot of stuff in it.

Elaborating on the "stuff is actually synchronous", in ImapClient.prototype._processServerQueue there are two paths that appear to go async:

  • calls to this._currentCommand.onplustagged
  • calls to _processServerResponse

onplustagged appears to only be used for login and it synchronously invokes next().

_processServerResponse calls the following async-looking but not-async paths:

  • _globalAcceptUntagged, every setHandler thing in browserbox.js calls next() during its handler
  • this._currentCommand.callback, every call to .exec() in browserbox.js also seems to call next() synchronously in its handler.

This does beg the question of whether it would make more sense to have these functions just be synchronous and having _processServerQueue spin a loop. (Although it might sense to still yield control flow periodically.)

What about when streaming happens?

BrowserBox.prototype.listMessages is arguably a case where it would make sense to not buffer everything in its entirety before invoking the callback. I think this is probably orthogonal, however.

Specifically, it seems like the way to implement this would be to:

  • allow imapHandler.parser to optionally take an optional handler that would be invoked as nodes are created/processed. In practice only literals are going to potentially be huge, but it could make sense for the parser to emit nodes as they happen. For literals over a certain size a stream would be emitted, otherwise a string would be emitted.
  • have fetch support an EventEmitter mode of operation where it would re-emit those streams to the consumer. This could then be hooked up to other streaming-enabled parsers (mailreader) or directly streamed to disk, etc. Since I think the server is allowed to arbitrarily reorder its results so that it does horrible things like return BODY before UID and return the messages in a random order, it's possible browserbox might need to have a failsafe mode for pathological servers like that (sorta doubt they exist) where separate FETCH statements are issued for each UID so things can be inferred.

In the event the stream needs to involve some I/O that has setup overhead, the streams' backpressure mechanism can be used to propagate that back to the TCPSocket.

And doing this need not make any _processServerResponse calls async.

Proposal Options

make _processServerQueue sync, run-to-completion

It's not clear there's a benefit from the fake-async calls. Right now it seems to just be causing all the functions to have to call next() and potentially be a foot-gun if you leave one out. The idiom of callbacks being invoked while their function on the stack can also frequently be a foot-gun. A standard means of addressing this is to use Promises, but those are not a panacea and using them if you don't need them frequently just contributes to headaches.

I think there are some real simplicity benefits from just having the callbacks be synchronous. If there's concern about eventually needing the ability to be async and having to change the call signatures again, I would argue that generators should be sufficiently available at that point that the callee is required to just return/be a generator instead.

Allow nextTick to be passed in

When ImapClient is created let a nextTick function be passed in as an option for this purpose. If omitted, setTimeout(blah, 0) will be used by default. For Gaia email we can pass in our own wrapper that keeps things associated.

BrowserBox.prototype._checkSpecialUseByName is not path-aware and will claim "INBOX.IDN.Sent" is special-use sent.

For FxOS email we have a user with a folder named "INBOX.IDN.Sent' (with "INBOX." as the personal namespace prefix). Our pre-browserbox logic already had a name-based inference logic (but without the multilingual database) where we ran into this problem in https://bugzilla.mozilla.org/show_bug.cgi?id=854128 and addressed it by only applying the name-inference logic when the folder is top-level or at the root of the namespace. (Unfortunately our unit test for that case was very much a unit test and did not pass things through browserbox. I've now got a better test-case.)

I think there are probably two issues here:

  1. The name inference logic probably wants to be prefix-aware. (This gets into the need to merge listNamespaces and listMailboxes described in #43).
  2. Although _checkSpecialUseByName is a useful helper heuristic, it makes it impossible for callers to tell whether the server actually indicated the folder had a special-use flag or whether it was synthetically injected by heuristic.

The latter issue matters for cases like this one where a user got a new fastmail.com account which came with a properly identified "Sent Items" folder that reports special-use of Sent, and then ran an import from an old IMAP account where Thunderbird had created a "Sent" folder and which lacks special-use because the Courier server didn't support such things. Arguably it would be better if fastmail's import logic did a fancier job, but there are arguments for what it did and this generally just being a hard problem. But it's useful for a client to be able to determine what is effectively the "true" special-use folder and what is historical IMAP folder cruft.

I don't think there's an easy way to address this without an API change of some form. The simplest solution that comes to mind is to add an additional property "inferredSpecialUse" on each mailbox. It would be the actual specialUse if there was one, or the inferred one by name if there wasn't. If we want to avoid changing existing consumers, then we could have the current "specialUse" be "explicitSpecialUse" and "inferredSpecialUse" be "specialUse".

Screenshots

It would be nice if you could provide some screenshots.

Logging verbosity

Hi there,

thanks for that great library! I'm currently trying to disable the very verbose debug logging. An older ticket suggested setting the log level via axe, so I tried the following:

var axe = require('axe-logger');
axe.logLevel = axe.ERROR;

Which I placed at the very top of my script. However, I still receive the very verbose debug logging. I'm running in a node.js environment. Any hints appreciated!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.