text:
IMAP client for browsers

[![Build Status](https://travis-ci.org/whiteout-io/browserbox.png?branch=master)](https://travis-ci.org/whiteout-io/browserbox)

## StringEncoding API

This module requires `TextEncoder` and `TextDecoder` to exist as part of the StringEncoding API (see: [MDN](https://developer.mozilla.org/en-US/docs/WebAPI/Encoding_API) [whatwg.org](http://encoding.spec.whatwg.org/#api)). This is supported by Firefox, Chrome and Opera. For other browser environments [there is a polyfill](https://github.com/whiteout-io/stringencoding).

## TCPSocket API

There is a [shim](https://github.com/whiteout-io/tcp-socket) that brings [Mozilla-flavored](https://developer.mozilla.org/en-US/docs/WebAPI/TCP_Socket) version of the [Raw Socket API](http://www.w3.org/TR/raw-sockets/) 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](https://github.com/whiteout-io/tcp-socket) for more information!

## Promises

This module uses the Promises API, so make sure your platform either supports `Promise` constructor natively, or use the [es6-promise](https://www.npmjs.com/package/es6-promise) polyfill.

    var ES6Promise = require('es6-promises');
    ES6Promise.polyfill();

## Installation

### [npm](https://www.npmjs.org/):

    npm install browserbox

## Usage

### AMD

Require [browserbox.js](src/browserbox.js) as `browserbox`

### Global context

Include following files on the page.

```html
<script src="browserbox.js"></script>
<script src="browserbox-imap.js"></script>
```

This exposes the constructor `BrowserBox` as a global variable

## API

    var BrowserBox = require('browserbox')

## Create connection to an IMAP server

```
new BrowserBox(host[, port][, options]) → IMAP client object
```

Where

  * **host** is to hostname to connect to
  * **port** (optional) is the port to connect to (defaults to 143)
  * **options** (optional) is the options object
    * **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](http://tools.ietf.org/html/rfc2971#section-3.3) (ex. `{name: 'myclient', version: '1'}`)
    * **useSecureTransport** (optional) enables TLS
    * **ca** (optional) (only in conjunction with the [TCPSocket shim](https://github.com/whiteout-io/tcp-socket)) if you use TLS with forge, pin a PEM-encoded certificate as a string. Please refer to the [tcp-socket documentation](https://github.com/whiteout-io/tcp-socket) for more information!
    * **tlsWorkerPath** (optional) (only in conjunction with the [TCPSocket shim](https://github.com/whiteout-io/tcp-socket)) if you use TLS with forge, this path indicates where the file for the TLS Web Worker is located. Please refer to the [tcp-socket documentation](https://github.com/whiteout-io/tcp-socket) for more information!
    * **ignoreTLS** – if set to true, do not call STARTTLS before authentication even if the host advertises support for it
    * **requireTLS** – if set to true, always use 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*
    * **compressionWorkerPath** (optional) offloads de-/compression computation to a web worker, this is the path to the browserified browserbox-compressor-worker.js

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

```javascript
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`

## Initiate connection

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.

## Events

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

### onerror

Is fired when something unexpected happened.

```
client.onerror = function(err){}
```

Where

  * **err** is an error object

### onclose

Is fired when the connection to the IMAP server is closed.

```
client.onclose = function(){}
```

### onauth

Is fired when the user is successfully authenticated

## List mailboxes

List all mailboxes with `listMailboxes()` method

```
client.listMailboxes(callback)
```

Where

  * **callback** is the callback function with the following arguments
    * **err** is an error object, only set if the request failed
    * **mailboxes** is an object with the mailbox structure

If callback is not specified, the method returns a Promise.

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 LSUB response
  * **specialUse** (string) mailbox was identified as a special use mailbox ('\Trash', '\Sent', '\Junk' etc. see [RFC6154](http://tools.ietf.org/html/rfc6154#section-2))
  * **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

```javascript
client.listMailboxes(function(err, mailboxes){
    console.log(err || mailboxes);
});
```

```json
{
  "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
        }
      ]
    }
  ]
}
```

### Notes

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:

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

## List namespaces

List available namespaces with `listNamespaces()`. If [NAMESPACE](https://tools.ietf.org/html/rfc2342) extension is not supported, the method returns `false`.

```javascript
client.listNamespaces(callback)
```

Where

  * **callback** is the callback function with the following arguments
    * **err** is an error object, only set if the request failed
    * **namespaces** is an object with the namespace values or `false` if NAMESPACE is not supported

If callback is not specified, the method returns a Promise.

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

```javascript
client.listNamespaces(function(err, namespaces){
    console.log(err || namespaces);
});
```

```json
{
    "personal": [
        {
            "prefix": "",
            "delimiter": "/"
        }
    ],
    "users": false,
    "shared": false
}
```

## Create mailbox

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

```javascript
// 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 mailbox

Select specific mailbox by path with `selectMailbox()`

```javascript
client.selectMailbox(path[, options], callback)
```

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`
  * **callback** is the callback function with the following arguments
    * **err** is an error object, only set if the request failed
    * **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)

If callback is not specified, the method returns a Promise.

Example

```javascript
client.selectMailbox('INBOX', function(err, mailbox){
    console.log(err || mailbox);
});
```

```json
{
    "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

List messages with `listMessages()`

```javascript
client.listMessages(sequence, query[, options], callback)
```

Where

  * **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
  * **callback** is the callback function to run once all me messages are processed with the following arguments
    * **err** is an error object, only set if the request failed
    * **messages** is an array of messages from the provided sequence range

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 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

Example

```javascript
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

### 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.

```json
{
    "#": 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)]`

### 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:

```json
[
    {
        "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
```

```json
{
    "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()`

```javascript
client.search(query[, options], callback)
```

Where

  * **query** defines the search terms, see below
  * **options** is an optional options object
    * **byUid** if `true` executes `UID SEARCH` instead of `SEARCH`
  * **callback** is the callback function to run once all me messages are processed with the following arguments
    * **err** is an error object, only set if the request failed
    * **results** is an array of sorted and unique message sequence numbers or UID numbers that match the specified search query

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:

```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}}
```

### Example

```javascript
client.search({unseen: true}, {byUid: true}, function(err, result){
    result.forEach(function(uid){
        console.log('Message ' + uid + ' is unread');
    });
});
```

## Update flags

Update message flags with `setFlags()`. This is a wrapper around `store()`

```javascript
client.setFlags(sequence, flags[, options], callback)
```

Where

  * **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:*'`)
  * **callback** is the callback function to run once all me messages are processed with the following arguments
    * **err** is an error object, only set if the request failed
    * **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.

If callback is not specified, the method returns a Promise.

## Store

Store flags or labels with `store()`

```javascript
client.store(sequence, action, flags[, options], callback)
```

Where

  * **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:*'`)
  * **callback** is the callback function to run once all me messages are processed with the following arguments
    * **err** is an error object, only set if the request failed
    * **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** - overwrite message labels with provided ones
 * **+X-GM-LABELS** - add provided labels to message labels
 * **-X-GM-LABELS** - remove provided labels from message labels

If callback is not specified, the method returns a Promise.

### Example

Add label `\Sent` to messages

```javascript
client.store('1:*', '+X-GM-LABELS', ['\\Sent'], function(err, result){
    ...
});
```

### 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']`

### Example

```javascript
client.setFlags('1', {add: ['\\Seen']}, function(err, result){
    console.log('New flags for message: ' + result[0].flags.join(', '));
});
```

## Delete messages

Delete messages with `deleteMessages()`

```javascript
client.deleteMessages(sequence[, options], callback)
```

Where

  * **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
  * **callback** is the callback function to run once all me messages are processed with the following arguments
    * **err** is an error object, only set if the request failed

If 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.

### Example

```javascript
client.deleteMessages('1:5', function(err){
    if(err){
        console.log('Command failed');
    }else{
        console.log('Messages were deleted');
    }
});
```

## Copy messages

Copy messages with `copyMessages()`

```javascript
client.copyMessages(sequence, destination[, options], callback)
```

Where

  * **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
  * **callback** is the callback function to run once all me messages are processed with the following arguments
    * **err** is an error object, only set if the request failed
    * **message** nothing useful, just the response text from the server

If callback is not specified, the method returns a Promise.

### Example

```javascript
client.copyMessages('1:5', '[Gmail]/Trash', function(err){
    console.log('Messages were copied to [Gmail]/Trash');
});
```

## Move messages

Move messages with `moveMessages()`

```javascript
client.moveMessages(sequence, destination[, options], callback)
```

Where

  * **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
  * **callback** is the callback function to run once all me messages are processed with the following arguments
    * **err** is an error object, only set if the request failed

If 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.

### Example

```javascript
client.moveMessages('1:5', '[Gmail]/Trash', function(err){
    if(err){
        console.log('Command failed');
    }else{
        console.log('Messages were moved to [Gmail]/Trash');
    }
});
```

## Update notifications

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**

```javascript
client.onupdate = function(type, value){
    if (type == 'exists') {
        console.log(value + ' messages exists in selected mailbox');
    }
}
```

Possible types:

  * **exists** is emitted on untagged `EXISTS` response, `value` is the argument number used
  * **expunge** is emitted on untagged `EXPUNGE` response, `value` is the sequence number of the deleted message
  * **fetch** is emitted on flag change. `value` includes the parsed message object (probably includes only the sequence number `#` and `flags` array)

## Mailbox change notifications

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](#select-mailbox)).

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

**Example**

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

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

## Close connection

You can close the connection with `close()`. This method doesn't actually terminate the connection, it sends LOGOUT command to the server.

```javascript
client.close();
```

Once the connection is actually closed `onclose` event is fired.

## State-dependend calls

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:

```javascript
// 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:
* **ctx** is a context parameter, i.e. a pointer to the current position in the command queue
* **next** callback to be invoked when the precheck is done

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:

```javascript
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.

## Get your hands dirty

    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)

## 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.


text:
Hello, World!

This is a very simple example of a Jivoo application. Pages can be added to
the application simply by adding templates to the app/templates/pages
directory

redpajama_set_name: RedPajamaGithub


text:
// searchInput.drv.js

'use strict';

function searchInputController (searchNote, noteData) {
  /*jshint validthis: true */
  var controller = this;
  controller.searchNote = searchNote;

  controller.applySearchNotes = function (searchTerm) {
    noteData.applySearchNotes(searchTerm);
  };
}

function link (scope) {
  scope.$watch('ctrl.searchNote.searchTerm.$', function (newValue, oldValue) {
    if (newValue !== oldValue) {
      scope.ctrl.applySearchNotes(newValue);
    }
  });
}

function searchInput () {
  return {
    restrict: 'E',
    templateUrl: 'scripts/directive/search-input.drv.html',
    scope: {},
    controller: searchInputController,
    controllerAs: 'ctrl',
    bindToController: true,
    link: link
  };
}

angular.module('simpleNote').directive('searchInput', searchInput);


text:
require 'active_support/concern'
require 'active_support/core_ext/class/attribute'
require 'csv'
require 'to_csv/version'
require 'to_csv/interceptor'

module ToCsv
  extend ActiveSupport::Concern

  included do
    class_attribute :csv_instructions
    self.csv_instructions = Hash.new({})
  end

  module ClassMethods
    def csv(namespace=:default,&block)
      csv_instructions[namespace] = block
    end
  end

  def to_csv(namespace = :default)
    Interceptor.from(self).to_block(&csv_instructions[namespace]).with_result do |results,methods|
      CSV::generate_line(results)
    end
  end

  def to_csv_header(namespace = :default)
    Interceptor.from(self).to_block(&csv_instructions[namespace]).with_result do |results,methods|
      CSV::generate_line(methods)
    end
  end
end


text:
package pv.render.awt;

import java.awt.Color;

import pv.util.IntObjectHashMap;

/**
 * This class provides methods for mapping ARGB color values to
 * Java {@link java.awt.Color} instances. A cache is maintained for
 * quick-lookups, avoiding the need to repeatedly allocate new Color
 * instances.
 */
public class Colors extends pv.style.Color {

	private static final IntObjectHashMap colorMap = new IntObjectHashMap();
    private static int misses = 0;
    private static int lookups = 0;

	// ------------------------------------------------------------------------
    // java.awt.Color Lookup Methods

    /**
     * Get a Java Color object for the given red, green, blue, and alpha values
     * as floating point numbers in the range 0-1.0.
     * @param r the red color component (in the range 0-1.0)
     * @param g the green color component (in the range 0-1.0)
     * @param b the blue color component (in the range 0-1.0)
     * @param a the alpha (transparency) component (in the range 0-1.0)
     * @return a Java Color object
     */
    public static Color getColor(float r, float g, float b, float a) {
        return getColor(rgba(r,g,b,a));
    }

    /**
     * Get a Java Color object for the given red, green, and blue values
     * as floating point numbers in the range 0-1.0.
     * @param r the red color component (in the range 0-1.0)
     * @param g the green color component (in the range 0-1.0)
     * @param b the blue color component (in the range 0-1.0)
     * @return a Java Color object
     */
    public static Color getColor(float r, float g, float b) {
        return getColor(r,g,b,1.0f);
    }

    /**
     * Get a Java Color object for the given red, green, and blue values.
     * @param r the red color component (in the range 0-255)
     * @param g the green color component (in the range 0-255)
     * @param b the blue color component (in the range 0-255)
     * @param a the alpa (transparency) component (in the range 0-255)
     * @return a Java Color object
     */
    public static Color getColor(int r, int g, int b, int a) {
        return getColor(rgba(r,g,b,a));
    }

    /**
     * Get a Java Color object for the given red, green, and blue values.
     * @param rgb an rgb integer
     * @param a the alpa (transparency) component (in the range 0-1)
     * @return a Java Color object
     */
    public static Color getColor(int rgb, double alpha) {
        return getColor(rgba(rgb, alpha));
    }

    /**
     * Get a Java Color object for the given red, green, and blue values.
     * @param r the red color component (in the range 0-255)
     * @param g the green color component (in the range 0-255)
     * @param b the blue color component (in the range 0-255)
     * @return a Java Color object
     */
    public static Color getColor(int r, int g, int b) {
        return getColor(r,g,b,255);
    }

    /**
     * Get a Java Color object for the given grayscale value.
     * @param v the grayscale value (in the range 0-255, 0 is
     * black and 255 is white)
     * @return a Java Color object
     */
    public static Color getGrayscale(int v) {
        return getColor(v,v,v,255);
    }

    /**
     * Get a Java Color object for the given color code value.
     * @param rgba the integer color code containing red, green,
     * blue, and alpha channel information
     * @return a Java Color object
     */
    public static Color getColor(int rgba) {
        Color c = null;
        if ( (c=(Color)colorMap.get(rgba)) == null ) {
            c = new Color(rgba,true);
            colorMap.put(rgba,c);
            misses++;
        }
        lookups++;
        return c;
    }

    // ------------------------------------------------------------------------
    // ColorLib Statistics and Cache Management

    /**
     * Get the number of cache misses to the Color object cache.
     * @return the number of cache misses
     */
    public static int getCacheMissCount() {
        return misses;
    }

    /**
     * Get the number of cache lookups to the Color object cache.
     * @return the number of cache lookups
     */
    public static int getCacheLookupCount() {
        return lookups;
    }

    /**
     * Clear the Color object cache.
     */
    public static void clearCache() {
        colorMap.clear();
    }

}


text:
package de.javax.util.eventbinding.impl.source.testmodel;

import de.javax.util.eventbinding.impl.testmodel.Button;
import de.javax.util.eventbinding.impl.testmodel.ParentComponent;
import de.javax.util.eventbinding.source.EventSource;
import de.javax.util.eventbinding.source.EventSourceProvider;

public class ContactEditorGui extends ParentComponent {

    @EventSourceProvider("personEditor")
    private final PersonEditorGui personEditor;

    @EventSourceProvider
    final AddressEditorGui addressEditor;

    @EventSource("okButton")
    private final Button okButton = new Button();

    @EventSource("cancelButton")
    private final Button cancelButton = new Button();

    public ContactEditorGui(PersonEditorGui personEditor, AddressEditorGui addressEditor) {
        this.personEditor = personEditor;
        this.addressEditor = addressEditor;
    }
}


text:
package org.sjmvc.web;

import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import static org.testng.Assert.fail;

import javax.servlet.http.HttpServletResponse;

import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import com.meterware.httpunit.HttpException;
import com.meterware.httpunit.PostMethodWebRequest;
import com.meterware.httpunit.WebRequest;
import com.meterware.servletunit.ServletRunner;
import com.meterware.servletunit.ServletUnitClient;

/**
 * Unit tests for the {@link MVCServlet} class.
 * <p>
 * To test the loading of mapped views, a full servlet container is required.
 * During test execution JSPs are not compiled, and will not exist in the
 * classpath, so the rendering of mapped views needs to be tested in an
 * integration test.
 *
 * @author Ignasi Barrera
 */
public class MVCServletTest
{
	/** The base path used for web requests. */
	private static final String BASE_PATH = "http://sjmvc.org/sjmvc/web";

	/** The servlet client used to perform unit tests. */
	private ServletUnitClient servletClient;

	@BeforeMethod
	public void setUp()
	{
		ServletRunner servletRunner = new ServletRunner();
		servletRunner.registerServlet("sjmvc/web/*", MVCServlet.class.getName());
		servletClient = servletRunner.newClient();
	}

	@Test
	public void testHandleUnmappedRequest() throws Exception
	{
		WebRequest request = new PostMethodWebRequest(BASE_PATH + "/unmapped/test");
		HttpException ex = checkReponseError(request, HttpServletResponse.SC_NOT_FOUND);

		// Ensure MVC servlet has not tried to load the view
		assertFalse(ex.getMessage().contains(".jsp"));
	}

	@Test
	public void testControllerError() throws Exception
	{
		WebRequest request1 = new PostMethodWebRequest(BASE_PATH + "/mock/fail");
		WebRequest request2 = new PostMethodWebRequest(BASE_PATH + "/mock/invalidArguments");

		checkReponseError(request1, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
		checkReponseError(request2, HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
	}

	private HttpException checkReponseError(WebRequest request, int expectedCode)
	{
		try
		{
			servletClient.getResponse(request);
			fail("Expected Response error: " + expectedCode);
			return null;
		}
		catch (Exception ex)
		{
			assertTrue(ex instanceof HttpException);
			assertEquals(((HttpException) ex).getResponseCode(), expectedCode);

			return (HttpException) ex;
		}
	}
}


text:
Puppet Module for managing Mikrotik Devices

Work in Progress.

## Local Testing

### Bootstrap
For local testing using RVM:
* rvm use 2.4.5
* gem install bundler
* bundle install --binstubs [LEGACY?]

## Acceptance Testing

Single run:

	rake beaker

Multiple runs:

	BEAKER_provision=yes BEAKER_destroy=no rake beaker

	BEAKER_provision=no BEAKER_destroy=no rake beaker

	BEAKER_provision=no BEAKER_destroy=onpass rake beaker

### Debugging inside Vagrant

	cd .vagrant/beaker_vagrant_files/default.yml

	vagrant ssh puppet
	vagrant ssh ubuntu-16-04

### Defining SUT and environment
* See spec/fixtures/testnodes.example.yaml
* See spec/fixtures/upgrade_source.example.yaml

### Optional: using CHR as testmachine
* vagrant boxes add dulin/mikrotik
* cd vagrant; vagrant up
See spec/fixtures/testnodes.example.yaml

## NOTES

### IPv6
The IPv6 package is not installed by default!

### Tested Using
Works:
* Ruby 2.1.8
* Puppet 4.3.2

Works:
* Ruby 2.4.5
* Puppet 5.5.10

Works ("puppet6" branch):
* Ruby 2.4.5
* Puppet 6.13.0


text:
package org.apache.river.discovery;

import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import net.jini.core.constraint.InvocationConstraint;
import org.apache.river.api.io.AtomicSerial;
import org.apache.river.api.io.AtomicSerial.GetArg;
import org.apache.river.api.io.AtomicSerial.PutArg;
import org.apache.river.api.io.AtomicSerial.SerialForm;

/**
 * Represents a constraint on the size (in bytes) of multicast packets used in
 * the multicast request and multicast announcement discovery protocols.
 * Lookup services and discovery clients can use this constraint to limit the
 * size of multicast request and announcement packets sent, so as to avoid
 * fragmentation or loss when the packets traverse routers.  This constraint
 * can also be used to control the size of the buffers used to receive incoming
 * multicast request and announcement packets.
 *
 * @author Sun Microsystems, Inc.
 * @since 2.0
 */
@AtomicSerial
public final class MulticastMaxPacketSize
    implements InvocationConstraint, Serializable
{
    private static final long serialVersionUID = 2277375127808559673L;

    /** The minimum allowable multicast packet size limit. */
    public static final int MIN_MAX_PACKET_SIZE = 512;

    public static SerialForm[] serialForm(){
        return new SerialForm[]{
            new SerialForm("size", Integer.TYPE)
        };
    }

    public static void serialize(PutArg arg, MulticastMaxPacketSize m)
            throws IOException{
        arg.put("size", m.size);
        arg.writeArgs();
    }

    /**
     * The multicast packet size limit.
     *
     * @serial
     */
    private final int size;

    /**
     * Creates a MulticastMaxPacketSize constraint for the given
     * multicast packet size limit.
     *
     * @param size the multicast packet size limit
     * @throws IllegalArgumentException if the given size is less than
     * {@link #MIN_MAX_PACKET_SIZE}.
     */
    public MulticastMaxPacketSize(int size) {
	this(size, check(size));
    }

    MulticastMaxPacketSize(GetArg arg) throws IOException {
	this(arg.get("size",0));
    }

    private MulticastMaxPacketSize(int size, boolean check){
	this.size = size;
    }

    private static boolean check(int size){
	if (size < MIN_MAX_PACKET_SIZE) {
	    throw new IllegalArgumentException("invalid size");
	}
	return true;
    }

    /**
     * Returns the multicast packet size limit.
     *
     * @return the multicast packet size limit
     */
    public int getSize() {
	return size;
    }

    public int hashCode() {
	return MulticastMaxPacketSize.class.hashCode() + size;
    }

    public boolean equals(Object obj) {
	return obj instanceof MulticastMaxPacketSize &&
	       size == ((MulticastMaxPacketSize) obj).size;
    }

    public String toString() {
	return "MulticastMaxPacketSize[" + size + "]";
    }

    private void readObject(ObjectInputStream in)
	throws IOException, ClassNotFoundException
    {
	in.defaultReadObject();
	if (size <= MIN_MAX_PACKET_SIZE) {
	    throw new InvalidObjectException("invalid size");
	}
    }
}


text:
package org.apache.camel.converter.jaxb;

import java.io.Closeable;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.stream.FactoryConfigurationError;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import javax.xml.transform.Source;

import org.apache.camel.Exchange;
import org.apache.camel.NoTypeConversionAvailableException;
import org.apache.camel.Processor;
import org.apache.camel.StreamCache;
import org.apache.camel.TypeConversionException;
import org.apache.camel.TypeConverter;
import org.apache.camel.component.bean.BeanInvocation;
import org.apache.camel.converter.jaxp.StaxConverter;
import org.apache.camel.spi.TypeConverterAware;
import org.apache.camel.support.ServiceSupport;
import org.apache.camel.util.IOHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @version
 */
public class FallbackTypeConverter extends ServiceSupport implements TypeConverter, TypeConverterAware {
    private static final transient Logger LOG = LoggerFactory.getLogger(FallbackTypeConverter.class);
    private final Map<Class<?>, JAXBContext> contexts = new HashMap<Class<?>, JAXBContext>();
    private final StaxConverter staxConverter = new StaxConverter();
    private TypeConverter parentTypeConverter;
    private boolean prettyPrint = true;

    public boolean isPrettyPrint() {
        return prettyPrint;
    }

    public void setPrettyPrint(boolean prettyPrint) {
        this.prettyPrint = prettyPrint;
    }

    public void setTypeConverter(TypeConverter parentTypeConverter) {
        this.parentTypeConverter = parentTypeConverter;
    }

    public <T> T convertTo(Class<T> type, Object value) {
        return convertTo(type, null, value);
    }

    public <T> T convertTo(Class<T> type, Exchange exchange, Object value) {
        if (BeanInvocation.class.isAssignableFrom(type) || Processor.class.isAssignableFrom(type)) {
            // JAXB cannot convert to a BeanInvocation / Processor, so we need to indicate this
            // to avoid Camel trying to do this when using beans with JAXB payloads
            return null;
        }

        try {
            if (isJaxbType(type)) {
                return unmarshall(type, exchange, value);
            }
            if (value != null) {
                if (isJaxbType(value.getClass()) && isNotStreamCacheType(type)) {
                    return marshall(type, exchange, value);
                }
            }
        } catch (Exception e) {
            throw new TypeConversionException(value, type, e);
        }

        // should return null if didn't even try to convert at all or for whatever reason the conversion is failed
        return null;
    }

    public <T> T mandatoryConvertTo(Class<T> type, Object value) throws NoTypeConversionAvailableException {
        return mandatoryConvertTo(type, null, value);
    }

    public <T> T mandatoryConvertTo(Class<T> type, Exchange exchange, Object value) throws NoTypeConversionAvailableException {
        T answer = convertTo(type, exchange, value);
        if (answer == null) {
            throw new NoTypeConversionAvailableException(value, type);
        }
        return answer;
    }

    public <T> T tryConvertTo(Class<T> type, Object value) {
        try {
            return convertTo(type, null, value);
        } catch (Exception e) {
            return null;
        }
    }

    public <T> T tryConvertTo(Class<T> type, Exchange exchange, Object value) {
        try {
            return convertTo(type, exchange, value);
        } catch (Exception e) {
            return null;
        }
    }

    @Override
    protected void doStart() throws Exception {
        // noop
    }

    @Override
    protected void doStop() throws Exception {
        contexts.clear();
    }

    protected <T> boolean isJaxbType(Class<T> type) {
        XmlRootElement element = type.getAnnotation(XmlRootElement.class);
        return element != null;
    }

    /**
     * Lets try parse via JAXB
     */
    protected <T> T unmarshall(Class<T> type, Exchange exchange, Object value) throws Exception {
        LOG.trace("Unmarshal to {} with value {}", type, value);

        if (value == null) {
            throw new IllegalArgumentException("Cannot convert from null value to JAXBSource");
        }

        Unmarshaller unmarshaller = getUnmarshaller(type);

        if (parentTypeConverter != null) {
            if (!needFiltering(exchange)) {
                // we cannot filter the XMLStreamReader if necessary
                XMLStreamReader xmlReader = parentTypeConverter.convertTo(XMLStreamReader.class, exchange, value);
                if (xmlReader != null) {
                    try {
                        Object unmarshalled = unmarshal(unmarshaller, exchange, xmlReader);
                        return type.cast(unmarshalled);
                    } catch (Exception ex) {
                        // There is some issue on the StaxStreamReader to CXFPayload message body with different namespaces
                        LOG.debug("Cannot use StaxStreamReader to unmarshal the message, due to {}", ex);
                    }
                }
            }
            InputStream inputStream = parentTypeConverter.convertTo(InputStream.class, exchange, value);
            if (inputStream != null) {
                Object unmarshalled = unmarshal(unmarshaller, exchange, inputStream);
                return type.cast(unmarshalled);
            }
            Reader reader = parentTypeConverter.convertTo(Reader.class, exchange, value);
            if (reader != null) {
                Object unmarshalled = unmarshal(unmarshaller, exchange, reader);
                return type.cast(unmarshalled);
            }
            Source source = parentTypeConverter.convertTo(Source.class, exchange, value);
            if (source != null) {
                Object unmarshalled = unmarshal(unmarshaller, exchange, source);
                return type.cast(unmarshalled);
            }
        }

        if (value instanceof String) {
            value = new StringReader((String) value);
        }
        if (value instanceof InputStream || value instanceof Reader) {
            Object unmarshalled = unmarshal(unmarshaller, exchange, value);
            return type.cast(unmarshalled);
        }

        return null;
    }

    protected <T> T marshall(Class<T> type, Exchange exchange, Object value)
        throws JAXBException, XMLStreamException, FactoryConfigurationError, TypeConversionException {
        LOG.trace("Marshal from value {} to type {}", value, type);

        T answer = null;
        if (parentTypeConverter != null) {
            // lets convert the object to a JAXB source and try convert that to
            // the required source
            JAXBContext context = createContext(value.getClass());
            // must create a new instance of marshaller as its not thread safe
            Marshaller marshaller = context.createMarshaller();
            Writer buffer = new StringWriter();
            if (isPrettyPrint()) {
                marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
            }
            if (exchange != null && exchange.getProperty(Exchange.CHARSET_NAME, String.class) != null) {
                marshaller.setProperty(Marshaller.JAXB_ENCODING, exchange.getProperty(Exchange.CHARSET_NAME, String.class));
            }
            if (needFiltering(exchange)) {
                XMLStreamWriter writer = parentTypeConverter.convertTo(XMLStreamWriter.class, buffer);
                FilteringXmlStreamWriter filteringWriter = new FilteringXmlStreamWriter(writer);
                marshaller.marshal(value, filteringWriter);
            } else {
                marshaller.marshal(value, buffer);
            }
            // we need to pass the exchange
            answer = parentTypeConverter.convertTo(type, exchange, buffer.toString());
        }

        return answer;
    }

    protected Object unmarshal(Unmarshaller unmarshaller, Exchange exchange, Object value)
        throws JAXBException, UnsupportedEncodingException, XMLStreamException {
        try {
            XMLStreamReader xmlReader;
            if (value instanceof XMLStreamReader) {
                xmlReader = (XMLStreamReader) value;
            } else if (value instanceof InputStream) {
                if (needFiltering(exchange)) {
                    xmlReader = staxConverter.createXMLStreamReader(new NonXmlFilterReader(new InputStreamReader((InputStream)value, IOHelper.getCharsetName(exchange))));
                } else {
                    xmlReader = staxConverter.createXMLStreamReader((InputStream)value, exchange);
                }
            } else if (value instanceof Reader) {
                Reader reader = (Reader)value;
                if (needFiltering(exchange)) {
                    if (!(value instanceof NonXmlFilterReader)) {
                        reader = new NonXmlFilterReader((Reader)value);
                    }
                }
                xmlReader = staxConverter.createXMLStreamReader(reader);
            } else if (value instanceof Source) {
                xmlReader = staxConverter.createXMLStreamReader((Source)value);
            } else {
                throw new IllegalArgumentException("Cannot convert from " + value.getClass());
            }
            return unmarshaller.unmarshal(xmlReader);
        } finally {
            if (value instanceof Closeable) {
                IOHelper.close((Closeable)value, "Unmarshalling", LOG);
            }
        }
    }

    protected boolean needFiltering(Exchange exchange) {
        // exchange property takes precedence over data format property
        return exchange != null && exchange.getProperty(Exchange.FILTER_NON_XML_CHARS, Boolean.FALSE, Boolean.class);
    }

    protected synchronized <T> JAXBContext createContext(Class<T> type) throws JAXBException {
        JAXBContext context = contexts.get(type);
        if (context == null) {
            context = JAXBContext.newInstance(type);
            contexts.put(type, context);
        }
        return context;
    }

    protected <T> Unmarshaller getUnmarshaller(Class<T> type) throws JAXBException {
        JAXBContext context = createContext(type);
        return context.createUnmarshaller();
    }

    private static <T> boolean isNotStreamCacheType(Class<T> type) {
        return !StreamCache.class.isAssignableFrom(type);
    }

}