Research
Security News
Malicious npm Packages Inject SSH Backdoors via Typosquatted Libraries
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
browserbox
Advanced tools
IMAP client for browsers
This module requires TextEncoder
and TextDecoder
to exist as part of the StringEncoding API (see: MDN whatwg.org). Firefox 19+ is basically the only browser that supports this at the time of writing, while Chromium in canary, not stable. Luckily, there is a polyfill!
There is a shim that brings Mozilla-flavored version of the Raw Socket API to other platforms.
If you are on a platform that uses forge instead of a native TLS implementation (e.g. chrome.socket), you have to set the .oncert(pemEncodedCertificate) handler that passes the TLS certificate that the server presents. It can be used on a trust-on-first-use basis for subsequent connection.
If forge is used to handle TLS traffic, you may choose to handle the TLS-related load in a Web Worker. Please use tlsWorkerPath to point to tcp-socket-tls-worker.js
!
Please take a look at the tcp-socket documentation for more information!
This module uses the Promises API, so make sure your platform either supports Promise
constructor natively, or use the es6-promise polyfill.
var ES6Promise = require('es6-promises');
ES6Promise.polyfill();
npm install browserbox
Require browserbox.js as browserbox
Include following files on the page.
<script src="browserbox.js"></script>
<script src="browserbox-imap.js"></script>
This exposes the constructor BrowserBox
as a global variable
var BrowserBox = require('browserbox')
new BrowserBox(host[, port][, options]) → IMAP client object
Where
{name: 'myclient', version: '1'}
)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 BrowserBox('localhost', 143, {
auth: {
user: 'testuser',
pass: 'testpass'
},
id: {
name: 'My Client',
version: '0.1'
}
});
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 browserbox-compressor-worker.js
, specify the path via options.compressionWorkerPath
BrowserBox object by default does not initiate the connection, you need to call client.connect()
to establish it
client.connect();
This function does not take any arguments and does not return anything. See the events section to handle connection issues.
The IMAP client has several events you can attach to by setting a listener
Is fired when something unexpected happened.
client.onerror = function(err){}
Where
Is fired when the connection to the IMAP server is closed.
client.onclose = function(){}
Is fired when the user is successfully authenticated
List all mailboxes with listMailboxes()
method
client.listMailboxes(callback)
Where
If callback is not specified, the method returns a Promise.
Mailbox object is with the following structure
true
if the node is rootExample
client.listMailboxes(function(err, mailboxes){
console.log(err || mailboxes);
});
{
"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",
"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 available namespaces with listNamespaces()
. If NAMESPACE extension is not supported, the method returns false
.
client.listNamespaces(callback)
Where
false
if NAMESPACE is not supportedIf callback is not specified, the method returns a Promise.
Namespace object is with the following structure
false
for Personal Namespacefalse
for Other Users' Namespacefalse
for Shared NamespaceNamespace element object has the following structure
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(function(err, namespaces){
console.log(err || namespaces);
});
{
"personal": [
{
"prefix": "",
"delimiter": "/"
}
],
"users": false,
"shared": false
}
Create a folder with the given path, automatically handling utf-7 encoding. You currently need to manually build the path string yourself. (There is potential for future enhancement to provide assistance.)
If the server indicates a failure but that the folder already exists with the ALREADYEXISTS response code, the request will be treated as a success.
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', function callback(err, alreadyExists) {});
// Do the same on a server where the personal namespace is ''
client.createMailbox('Foo', function callback(err, alreadyExists) {});
If callback is not specified, the method returns a Promise.
Select specific mailbox by path with selectMailbox()
client.selectMailbox(path[, options], callback)
Where
listMailboxes
)true
adds (CONDSTORE) option when selectingtrue
uses EXAMINE
instead of SELECT
true
if the mailbox is in read only modeIf callback is not specified, the method returns a Promise.
Example
client.selectMailbox('INBOX', function(err, mailbox){
console.log(err || mailbox);
});
{
"readOnly": false,
"exists": 6596,
"flags": [
"\\Answered",
"\\Flagged"
],
"permanentFlags": [
"\\Answered",
"\\Flagged"
],
"uidValidity": 2,
"uidNext": 38361,
"highestModseq": "3682918"
}
You can check the currently selected mailbox path from client.selectedMailbox
.
If no mailbox is currently selected, the value is false
.
console.log('Current mailbox: %s', client.selectedMailbox);
List messages with listMessages()
client.listMessages(sequence, query[, options], callback)
Where
byUid
option is set to true). Example: '1', '1:*', '1,2:3,4' etc.true
executes UID FETCH
instead of FETCH
If callback is not specified, the method returns a Promise.
A note about sequence ranges – using
*
as a range selector might be a really bad idea. If the mailbox contains thousands of messages and you are running a1:*
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 of5000:*
you still get a match as the query is treated as10:5000
by the server
Example
client.listMessages('1:10', ['uid', 'flags', 'body[]'], function(err, messages){
messages.forEach(function(message){
console.log('Flags for ' + message.uid + ': ' + message.flags.join(', '));
});
});
NB! this method does not stream the values, you need to handle this by yourself by using reasonable sized sequence ranges
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": "sender@example.com"}],
"to": [{"name": "Receiver name", "address": "receiver@example.com"}],
"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)]
An envelope includes the following fields (a value is only included in the response if it is set).
from
headersender
headerreply-to
headerto
headercc
headerbcc
headerAll address fields are in the following format:
[
{
"name": "MIME decoded name",
"address": "email@address"
}
]
A bodystructure object includes the following fields (all values are lowercase, unless the value might be case sensitive, eg. Content-Id value):
BODY[x.x.x]
, eg. '4.1.1' (this value is not set for the root object){border: 'abc'}
{filename: 'foo.gif'}
text/*
and message/rfc822
) is the count of lines in the bodymessage/rfc822
) is the envelope object of the sub-partmultipart/*
and message/rfc822
) is an array of embedded bodystructure objectsExample
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
}
]
}
]
}
Search for messages with search()
client.search(query[, options], callback)
Where
true
executes UID SEARCH
instead of SEARCH
If callback is not specified, the method returns a Promise.
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.
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}}
client.search({unseen: true}, {byUid: true}, function(err, result){
result.forEach(function(uid){
console.log('Message ' + uid + ' is unread');
});
});
Update message flags with setFlags()
client.setFlags(sequence, flags[, options], callback)
Where
byUid
option is set to true). Example: '1', '1:*', '1,2:3,4' etc.true
executes UID SEARCH
instead of SEARCH
true
does not return anything. Useful when updating large range of messages at once ('1:*'
)silent:true
option is set). Includes flags
property and uid
if byUid:true
option was used.If callback is not specified, the method returns a Promise.
You can check the flags for a message or a range of messages with listMessages
- use ['flags']
as the query object.
{ set: arrFlags }
for setting flags{ add: arrFlags }
for adding new flags{ remove: arrFlags }
for removing specified flagsWhere arrFlags
is an array containing flag strings, ie. ['\\Seen', '$MyFlag']
client.setFlags('1', {add: ['\\Seen']}, function(err, result){
console.log('New flags for message: ' + result[0].flags.join(', '));
});
Delete messages with deleteMessages()
client.deleteMessages(sequence[, options], callback)
Where
byUid
option is set to true). Example: '1', '1:*', '1,2:3,4' etc.true
uses UID values instead of sequence numbers to define the rangeIf callback is not specified, the method returns a Promise.
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.
client.deleteMessages('1:5', function(err){
if(err){
console.log('Command failed');
}else{
console.log('Messages were deleted');
}
});
Copy messages with copyMessages()
client.copyMessages(sequence, destination[, options], callback)
Where
byUid
option is set to true). Example: '1', '1:*', '1,2:3,4' etc.true
uses UID values instead of sequence numbers to define the rangeIf callback is not specified, the method returns a Promise.
client.copyMessages('1:5', '[Gmail]/Trash', function(err){
console.log('Messages were copied to [Gmail]/Trash');
});
Move messages with moveMessages()
client.moveMessages(sequence, destination[, options], callback)
Where
byUid
option is set to true). Example: '1', '1:*', '1,2:3,4' etc.true
uses UID values instead of sequence numbers to define the rangeIf callback is not specified, the method returns a Promise.
If possible (MOVE extension is supported by the server) uses MOVE
or UID MOVE
otherwise falls back to COPY + EXPUNGE.
The returned list of sequence numbers might not match with the sequence numbers provided to the method.
client.moveMessages('1:5', '[Gmail]/Trash', function(err){
if(err){
console.log('Command failed');
}else{
console.log('Messages were moved to [Gmail]/Trash');
}
});
Message updates can be listened for by setting the onupdate
handler. First argument for the callback defines the update type, and the second one is the new value.
Example
client.onupdate = function(type, value){
if (type == 'exists') {
console.log(value + ' messages exists in selected mailbox');
}
}
Possible types:
EXISTS
response, value
is the argument number usedEXPUNGE
response, value
is the sequence number of the deleted messagevalue
includes the parsed message object (probably includes only the sequence number #
and flags
array)Listening mailbox select notification is done by setting the onselectmailbox
and onclosemailbox
handlers.
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);
}
You can close the connection with close()
. This method doesn't actually terminate the connection, it sends LOGOUT command to the server.
client.close();
Once the connection is actually closed onclose
event is fired.
Calls to Browserbox are queued in FIFO order until they are ready for execution. This may be problematic for calls that depend on a selected mailbox, e.g. #search
or #setFlags
. These call can be issued with a precheck
callback that is invoked before the IMAP command is sent to the server.
Example:
// search depends on a selected mailbox, e.g. inbox
imap.search({
header: ['subject', 'hello 3']
}, {
// add precheck(ctx, next) to the query options
precheck: function(ctx, next) {
// make sure inbox is selected before the search command is run
imap.selectMailbox('inbox', {
ctx: ctx // pass the context parameter received in the precheck callback as a query option to bypass the command queue
}, next); // next should invoked when you're done
},
byUid: true
}, function(error, result) {
...
});
A precheck
callback receives two arguments:
Calls issued in a precheck
callback will be executed before the parent call, if you pass the ctx
parameter received as a argument of the precheck
callback to the query options. This bypasses the internal FIFO queue and executes the call on the spot! If the context parameter is left blank, the calls will be queued as usual.
Invoke next
once you're done with the precheck
callback to resume normal operation.
If you want to remove a call from the FIFO queue, e.g. because a message is no longer available in a mailbox, pass in an error to the next
callback. The parent call will not be executed and you will receive the error passed to next in the callback of the parent call.
Example:
imap.setFlags('1', ['\\Seen', '$MyFlag'], {
precheck: function(ctx, next) {
next(new Error('Foo')); // this removes the setFlags query from the queue
}
}, function(err, result) {
// err.message -> 'Foo'
...
});
NB! precheck
callbacks can be also nested! For details, have a look at the integration test that covers this portion of the code.
git clone git@github.com:whiteout-io/browserbox.git
cd browserbox
npm install && npm test
To run the integration tests against a local smtp server
grunt imap
add the test folder as a chrome app (chrome settings -> extensions -> check 'developer mode' -> load unpacked extension)
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.
FAQs
IMAP client for browsers.
The npm package browserbox receives a total of 17 weekly downloads. As such, browserbox popularity was classified as not popular.
We found that browserbox demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 3 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket’s threat research team has detected six malicious npm packages typosquatting popular libraries to insert SSH backdoors.
Security News
MITRE's 2024 CWE Top 25 highlights critical software vulnerabilities like XSS, SQL Injection, and CSRF, reflecting shifts due to a refined ranking methodology.
Security News
In this segment of the Risky Business podcast, Feross Aboukhadijeh and Patrick Gray discuss the challenges of tracking malware discovered in open source softare.