Intial Commit

This commit is contained in:
valki
2020-10-17 18:42:50 +02:00
commit 664c6d8ca3
5892 changed files with 759183 additions and 0 deletions

19
nodered/rootfs/data/node_modules/imap/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,19 @@
Copyright Brian White. All rights reserved.
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.

757
nodered/rootfs/data/node_modules/imap/README.md generated vendored Normal file
View File

@@ -0,0 +1,757 @@
Description
===========
node-imap is an IMAP client module for [node.js](http://nodejs.org/).
This module does not perform any magic such as auto-decoding of messages/attachments or parsing of email addresses (node-imap leaves all mail header values as-is).
An upgrade guide from node-imap v0.7.x to v0.8.x can be found [here](https://github.com/mscdex/node-imap/wiki/API-changes-between-v0.7-and-v0.8).
Requirements
============
* [node.js](http://nodejs.org/) -- v0.8.0 or newer
* NOTE: node v0.8.x users are supported via the readable-stream module which
may not be up-to-date (compared to node v0.10 streams2 implementation)
* An IMAP server to connect to -- tested with gmail
Installation
============
npm install imap
Examples
========
* Fetch the 'date', 'from', 'to', 'subject' message headers and the message structure of the first 3 messages in the Inbox:
```javascript
var Imap = require('imap'),
inspect = require('util').inspect;
var imap = new Imap({
user: 'mygmailname@gmail.com',
password: 'mygmailpassword',
host: 'imap.gmail.com',
port: 993,
tls: true
});
function openInbox(cb) {
imap.openBox('INBOX', true, cb);
}
imap.once('ready', function() {
openInbox(function(err, box) {
if (err) throw err;
var f = imap.seq.fetch('1:3', {
bodies: 'HEADER.FIELDS (FROM TO SUBJECT DATE)',
struct: true
});
f.on('message', function(msg, seqno) {
console.log('Message #%d', seqno);
var prefix = '(#' + seqno + ') ';
msg.on('body', function(stream, info) {
var buffer = '';
stream.on('data', function(chunk) {
buffer += chunk.toString('utf8');
});
stream.once('end', function() {
console.log(prefix + 'Parsed header: %s', inspect(Imap.parseHeader(buffer)));
});
});
msg.once('attributes', function(attrs) {
console.log(prefix + 'Attributes: %s', inspect(attrs, false, 8));
});
msg.once('end', function() {
console.log(prefix + 'Finished');
});
});
f.once('error', function(err) {
console.log('Fetch error: ' + err);
});
f.once('end', function() {
console.log('Done fetching all messages!');
imap.end();
});
});
});
imap.once('error', function(err) {
console.log(err);
});
imap.once('end', function() {
console.log('Connection ended');
});
imap.connect();
```
* Retrieve the 'from' header and buffer the entire body of the newest message:
```javascript
// using the functions and variables already defined in the first example ...
openInbox(function(err, box) {
if (err) throw err;
var f = imap.seq.fetch(box.messages.total + ':*', { bodies: ['HEADER.FIELDS (FROM)','TEXT'] });
f.on('message', function(msg, seqno) {
console.log('Message #%d', seqno);
var prefix = '(#' + seqno + ') ';
msg.on('body', function(stream, info) {
if (info.which === 'TEXT')
console.log(prefix + 'Body [%s] found, %d total bytes', inspect(info.which), info.size);
var buffer = '', count = 0;
stream.on('data', function(chunk) {
count += chunk.length;
buffer += chunk.toString('utf8');
if (info.which === 'TEXT')
console.log(prefix + 'Body [%s] (%d/%d)', inspect(info.which), count, info.size);
});
stream.once('end', function() {
if (info.which !== 'TEXT')
console.log(prefix + 'Parsed header: %s', inspect(Imap.parseHeader(buffer)));
else
console.log(prefix + 'Body [%s] Finished', inspect(info.which));
});
});
msg.once('attributes', function(attrs) {
console.log(prefix + 'Attributes: %s', inspect(attrs, false, 8));
});
msg.once('end', function() {
console.log(prefix + 'Finished');
});
});
f.once('error', function(err) {
console.log('Fetch error: ' + err);
});
f.once('end', function() {
console.log('Done fetching all messages!');
imap.end();
});
});
```
* Save raw unread emails since May 20, 2010 to files:
```javascript
// using the functions and variables already defined in the first example ...
var fs = require('fs'), fileStream;
openInbox(function(err, box) {
if (err) throw err;
imap.search([ 'UNSEEN', ['SINCE', 'May 20, 2010'] ], function(err, results) {
if (err) throw err;
var f = imap.fetch(results, { bodies: '' });
f.on('message', function(msg, seqno) {
console.log('Message #%d', seqno);
var prefix = '(#' + seqno + ') ';
msg.on('body', function(stream, info) {
console.log(prefix + 'Body');
stream.pipe(fs.createWriteStream('msg-' + seqno + '-body.txt'));
});
msg.once('attributes', function(attrs) {
console.log(prefix + 'Attributes: %s', inspect(attrs, false, 8));
});
msg.once('end', function() {
console.log(prefix + 'Finished');
});
});
f.once('error', function(err) {
console.log('Fetch error: ' + err);
});
f.once('end', function() {
console.log('Done fetching all messages!');
imap.end();
});
});
});
```
API
===
#### Data types
* _MessageSource_ can be a single message identifier, a message identifier range (e.g. `'2504:2507'` or `'*'` or `'2504:*'`), an _array_ of message identifiers, or an _array_ of message identifier ranges.
* _Box_ is an object representing the currently open mailbox, and has the following properties:
* **name** - _string_ - The name of this mailbox.
* **readOnly** - _boolean_ - True if this mailbox was opened in read-only mode. **(Only available with openBox() calls)**
* **newKeywords** - _boolean_ - True if new keywords can be added to messages in this mailbox.
* **uidvalidity** - _integer_ - A 32-bit number that can be used to determine if UIDs in this mailbox have changed since the last time this mailbox was opened.
* **uidnext** - _integer_ - The uid that will be assigned to the next message that arrives at this mailbox.
* **flags** - _array_ - A list of system-defined flags applicable for this mailbox. Flags in this list but *not* in `permFlags` may be stored for the current session only. Additional server implementation-specific flags may also be available.
* **permFlags** - _array_ - A list of flags that can be permanently added/removed to/from messages in this mailbox.
* **persistentUIDs** - _boolean_ - Whether or not this mailbox has persistent UIDs. This should almost always be true for modern mailboxes and should only be false for legacy mail stores where supporting persistent UIDs was not technically feasible.
* **messages** - _object_ - Contains various message counts for this mailbox:
* **total** - _integer_ - Total number of messages in this mailbox.
* **new** - _integer_ - Number of messages in this mailbox having the Recent flag (this IMAP session is the first to see these messages).
* **unseen** - _integer_ - **(Only available with status() calls)** Number of messages in this mailbox not having the Seen flag (marked as not having been read).
* _ImapMessage_ is an object representing an email message. It consists of:
* Events:
* **body**(< _ReadableStream_ >stream, < _object_ >info) - Emitted for each requested body. Example `info` properties:
* **which** - _string_ - The specifier for this body (e.g. 'TEXT', 'HEADER.FIELDS (TO FROM SUBJECT)', etc).
* **size** - _integer_ - The size of this body in bytes.
* **attributes**(< _object_ >attrs) - Emitted when all message attributes have been collected. Example `attrs` properties:
* **uid** - _integer_ - A 32-bit ID that uniquely identifies this message within its mailbox.
* **flags** - _array_ - A list of flags currently set on this message.
* **date** - _Date_ - The internal server date for the message.
* **struct** - _array_ - The message's body structure **(only set if requested with fetch())**. See below for an explanation of the format of this property.
* **size** - _integer_ - The RFC822 message size **(only set if requested with fetch())**.
* **end**() - Emitted when all attributes and bodies have been parsed.
* _ImapFetch_ is an object representing a fetch() request. It consists of:
* Events:
* **message**(< _ImapMessage_ >msg, < _integer_ >seqno) - Emitted for each message resulting from a fetch request. `seqno` is the message's sequence number.
* **error**(< _Error_ >err) - Emitted when an error occurred.
* **end**() - Emitted when all messages have been parsed.
A message structure with multiple parts might look something like the following:
```javascript
[ { type: 'mixed',
params: { boundary: '000e0cd294e80dc84c0475bf339d' },
disposition: null,
language: null,
location: null
},
[ { type: 'alternative',
params: { boundary: '000e0cd294e80dc83c0475bf339b' },
disposition: null,
language: null
},
[ { partID: '1.1',
type: 'text',
subtype: 'plain',
params: { charset: 'ISO-8859-1' },
id: null,
description: null,
encoding: '7BIT',
size: 935,
lines: 46,
md5: null,
disposition: null,
language: null
}
],
[ { partID: '1.2',
type: 'text',
subtype: 'html',
params: { charset: 'ISO-8859-1' },
id: null,
description: null,
encoding: 'QUOTED-PRINTABLE',
size: 1962,
lines: 33,
md5: null,
disposition: null,
language: null
}
]
],
[ { partID: '2',
type: 'application',
subtype: 'octet-stream',
params: { name: 'somefile' },
id: null,
description: null,
encoding: 'BASE64',
size: 98,
lines: null,
md5: null,
disposition:
{ type: 'attachment',
params: { filename: 'somefile' }
},
language: null,
location: null
}
]
]
```
The above structure describes a message having both an attachment and two forms of the message body (plain text and HTML).
Each message part is identified by a partID which is used when you want to fetch the content of that part (see fetch()).
The structure of a message with only one part will simply look something like this:
```javascript
[ { partID: '1',
type: 'text',
subtype: 'plain',
params: { charset: 'ISO-8859-1' },
id: null,
description: null,
encoding: '7BIT',
size: 935,
lines: 46,
md5: null,
disposition: null,
language: null
}
]
```
Therefore, an easy way to check for a multipart message is to check if the structure length is >1.
Lastly, here are the system flags defined by RFC3501 that may be added/removed:
* \Seen - Message has been read
* \Answered - Message has been answered
* \Flagged - Message is "flagged" for urgent/special attention
* \Deleted - Message is marked for removal
* \Draft - Message has not completed composition (marked as a draft).
It should be noted however that the IMAP server can limit which flags can be permanently modified for any given message. If in doubt, check the mailbox's **permFlags** first.
Additional custom flags may be provided by the server. If available, these will also be listed in the mailbox's **permFlags**.
require('imap') returns one object: **Connection**.
Connection Events
-----------------
* **ready**() - Emitted when a connection to the server has been made and authentication was successful.
* **alert**(< _string_ >message) - Emitted when the server issues an alert (e.g. "the server is going down for maintenance").
* **mail**(< _integer_ >numNewMsgs) - Emitted when new mail arrives in the currently open mailbox.
* **expunge**(< _integer_ >seqno) - Emitted when a message was expunged externally. `seqno` is the sequence number (instead of the unique UID) of the message that was expunged. If you are caching sequence numbers, all sequence numbers higher than this value **MUST** be decremented by 1 in order to stay synchronized with the server and to keep correct continuity.
* **uidvalidity**(< _integer_ >uidvalidity) - Emitted if the UID validity value for the currently open mailbox changes during the current session.
* **update**(< _integer_ >seqno, < _object_ >info) - Emitted when message metadata (e.g. flags) changes externally.
* **error**(< _Error_ >err) - Emitted when an error occurs. The 'source' property will be set to indicate where the error originated from.
* **close**(< _boolean_ >hadError) - Emitted when the connection has completely closed.
* **end**() - Emitted when the connection has ended.
Connection Properties
---------------------
* **state** - _string_ - The current state of the connection (e.g. 'disconnected', 'connected', 'authenticated').
* **delimiter** - _string_ - The (top-level) mailbox hierarchy delimiter. If the server does not support mailbox hierarchies and only a flat list, this value will be falsey.
* **namespaces** - _object_ - Contains information about each namespace type (if supported by the server) with the following properties:
* **personal** - _array_ - Mailboxes that belong to the logged in user.
* **other** - _array_ - Mailboxes that belong to other users that the logged in user has access to.
* **shared** - _array_ - Mailboxes that are accessible by any logged in user.
There should always be at least one entry (although the IMAP spec allows for more, it doesn't seem to be very common) in the personal namespace list, with a blank namespace prefix. Each property's array contains objects of the following format (with example values):
```javascript
{ prefix: '', // A string containing the prefix to use to access mailboxes in this namespace
delimiter: '/', // A string containing the hierarchy delimiter for this namespace, or boolean false
// for a flat namespace with no hierarchy
extensions: [ // An array of namespace extensions supported by this namespace, or null if none
// are specified
{ name: 'X-FOO-BAR', // A string indicating the extension name
params: [ 'BAZ' ] // An array of strings containing the parameters for this extension,
// or null if none are specified
}
]
}
```
Connection Static Methods
-------------------------
* **parseHeader**(< _string_ >rawHeader[, < _boolean_ >disableAutoDecode]) - _object_ - Parses a raw header and returns an object keyed on header fields and the values are Arrays of header field values. Set `disableAutoDecode` to true to disable automatic decoding of MIME encoded-words that may exist in header field values.
Connection Instance Methods
---------------------------
**Note:** Message UID ranges are not guaranteed to be contiguous.
* **(constructor)**([< _object_ >config]) - _Connection_ - Creates and returns a new instance of _Connection_ using the specified configuration object. Valid config properties are:
* **user** - _string_ - Username for plain-text authentication.
* **password** - _string_ - Password for plain-text authentication.
* **xoauth** - _string_ - Base64-encoded OAuth token for [OAuth authentication](https://sites.google.com/site/oauthgoog/Home/oauthimap) for servers that support it (See Andris Reinman's [xoauth.js](https://github.com/andris9/inbox/blob/master/lib/xoauth.js) module to help generate this string).
* **xoauth2** - _string_ - Base64-encoded OAuth2 token for [The SASL XOAUTH2 Mechanism](https://developers.google.com/google-apps/gmail/xoauth2_protocol#the_sasl_xoauth2_mechanism) for servers that support it (See Andris Reinman's [xoauth2](https://github.com/andris9/xoauth2) module to help generate this string).
* **host** - _string_ - Hostname or IP address of the IMAP server. **Default:** "localhost"
* **port** - _integer_ - Port number of the IMAP server. **Default:** 143
* **tls** - _boolean_ - Perform implicit TLS connection? **Default:** false
* **tlsOptions** - _object_ - Options object to pass to tls.connect() **Default:** (none)
* **autotls** - _string_ - Set to 'always' to always attempt connection upgrades via STARTTLS, 'required' only if upgrading is required, or 'never' to never attempt upgrading. **Default:** 'never'
* **connTimeout** - _integer_ - Number of milliseconds to wait for a connection to be established. **Default:** 10000
* **authTimeout** - _integer_ - Number of milliseconds to wait to be authenticated after a connection has been established. **Default:** 5000
* **socketTimeout** - _integer_ - The timeout set for the socket created when communicating with the IMAP server. If not set, the socket will not have a timeout. **Default:** 0
* **keepalive** - _mixed_ - Configures the keepalive mechanism. Set to `true` to enable keepalive with defaults or set to object to enable and configure keepalive behavior: **Default:** true
* **interval** - _integer_ - This is the interval (in milliseconds) at which NOOPs are sent and the interval at which `idleInterval` is checked. **Default:** 10000
* **idleInterval** - _integer_ - This is the interval (in milliseconds) at which an IDLE command (for servers that support IDLE) is re-sent. **Default:** 300000 (5 mins)
* **forceNoop** - _boolean_ - Set to `true` to force use of NOOP keepalive on servers also support IDLE. **Default:** false
* **debug** - _function_ - If set, the function will be called with one argument, a string containing some debug info **Default:** (no debug output)
* **connect**() - _(void)_ - Attempts to connect and authenticate with the IMAP server.
* **end**() - _(void)_ - Closes the connection to the server after all requests in the queue have been sent.
* **destroy**() - _(void)_ - Immediately destroys the connection to the server.
* **openBox**(< _string_ >mailboxName[, < _boolean_ >openReadOnly=false[, < _object_ >modifiers]], < _function_ >callback) - _(void)_ - Opens a specific mailbox that exists on the server. `mailboxName` should include any necessary prefix/path. `modifiers` is used by IMAP extensions. `callback` has 2 parameters: < _Error_ >err, < _Box_ >mailbox.
* **closeBox**([< _boolean_ >autoExpunge=true, ]< _function_ >callback) - _(void)_ - Closes the currently open mailbox. If `autoExpunge` is true, any messages marked as Deleted in the currently open mailbox will be removed if the mailbox was NOT opened in read-only mode. If `autoExpunge` is false, you disconnect, or you open another mailbox, messages marked as Deleted will **NOT** be removed from the currently open mailbox. `callback` has 1 parameter: < _Error_ >err.
* **addBox**(< _string_ >mailboxName, < _function_ >callback) - _(void)_ - Creates a new mailbox on the server. `mailboxName` should include any necessary prefix/path. `callback` has 1 parameter: < _Error_ >err.
* **delBox**(< _string_ >mailboxName, < _function_ >callback) - _(void)_ - Removes a specific mailbox that exists on the server. `mailboxName` should including any necessary prefix/path. `callback` has 1 parameter: < _Error_ >err.
* **renameBox**(< _string_ >oldMailboxName, < _string_ >newMailboxName, < _function_ >callback) - _(void)_ - Renames a specific mailbox that exists on the server. Both `oldMailboxName` and `newMailboxName` should include any necessary prefix/path. `callback` has 2 parameters: < _Error_ >err, < _Box_ >mailbox. **Note:** Renaming the 'INBOX' mailbox will instead cause all messages in 'INBOX' to be moved to the new mailbox.
* **subscribeBox**(< _string_ >mailboxName, < _function_ >callback) - _(void)_ - Subscribes to a specific mailbox that exists on the server. `mailboxName` should include any necessary prefix/path. `callback` has 1 parameter: < _Error_ >err.
* **unsubscribeBox**(< _string_ >mailboxName, < _function_ >callback) - _(void)_ - Unsubscribes from a specific mailbox that exists on the server. `mailboxName` should include any necessary prefix/path. `callback` has 1 parameter: < _Error_ >err.
* **status**(< _string_ >mailboxName, < _function_ >callback) - _(void)_ - Fetches information about a mailbox other than the one currently open. `callback` has 2 parameters: < _Error_ >err, < _Box_ >mailbox. **Note:** There is no guarantee that this will be a fast operation on the server. Also, do **not** call this on the currently open mailbox.
* **getBoxes**([< _string_ >nsPrefix, ]< _function_ >callback) - _(void)_ - Obtains the full list of mailboxes. If `nsPrefix` is not specified, the main personal namespace is used. `callback` has 2 parameters: < _Error_ >err, < _object_ >boxes. `boxes` has the following format (with example values):
```javascript
{ INBOX: // mailbox name
{ attribs: [], // mailbox attributes. An attribute of 'NOSELECT' indicates the mailbox cannot
// be opened
delimiter: '/', // hierarchy delimiter for accessing this mailbox's direct children.
children: null, // an object containing another structure similar in format to this top level,
// otherwise null if no children
parent: null // pointer to parent mailbox, null if at the top level
},
Work:
{ attribs: [],
delimiter: '/',
children: null,
parent: null
},
'[Gmail]':
{ attribs: [ '\\NOSELECT' ],
delimiter: '/',
children:
{ 'All Mail':
{ attribs: [ '\\All' ],
delimiter: '/',
children: null,
parent: [Circular]
},
Drafts:
{ attribs: [ '\\Drafts' ],
delimiter: '/',
children: null,
parent: [Circular]
},
Important:
{ attribs: [ '\\Important' ],
delimiter: '/',
children: null,
parent: [Circular]
},
'Sent Mail':
{ attribs: [ '\\Sent' ],
delimiter: '/',
children: null,
parent: [Circular]
},
Spam:
{ attribs: [ '\\Junk' ],
delimiter: '/',
children: null,
parent: [Circular]
},
Starred:
{ attribs: [ '\\Flagged' ],
delimiter: '/',
children: null,
parent: [Circular]
},
Trash:
{ attribs: [ '\\Trash' ],
delimiter: '/',
children: null,
parent: [Circular]
}
},
parent: null
}
}
```
* **getSubscribedBoxes**([< _string_ >nsPrefix, ]< _function_ >callback) - _(void)_ - Obtains the full list of subscribed mailboxes. If `nsPrefix` is not specified, the main personal namespace is used. `callback` has 2 parameters: < _Error_ >err, < _object_ >boxes. `boxes` has the same format as getBoxes above.
* **expunge**([< _MessageSource_ >uids, ]< _function_ >callback) - _(void)_ - Permanently removes all messages flagged as Deleted in the currently open mailbox. If the server supports the 'UIDPLUS' capability, `uids` can be supplied to only remove messages that both have their uid in `uids` and have the \Deleted flag set. `callback` has 1 parameter: < _Error_ >err. **Note:** At least on Gmail, performing this operation with any currently open mailbox that is not the Spam or Trash mailbox will merely archive any messages marked as Deleted (by moving them to the 'All Mail' mailbox).
* **append**(< _mixed_ >msgData, [< _object_ >options, ]< _function_ >callback) - _(void)_ - Appends a message to selected mailbox. `msgData` is a string or Buffer containing an RFC-822 compatible MIME message. Valid `options` properties are:
* **mailbox** - _string_ - The name of the mailbox to append the message to. **Default:** the currently open mailbox
* **flags** - _mixed_ - A single flag (e.g. 'Seen') or an _array_ of flags (e.g. `['Seen', 'Flagged']`) to append to the message. **Default:** (no flags)
* **date** - _Date_ - What to use for message arrival date/time. **Default:** (current date/time)
`callback` has 1 parameter: < _Error_ >err.
**All functions below have sequence number-based counterparts that can be accessed by using the 'seq' namespace of the imap connection's instance (e.g. conn.seq.search() returns sequence number(s) instead of UIDs, conn.seq.fetch() fetches by sequence number(s) instead of UIDs, etc):**
* **search**(< _array_ >criteria, < _function_ >callback) - _(void)_ - Searches the currently open mailbox for messages using given criteria. `criteria` is a list describing what you want to find. For criteria types that require arguments, use an _array_ instead of just the string criteria type name (e.g. ['FROM', 'foo@bar.com']). Prefix criteria types with an "!" to negate.
* The following message flags are valid types that do not have arguments:
* 'ALL' - All messages.
* 'ANSWERED' - Messages with the Answered flag set.
* 'DELETED' - Messages with the Deleted flag set.
* 'DRAFT' - Messages with the Draft flag set.
* 'FLAGGED' - Messages with the Flagged flag set.
* 'NEW' - Messages that have the Recent flag set but not the Seen flag.
* 'SEEN' - Messages that have the Seen flag set.
* 'RECENT' - Messages that have the Recent flag set.
* 'OLD' - Messages that do not have the Recent flag set. This is functionally equivalent to "!RECENT" (as opposed to "!NEW").
* 'UNANSWERED' - Messages that do not have the Answered flag set.
* 'UNDELETED' - Messages that do not have the Deleted flag set.
* 'UNDRAFT' - Messages that do not have the Draft flag set.
* 'UNFLAGGED' - Messages that do not have the Flagged flag set.
* 'UNSEEN' - Messages that do not have the Seen flag set.
* The following are valid types that require string value(s):
* 'BCC' - Messages that contain the specified string in the BCC field.
* 'CC' - Messages that contain the specified string in the CC field.
* 'FROM' - Messages that contain the specified string in the FROM field.
* 'SUBJECT' - Messages that contain the specified string in the SUBJECT field.
* 'TO' - Messages that contain the specified string in the TO field.
* 'BODY' - Messages that contain the specified string in the message body.
* 'TEXT' - Messages that contain the specified string in the header OR the message body.
* 'KEYWORD' - Messages with the specified keyword set.
* 'HEADER' - **Requires two string values, with the first being the header name and the second being the value to search for.** If this second string is empty, all messages that contain the given header name will be returned.
* The following are valid types that require a string parseable by JavaScript's Date object OR a Date instance:
* 'BEFORE' - Messages whose internal date (disregarding time and timezone) is earlier than the specified date.
* 'ON' - Messages whose internal date (disregarding time and timezone) is within the specified date.
* 'SINCE' - Messages whose internal date (disregarding time and timezone) is within or later than the specified date.
* 'SENTBEFORE' - Messages whose Date header (disregarding time and timezone) is earlier than the specified date.
* 'SENTON' - Messages whose Date header (disregarding time and timezone) is within the specified date.
* 'SENTSINCE' - Messages whose Date header (disregarding time and timezone) is within or later than the specified date.
* The following are valid types that require one Integer value:
* 'LARGER' - Messages with a size larger than the specified number of bytes.
* 'SMALLER' - Messages with a size smaller than the specified number of bytes.
* The following are valid criterion that require one or more Integer values:
* 'UID' - Messages with UIDs corresponding to the specified UID set. Ranges are permitted (e.g. '2504:2507' or '\*' or '2504:\*').
* **Note 1:** For the UID-based search (i.e. "conn.search()"), you can retrieve the UIDs for sequence numbers by just supplying an _array_ of sequence numbers and/or ranges as a criteria (e.g. [ '24:29', 19, '66:*' ]).
* **Note 2:** By default, all criterion are ANDed together. You can use the special 'OR' on **two** criterion to find messages matching either search criteria (see example below).
`criteria` examples:
* Unread messages since April 20, 2010: [ 'UNSEEN', ['SINCE', 'April 20, 2010'] ]
* Messages that are EITHER unread OR are dated April 20, 2010 or later, you could use: [ ['OR', 'UNSEEN', ['SINCE', 'April 20, 2010'] ] ]
* All messages that have 'node-imap' in the subject header: [ ['HEADER', 'SUBJECT', 'node-imap'] ]
* All messages that _do not_ have 'node-imap' in the subject header: [ ['!HEADER', 'SUBJECT', 'node-imap'] ]
`callback` has 2 parameters: < _Error_ >err, < _array_ >UIDs.
* **fetch**(< _MessageSource_ >source, [< _object_ >options]) - _ImapFetch_ - Fetches message(s) in the currently open mailbox.
Valid `options` properties are:
* **markSeen** - _boolean_ - Mark message(s) as read when fetched. **Default:** false
* **struct** - _boolean_ - Fetch the message structure. **Default:** false
* **envelope** - _boolean_ - Fetch the message envelope. **Default:** false
* **size** - _boolean_ - Fetch the RFC822 size. **Default:** false
* **modifiers** - _object_ - Fetch modifiers defined by IMAP extensions. **Default:** (none)
* **extensions** - _array_ - Fetch custom fields defined by IMAP extensions, e.g. ['X-MAILBOX', 'X-REAL-UID']. **Default:** (none)
* **bodies** - _mixed_ - A string or Array of strings containing the body part section to fetch. **Default:** (none) Example sections:
* 'HEADER' - The message header
* 'HEADER.FIELDS (TO FROM SUBJECT)' - Specific header fields only
* 'HEADER.FIELDS.NOT (TO FROM SUBJECT)' - Header fields only that do not match the fields given
* 'TEXT' - The message body
* '' - The entire message (header + body)
* 'MIME' - MIME-related header fields only (e.g. 'Content-Type')
**Note:** You can also prefix `bodies` strings (i.e. 'TEXT', 'HEADER', 'HEADER.FIELDS', and 'HEADER.FIELDS.NOT' for `message/rfc822` messages and 'MIME' for any kind of message) with part ids. For example: '1.TEXT', '1.2.HEADER', '2.MIME', etc.
**Note 2:** 'HEADER*' sections are only valid for parts whose content type is `message/rfc822`, including the root part (no part id).
* **copy**(< _MessageSource_ >source, < _string_ >mailboxName, < _function_ >callback) - _(void)_ - Copies message(s) in the currently open mailbox to another mailbox. `callback` has 1 parameter: < _Error_ >err.
* **move**(< _MessageSource_ >source, < _string_ >mailboxName, < _function_ >callback) - _(void)_ - Moves message(s) in the currently open mailbox to another mailbox. `callback` has 1 parameter: < _Error_ >err. **Note:** The message(s) in the destination mailbox will have a new message UID.
* **addFlags**(< _MessageSource_ >source, < _mixed_ >flags, < _function_ >callback) - _(void)_ - Adds flag(s) to message(s). `callback` has 1 parameter: < _Error_ >err.
* **delFlags**(< _MessageSource_ >source, < _mixed_ >flags, < _function_ >callback) - _(void)_ - Removes flag(s) from message(s). `callback` has 1 parameter: < _Error_ >err.
* **setFlags**(< _MessageSource_ >source, < _mixed_ >flags, < _function_ >callback) - _(void)_ - Sets the flag(s) for message(s). `callback` has 1 parameter: < _Error_ >err.
* **addKeywords**(< _MessageSource_ >source, < _mixed_ >keywords, < _function_ >callback) - _(void)_ - Adds keyword(s) to message(s). `keywords` is either a single keyword or an _array_ of keywords. `callback` has 1 parameter: < _Error_ >err.
* **delKeywords**(< _MessageSource_ >source, < _mixed_ >keywords, < _function_ >callback) - _(void)_ - Removes keyword(s) from message(s). `keywords` is either a single keyword or an _array_ of keywords. `callback` has 1 parameter: < _Error_ >err.
* **setKeywords**(< _MessageSource_ >source, < _mixed_ >keywords, < _function_ >callback) - _(void)_ - Sets keyword(s) for message(s). `keywords` is either a single keyword or an _array_ of keywords. `callback` has 1 parameter: < _Error_ >err.
* **serverSupports**(< _string_ >capability) - _boolean_ - Checks if the server supports the specified capability.
Extensions Supported
--------------------
* **Gmail**
* Server capability: X-GM-EXT-1
* search() criteria extensions:
* **X-GM-RAW** - _string_ - Gmail's custom search syntax. Example: 'has:attachment in:unread'
* **X-GM-THRID** - _string_ - Conversation/thread id
* **X-GM-MSGID** - _string_ - Account-wide unique id
* **X-GM-LABELS** - _string_ - Gmail label
* fetch() will automatically retrieve the thread id, unique message id, and labels (named 'x-gm-thrid', 'x-gm-msgid', 'x-gm-labels' respectively)
* Additional Connection instance methods (seqno-based counterparts exist):
* **setLabels**(< _MessageSource_ >source, < _mixed_ >labels, < _function_ >callback) - _(void)_ - Replaces labels of message(s) with `labels`. `labels` is either a single label or an _array_ of labels. `callback` has 1 parameter: < _Error_ >err.
* **addLabels**(< _MessageSource_ >source, < _mixed_ >labels, < _function_ >callback) - _(void)_ - Adds `labels` to message(s). `labels` is either a single label or an _array_ of labels. `callback` has 1 parameter: < _Error_ >err.
* **delLabels**(< _MessageSource_ >source, < _mixed_ >labels, < _function_ >callback) - _(void)_ - Removes `labels` from message(s). `labels` is either a single label or an _array_ of labels. `callback` has 1 parameter: < _Error_ >err.
* **RFC2087**
* Server capability: QUOTA
* Additional Connection instance methods:
* **setQuota**(< _string_ >quotaRoot, < _object_ >quotas, < _function_ >callback) - _(void)_ - Sets the resource limits for `quotaRoot` using the limits in `quotas`. `callback` has 2 parameters: < _Error_ >err, < _object_ >limits. `limits` has the same format as `limits` passed to getQuota()'s callback. Example `quotas` properties (taken from RFC2087):
* storage - Sum of messages' (RFC822) size, in kilobytes (integer).
* message - Number of messages (integer).
* **getQuota**(< _string_ >quotaRoot, < _function_ >callback) - _(void)_ - Gets the resource usage and limits for `quotaRoot`. `callback` has 2 parameters: < _Error_ >err, < _object_ >limits. `limits` is keyed on the resource name, where the values are objects with the following properties:
* usage - _integer_ - Resource usage.
* limit - _integer_ - Resource limit.
* **getQuotaRoot**(< _string_ >mailbox, < _function_ >callback) - _(void)_ - Gets the list of quota roots for `mailbox` and the resource usage and limits for each. `callback` has 2 parameters: < _Error_ >err, < _object_ >info. `info` is keyed on the quota root name, where the values are objects structured like `limits` given by getQuota(). Example `info`:
```javascript
{
'': {
storage: { usage: 20480, limit: 102400 }
},
foo: {
storage: { usage: 1024, limit: 4096 },
message: { usage: 14, limit: 9001 }
}
}
```
* **RFC4315**
* Server capability: UIDPLUS
* The callback passed to append() will receive an additional argument (the UID of the appended message): < _integer_ >appendedUID.
* The callback passed to copy(), move(), seq.copy(), seq.move() will receive an additional argument (the UID(s) of the copied message(s) in the destination mailbox): < _mixed_ >newUIDs. `newUIDs` can be an integer if just one message was copied, or a string for multiple messages (e.g. '100:103' or '100,125,130' or '100,200:201').
* **RFC4551**
* Server capability: CONDSTORE
* Connection event 'update' info may contain the additional property:
* **modseq** - _string_ - The new modification sequence value for the message.
* search() criteria extensions:
* **MODSEQ** - _string_ - Modification sequence value. If this criteria is used, the callback parameters are then: < _Error_ >err, < _array_ >UIDs, < _string_ >modseq. The `modseq` callback parameter is the highest modification sequence value of all the messages identified in the search results.
* fetch() will automatically retrieve the modification sequence value (named 'modseq') for each message.
* fetch() modifier:
* **changedsince** - _string_ - Only fetch messages that have changed since the specified modification sequence.
* The _Box_ type can now have the following property when using openBox() or status():
* **highestmodseq** - _string_ - The highest modification sequence value of all messages in the mailbox.
* Additional Connection instance methods (seqno-based counterparts exist):
* **addFlagsSince**(< _MessageSource_ >source, < _mixed_ >flags, < _string_ >modseq, < _function_ >callback) - _(void)_ - Adds flag(s) to message(s) that have not changed since `modseq`. `flags` is either a single flag or an _array_ of flags. `callback` has 1 parameter: < _Error_ >err.
* **delFlagsSince**(< _MessageSource_ >source, < _mixed_ >flags, < _string_ >modseq, < _function_ >callback) - _(void)_ - Removes flag(s) from message(s) that have not changed since `modseq`. `flags` is either a single flag or an _array_ of flags. `callback` has 1 parameter: < _Error_ >err.
* **setFlagsSince**(< _MessageSource_ >source, < _mixed_ >flags, < _string_ >modseq, < _function_ >callback) - _(void)_ - Sets the flag(s) for message(s) that have not changed since `modseq`. `flags` is either a single flag or an _array_ of flags. `callback` has 1 parameter: < _Error_ >err.
* **addKeywordsSince**(< _MessageSource_ >source, < _mixed_ >keywords, < _string_ >modseq, < _function_ >callback) - _(void)_ - Adds keyword(s) to message(s) that have not changed since `modseq`. `keywords` is either a single keyword or an _array_ of keywords. `callback` has 1 parameter: < _Error_ >err.
* **delKeywordsSince**(< _MessageSource_ >source, < _mixed_ >keywords, < _string_ >modseq, < _function_ >callback) - _(void)_ - Removes keyword(s) from message(s) that have not changed since `modseq`. `keywords` is either a single keyword or an _array_ of keywords. `callback` has 1 parameter: < _Error_ >err.
* **setKeywordsSince**(< _MessageSource_ >source, < _mixed_ >keywords, < _string_ >modseq, < _function_ >callback) - _(void)_ - Sets keyword(s) for message(s) that have not changed since `modseq`. `keywords` is either a single keyword or an _array_ of keywords. `callback` has 1 parameter: < _Error_ >err.
* **RFC4731**
* Server capability: ESEARCH
* Additional Connection instance methods (seqno-based counterpart exists):
* **esearch**(< _array_ >criteria, < _array_ >options, < _function_ >callback) - _(void)_ - A variant of search() that can return metadata about results. `callback` has 2 parameters: < _Error_ >err, < _object_ >info. `info` has possible keys: 'all', 'min', 'max', 'count'. Valid `options`:
* 'ALL' - Retrieves UIDs in a compact form (e.g. [2, '10:11'] instead of search()'s [2, 10, 11]) that match the criteria.
* 'MIN' - Retrieves the lowest UID that satisfies the criteria.
* 'MAX' - Retrieves the highest UID that satisfies the criteria.
* 'COUNT' - Retrieves the number of messages that satisfy the criteria.
**Note:** specifying no `options` or [] for `options` is the same as ['ALL']
* **RFC5256**
* Server capability: SORT
* Additional Connection instance methods (seqno-based counterpart exists):
* **sort**(< _array_ >sortCriteria, < _array_ >searchCriteria, < _function_ >callback) - _(void)_ - Performs a sorted search(). A seqno-based counterpart also exists for this function. `callback` has 2 parameters: < _Error_ >err, < _array_ >UIDs. Valid `sortCriteria` are (reverse sorting of individual criteria is done by prefixing the criteria with '-'):
* 'ARRIVAL' - Internal date and time of the message. This differs from the ON criteria in search(), which uses just the internal date.
* 'CC' - The mailbox of the **first** "cc" address.
* 'DATE' - Message sent date and time.
* 'FROM' - The mailbox of the **first** "from" address.
* 'SIZE' - Size of the message in octets.
* 'SUBJECT' - Base subject text.
* 'TO' - The mailbox of the **first** "to" address.
* Server capability: THREAD=REFERENCES, THREAD=ORDEREDSUBJECT
* Additional Connection instance methods (seqno-based counterpart exists):
* **thread**(< _string_ >algorithm, < _array_ >searchCriteria, < _function_ >callback) - _(void)_ - Performs a regular search with `searchCriteria` and groups the resulting search results using the given `algorithm` (e.g. 'references', 'orderedsubject'). `callback` has 2 parameters: < _Error_ >err, < _array_ >UIDs. `UIDs` is a nested array.
TODO
----
Several things not yet implemented in no particular order:
* Support additional IMAP commands/extensions:
* NOTIFY (via NOTIFY extension -- RFC5465)
* STATUS addition to LIST (via LIST-STATUS extension -- RFC5819)
* QRESYNC (RFC5162)

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,2340 @@
/*
Modifications for better node.js integration:
Copyright 2013 Brian White. All rights reserved.
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.
*/
/*
Original source code:
Copyright 2012 Joshua Bell
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
//
// Utilities
//
/**
* @param {number} a The number to test.
* @param {number} min The minimum value in the range, inclusive.
* @param {number} max The maximum value in the range, inclusive.
* @return {boolean} True if a >= min and a <= max.
*/
function inRange(a, min, max) {
return min <= a && a <= max;
}
/**
* @param {number} n The numerator.
* @param {number} d The denominator.
* @return {number} The result of the integer division of n by d.
*/
function div(n, d) {
return Math.floor(n / d);
}
//
// Implementation of Encoding specification
// http://dvcs.w3.org/hg/encoding/raw-file/tip/Overview.html
//
//
// 3. Terminology
//
//
// 4. Encodings
//
/** @const */ var EOF_byte = -1;
/** @const */ var EOF_code_point = -1;
/**
* @constructor
* @param {Buffer} bytes Array of bytes that provide the stream.
*/
function ByteInputStream(bytes) {
/** @type {number} */
var pos = 0;
/** @return {number} Get the next byte from the stream. */
this.get = function() {
return (pos >= bytes.length) ? EOF_byte : Number(bytes[pos]);
};
/** @param {number} n Number (positive or negative) by which to
* offset the byte pointer. */
this.offset = function(n) {
pos += n;
if (pos < 0) {
throw new Error('Seeking past start of the buffer');
}
if (pos > bytes.length) {
throw new Error('Seeking past EOF');
}
};
/**
* @param {Array.<number>} test Array of bytes to compare against.
* @return {boolean} True if the start of the stream matches the test
* bytes.
*/
this.match = function(test) {
if (test.length > pos + bytes.length) {
return false;
}
var i;
for (i = 0; i < test.length; i += 1) {
if (Number(bytes[pos + i]) !== test[i]) {
return false;
}
}
return true;
};
}
/**
* @constructor
* @param {Array.<number>} bytes The array to write bytes into.
*/
function ByteOutputStream(bytes) {
/** @type {number} */
var pos = 0;
/**
* @param {...number} var_args The byte or bytes to emit into the stream.
* @return {number} The last byte emitted.
*/
this.emit = function(var_args) {
/** @type {number} */
var last = EOF_byte;
var i;
for (i = 0; i < arguments.length; ++i) {
last = Number(arguments[i]);
bytes[pos++] = last;
}
return last;
};
}
/**
* @constructor
* @param {string} string The source of code units for the stream.
*/
function CodePointInputStream(string) {
/**
* @param {string} string Input string of UTF-16 code units.
* @return {Array.<number>} Code points.
*/
function stringToCodePoints(string) {
/** @type {Array.<number>} */
var cps = [];
// Based on http://www.w3.org/TR/WebIDL/#idl-DOMString
var i = 0, n = string.length;
while (i < string.length) {
var c = string.charCodeAt(i);
if (!inRange(c, 0xD800, 0xDFFF)) {
cps.push(c);
} else if (inRange(c, 0xDC00, 0xDFFF)) {
cps.push(0xFFFD);
} else { // (inRange(cu, 0xD800, 0xDBFF))
if (i === n - 1) {
cps.push(0xFFFD);
} else {
var d = string.charCodeAt(i + 1);
if (inRange(d, 0xDC00, 0xDFFF)) {
var a = c & 0x3FF;
var b = d & 0x3FF;
i += 1;
cps.push(0x10000 + (a << 10) + b);
} else {
cps.push(0xFFFD);
}
}
}
i += 1;
}
return cps;
}
/** @type {number} */
var pos = 0;
/** @type {Array.<number>} */
var cps = stringToCodePoints(string);
/** @param {number} n The number of bytes (positive or negative)
* to advance the code point pointer by.*/
this.offset = function(n) {
pos += n;
if (pos < 0) {
throw new Error('Seeking past start of the buffer');
}
if (pos > cps.length) {
throw new Error('Seeking past EOF');
}
};
/** @return {number} Get the next code point from the stream. */
this.get = function() {
if (pos >= cps.length) {
return EOF_code_point;
}
return cps[pos];
};
}
/**
* @constructor
*/
function CodePointOutputStream() {
/** @type {string} */
var string = '';
/** @return {string} The accumulated string. */
this.string = function() {
return string;
};
/** @param {number} c The code point to encode into the stream. */
this.emit = function(c) {
if (c <= 0xFFFF) {
string += String.fromCharCode(c);
} else {
c -= 0x10000;
string += String.fromCharCode(0xD800 + ((c >> 10) & 0x3ff));
string += String.fromCharCode(0xDC00 + (c & 0x3ff));
}
};
}
/**
* @constructor
* @param {string} message Description of the error.
*/
function EncodingError(message) {
this.name = 'EncodingError';
this.message = message;
this.code = 0;
}
EncodingError.prototype = Error.prototype;
/**
* @param {boolean} fatal If true, decoding errors raise an exception.
* @param {number=} opt_code_point Override the standard fallback code point.
* @return {number} The code point to insert on a decoding error.
*/
function decoderError(fatal, opt_code_point) {
if (fatal) {
throw new EncodingError('Decoder error');
}
return opt_code_point || 0xFFFD;
}
/**
* @param {number} code_point The code point that could not be encoded.
*/
function encoderError(code_point) {
throw new EncodingError('The code point ' + code_point +
' could not be encoded.');
}
/**
* @param {string} label The encoding label.
* @return {?{name:string,labels:Array.<string>}}
*/
function getEncoding(label) {
label = String(label).trim().toLowerCase();
if (Object.prototype.hasOwnProperty.call(label_to_encoding, label)) {
return label_to_encoding[label];
}
return null;
}
/** @type {Array.<{encodings: Array.<{name:string,labels:Array.<string>}>,
* heading: string}>} */
var encodings = [
{
"encodings": [
{
"labels": [
"unicode-1-1-utf-8",
"utf-8",
"utf8"
],
"name": "utf-8"
}
],
"heading": "The Encoding"
},
{
"encodings": [
{
"labels": [
"864",
"cp864",
"csibm864",
"ibm864"
],
"name": "ibm864"
},
{
"labels": [
"866",
"cp866",
"csibm866",
"ibm866"
],
"name": "ibm866"
},
{
"labels": [
"csisolatin2",
"iso-8859-2",
"iso-ir-101",
"iso8859-2",
"iso88592",
"iso_8859-2",
"iso_8859-2:1987",
"l2",
"latin2"
],
"name": "iso-8859-2"
},
{
"labels": [
"csisolatin3",
"iso-8859-3",
"iso-ir-109",
"iso8859-3",
"iso88593",
"iso_8859-3",
"iso_8859-3:1988",
"l3",
"latin3"
],
"name": "iso-8859-3"
},
{
"labels": [
"csisolatin4",
"iso-8859-4",
"iso-ir-110",
"iso8859-4",
"iso88594",
"iso_8859-4",
"iso_8859-4:1988",
"l4",
"latin4"
],
"name": "iso-8859-4"
},
{
"labels": [
"csisolatincyrillic",
"cyrillic",
"iso-8859-5",
"iso-ir-144",
"iso8859-5",
"iso88595",
"iso_8859-5",
"iso_8859-5:1988"
],
"name": "iso-8859-5"
},
{
"labels": [
"arabic",
"asmo-708",
"csiso88596e",
"csiso88596i",
"csisolatinarabic",
"ecma-114",
"iso-8859-6",
"iso-8859-6-e",
"iso-8859-6-i",
"iso-ir-127",
"iso8859-6",
"iso88596",
"iso_8859-6",
"iso_8859-6:1987"
],
"name": "iso-8859-6"
},
{
"labels": [
"csisolatingreek",
"ecma-118",
"elot_928",
"greek",
"greek8",
"iso-8859-7",
"iso-ir-126",
"iso8859-7",
"iso88597",
"iso_8859-7",
"iso_8859-7:1987",
"sun_eu_greek"
],
"name": "iso-8859-7"
},
{
"labels": [
"csiso88598e",
"csisolatinhebrew",
"hebrew",
"iso-8859-8",
"iso-8859-8-e",
"iso-ir-138",
"iso8859-8",
"iso88598",
"iso_8859-8",
"iso_8859-8:1988",
"visual"
],
"name": "iso-8859-8"
},
{
"labels": [
"csiso88598i",
"iso-8859-8-i",
"logical"
],
"name": "iso-8859-8-i"
},
{
"labels": [
"csisolatin6",
"iso-8859-10",
"iso-ir-157",
"iso8859-10",
"iso885910",
"l6",
"latin6"
],
"name": "iso-8859-10"
},
{
"labels": [
"iso-8859-13",
"iso8859-13",
"iso885913"
],
"name": "iso-8859-13"
},
{
"labels": [
"iso-8859-14",
"iso8859-14",
"iso885914"
],
"name": "iso-8859-14"
},
{
"labels": [
"csisolatin9",
"iso-8859-15",
"iso8859-15",
"iso885915",
"iso_8859-15",
"l9"
],
"name": "iso-8859-15"
},
{
"labels": [
"iso-8859-16"
],
"name": "iso-8859-16"
},
{
"labels": [
"cskoi8r",
"koi",
"koi8",
"koi8-r",
"koi8_r"
],
"name": "koi8-r"
},
{
"labels": [
"koi8-u"
],
"name": "koi8-u"
},
{
"labels": [
"csmacintosh",
"mac",
"macintosh",
"x-mac-roman"
],
"name": "macintosh"
},
{
"labels": [
"dos-874",
"iso-8859-11",
"iso8859-11",
"iso885911",
"tis-620",
"windows-874"
],
"name": "windows-874"
},
{
"labels": [
"cp1250",
"windows-1250",
"x-cp1250"
],
"name": "windows-1250"
},
{
"labels": [
"cp1251",
"windows-1251",
"x-cp1251"
],
"name": "windows-1251"
},
{
"labels": [
"ansi_x3.4-1968",
"ascii",
"cp1252",
"cp819",
"csisolatin1",
"ibm819",
"iso-8859-1",
"iso-ir-100",
"iso8859-1",
"iso88591",
"iso_8859-1",
"iso_8859-1:1987",
"l1",
"latin1",
"us-ascii",
"windows-1252",
"x-cp1252"
],
"name": "windows-1252"
},
{
"labels": [
"cp1253",
"windows-1253",
"x-cp1253"
],
"name": "windows-1253"
},
{
"labels": [
"cp1254",
"csisolatin5",
"iso-8859-9",
"iso-ir-148",
"iso8859-9",
"iso88599",
"iso_8859-9",
"iso_8859-9:1989",
"l5",
"latin5",
"windows-1254",
"x-cp1254"
],
"name": "windows-1254"
},
{
"labels": [
"cp1255",
"windows-1255",
"x-cp1255"
],
"name": "windows-1255"
},
{
"labels": [
"cp1256",
"windows-1256",
"x-cp1256"
],
"name": "windows-1256"
},
{
"labels": [
"cp1257",
"windows-1257",
"x-cp1257"
],
"name": "windows-1257"
},
{
"labels": [
"cp1258",
"windows-1258",
"x-cp1258"
],
"name": "windows-1258"
},
{
"labels": [
"x-mac-cyrillic",
"x-mac-ukrainian"
],
"name": "x-mac-cyrillic"
}
],
"heading": "Legacy single-byte encodings"
},
{
"encodings": [
{
"labels": [
"chinese",
"csgb2312",
"csiso58gb231280",
"gb2312",
"gb_2312",
"gb_2312-80",
"gbk",
"iso-ir-58",
"x-gbk"
],
"name": "gbk"
},
{
"labels": [
"gb18030"
],
"name": "gb18030"
},
{
"labels": [
"hz-gb-2312"
],
"name": "hz-gb-2312"
}
],
"heading": "Legacy multi-byte Chinese (simplified) encodings"
},
{
"encodings": [
{
"labels": [
"big5",
"big5-hkscs",
"cn-big5",
"csbig5",
"x-x-big5"
],
"name": "big5"
}
],
"heading": "Legacy multi-byte Chinese (traditional) encodings"
},
{
"encodings": [
{
"labels": [
"cseucpkdfmtjapanese",
"euc-jp",
"x-euc-jp"
],
"name": "euc-jp"
},
{
"labels": [
"csiso2022jp",
"iso-2022-jp"
],
"name": "iso-2022-jp"
},
{
"labels": [
"csshiftjis",
"ms_kanji",
"shift-jis",
"shift_jis",
"sjis",
"windows-31j",
"x-sjis"
],
"name": "shift_jis"
}
],
"heading": "Legacy multi-byte Japanese encodings"
},
{
"encodings": [
{
"labels": [
"cseuckr",
"csksc56011987",
"euc-kr",
"iso-ir-149",
"korean",
"ks_c_5601-1987",
"ks_c_5601-1989",
"ksc5601",
"ksc_5601",
"windows-949"
],
"name": "euc-kr"
}
],
"heading": "Legacy multi-byte Korean encodings"
},
{
"encodings": [
{
"labels": [
"csiso2022kr",
"iso-2022-kr",
"iso-2022-cn",
"iso-2022-cn-ext"
],
"name": "replacement"
},
{
"labels": [
"utf-16be"
],
"name": "utf-16be"
},
{
"labels": [
"utf-16",
"utf-16le"
],
"name": "utf-16le"
},
{
"labels": [
"x-user-defined"
],
"name": "x-user-defined"
}
],
"heading": "Legacy miscellaneous encodings"
}
];
var name_to_encoding = {};
var label_to_encoding = {};
encodings.forEach(function(category) {
category.encodings.forEach(function(encoding) {
name_to_encoding[encoding.name] = encoding;
encoding.labels.forEach(function(label) {
label_to_encoding[label] = encoding;
});
});
});
//
// 5. Indexes
//
/**
* @param {number} pointer The |pointer| to search for.
* @param {Array.<?number>} index The |index| to search within.
* @return {?number} The code point corresponding to |pointer| in |index|,
* or null if |code point| is not in |index|.
*/
function indexCodePointFor(pointer, index) {
return (index || [])[pointer] || null;
}
/**
* @param {number} code_point The |code point| to search for.
* @param {Array.<?number>} index The |index| to search within.
* @return {?number} The first pointer corresponding to |code point| in
* |index|, or null if |code point| is not in |index|.
*/
function indexPointerFor(code_point, index) {
var pointer = index.indexOf(code_point);
return pointer === -1 ? null : pointer;
}
/** @type {Object.<string, (Array.<number>|Array.<Array.<number>>)>} */
var indexes = require('./encoding-indexes');
/**
* @param {number} pointer The |pointer| to search for in the gb18030 index.
* @return {?number} The code point corresponding to |pointer| in |index|,
* or null if |code point| is not in the gb18030 index.
*/
function indexGB18030CodePointFor(pointer) {
if ((pointer > 39419 && pointer < 189000) || (pointer > 1237575)) {
return null;
}
var /** @type {number} */ offset = 0,
/** @type {number} */ code_point_offset = 0,
/** @type {Array.<Array.<number>>} */ index = indexes['gb18030'];
var i;
for (i = 0; i < index.length; ++i) {
var entry = index[i];
if (entry[0] <= pointer) {
offset = entry[0];
code_point_offset = entry[1];
} else {
break;
}
}
return code_point_offset + pointer - offset;
}
/**
* @param {number} code_point The |code point| to locate in the gb18030 index.
* @return {number} The first pointer corresponding to |code point| in the
* gb18030 index.
*/
function indexGB18030PointerFor(code_point) {
var /** @type {number} */ offset = 0,
/** @type {number} */ pointer_offset = 0,
/** @type {Array.<Array.<number>>} */ index = indexes['gb18030'];
var i;
for (i = 0; i < index.length; ++i) {
var entry = index[i];
if (entry[1] <= code_point) {
offset = entry[1];
pointer_offset = entry[0];
} else {
break;
}
}
return pointer_offset + code_point - offset;
}
//
// 7. The encoding
//
// 7.1 utf-8
/**
* @constructor
* @param {{fatal: boolean}} options
*/
function UTF8Decoder(options) {
var fatal = options.fatal;
var /** @type {number} */ utf8_code_point = 0,
/** @type {number} */ utf8_bytes_needed = 0,
/** @type {number} */ utf8_bytes_seen = 0,
/** @type {number} */ utf8_lower_boundary = 0;
/**
* @param {ByteInputStream} byte_pointer The byte stream to decode.
* @return {?number} The next code point decoded, or null if not enough
* data exists in the input stream to decode a complete code point.
*/
this.decode = function(byte_pointer) {
var bite = byte_pointer.get();
if (bite === EOF_byte) {
if (utf8_bytes_needed !== 0) {
return decoderError(fatal);
}
return EOF_code_point;
}
byte_pointer.offset(1);
if (utf8_bytes_needed === 0) {
if (inRange(bite, 0x00, 0x7F)) {
return bite;
}
if (inRange(bite, 0xC2, 0xDF)) {
utf8_bytes_needed = 1;
utf8_lower_boundary = 0x80;
utf8_code_point = bite - 0xC0;
} else if (inRange(bite, 0xE0, 0xEF)) {
utf8_bytes_needed = 2;
utf8_lower_boundary = 0x800;
utf8_code_point = bite - 0xE0;
} else if (inRange(bite, 0xF0, 0xF4)) {
utf8_bytes_needed = 3;
utf8_lower_boundary = 0x10000;
utf8_code_point = bite - 0xF0;
} else {
return decoderError(fatal);
}
utf8_code_point = utf8_code_point * Math.pow(64, utf8_bytes_needed);
return null;
}
if (!inRange(bite, 0x80, 0xBF)) {
utf8_code_point = 0;
utf8_bytes_needed = 0;
utf8_bytes_seen = 0;
utf8_lower_boundary = 0;
byte_pointer.offset(-1);
return decoderError(fatal);
}
utf8_bytes_seen += 1;
utf8_code_point = utf8_code_point + (bite - 0x80) *
Math.pow(64, utf8_bytes_needed - utf8_bytes_seen);
if (utf8_bytes_seen !== utf8_bytes_needed) {
return null;
}
var code_point = utf8_code_point;
var lower_boundary = utf8_lower_boundary;
utf8_code_point = 0;
utf8_bytes_needed = 0;
utf8_bytes_seen = 0;
utf8_lower_boundary = 0;
if (inRange(code_point, lower_boundary, 0x10FFFF) &&
!inRange(code_point, 0xD800, 0xDFFF)) {
return code_point;
}
return decoderError(fatal);
};
}
/**
* @constructor
* @param {{fatal: boolean}} options
*/
function UTF8Encoder(options) {
var fatal = options.fatal;
/**
* @param {ByteOutputStream} output_byte_stream Output byte stream.
* @param {CodePointInputStream} code_point_pointer Input stream.
* @return {number} The last byte emitted.
*/
this.encode = function(output_byte_stream, code_point_pointer) {
var code_point = code_point_pointer.get();
if (code_point === EOF_code_point) {
return EOF_byte;
}
code_point_pointer.offset(1);
if (inRange(code_point, 0xD800, 0xDFFF)) {
return encoderError(code_point);
}
if (inRange(code_point, 0x0000, 0x007f)) {
return output_byte_stream.emit(code_point);
}
var count, offset;
if (inRange(code_point, 0x0080, 0x07FF)) {
count = 1;
offset = 0xC0;
} else if (inRange(code_point, 0x0800, 0xFFFF)) {
count = 2;
offset = 0xE0;
} else if (inRange(code_point, 0x10000, 0x10FFFF)) {
count = 3;
offset = 0xF0;
}
var result = output_byte_stream.emit(
div(code_point, Math.pow(64, count)) + offset);
while (count > 0) {
var temp = div(code_point, Math.pow(64, count - 1));
result = output_byte_stream.emit(0x80 + (temp % 64));
count -= 1;
}
return result;
};
}
name_to_encoding['utf-8'].getEncoder = function(options) {
return new UTF8Encoder(options);
};
name_to_encoding['utf-8'].getDecoder = function(options) {
return new UTF8Decoder(options);
};
//
// 8. Legacy single-byte encodings
//
/**
* @constructor
* @param {Array.<number>} index The encoding index.
* @param {{fatal: boolean}} options
*/
function SingleByteDecoder(index, options) {
var fatal = options.fatal;
/**
* @param {ByteInputStream} byte_pointer The byte stream to decode.
* @return {?number} The next code point decoded, or null if not enough
* data exists in the input stream to decode a complete code point.
*/
this.decode = function(byte_pointer) {
var bite = byte_pointer.get();
if (bite === EOF_byte) {
return EOF_code_point;
}
byte_pointer.offset(1);
if (inRange(bite, 0x00, 0x7F)) {
return bite;
}
var code_point = index[bite - 0x80];
if (code_point === null) {
return decoderError(fatal);
}
return code_point;
};
}
/**
* @constructor
* @param {Array.<?number>} index The encoding index.
* @param {{fatal: boolean}} options
*/
function SingleByteEncoder(index, options) {
var fatal = options.fatal;
/**
* @param {ByteOutputStream} output_byte_stream Output byte stream.
* @param {CodePointInputStream} code_point_pointer Input stream.
* @return {number} The last byte emitted.
*/
this.encode = function(output_byte_stream, code_point_pointer) {
var code_point = code_point_pointer.get();
if (code_point === EOF_code_point) {
return EOF_byte;
}
code_point_pointer.offset(1);
if (inRange(code_point, 0x0000, 0x007F)) {
return output_byte_stream.emit(code_point);
}
var pointer = indexPointerFor(code_point, index);
if (pointer === null) {
encoderError(code_point);
}
return output_byte_stream.emit(pointer + 0x80);
};
}
(function() {
encodings.forEach(function(category) {
if (category.heading !== 'Legacy single-byte encodings')
return;
category.encodings.forEach(function(encoding) {
var index = indexes[encoding.name];
encoding.getDecoder = function(options) {
return new SingleByteDecoder(index, options);
};
encoding.getEncoder = function(options) {
return new SingleByteEncoder(index, options);
};
});
});
}());
//
// 9. Legacy multi-byte Chinese (simplified) encodings
//
// 9.1 gbk
/**
* @constructor
* @param {boolean} gb18030 True if decoding gb18030, false otherwise.
* @param {{fatal: boolean}} options
*/
function GBKDecoder(gb18030, options) {
var fatal = options.fatal;
var /** @type {number} */ gbk_first = 0x00,
/** @type {number} */ gbk_second = 0x00,
/** @type {number} */ gbk_third = 0x00;
/**
* @param {ByteInputStream} byte_pointer The byte stream to decode.
* @return {?number} The next code point decoded, or null if not enough
* data exists in the input stream to decode a complete code point.
*/
this.decode = function(byte_pointer) {
var bite = byte_pointer.get();
if (bite === EOF_byte && gbk_first === 0x00 &&
gbk_second === 0x00 && gbk_third === 0x00) {
return EOF_code_point;
}
if (bite === EOF_byte &&
(gbk_first !== 0x00 || gbk_second !== 0x00 || gbk_third !== 0x00)) {
gbk_first = 0x00;
gbk_second = 0x00;
gbk_third = 0x00;
decoderError(fatal);
}
byte_pointer.offset(1);
var code_point;
if (gbk_third !== 0x00) {
code_point = null;
if (inRange(bite, 0x30, 0x39)) {
code_point = indexGB18030CodePointFor(
(((gbk_first - 0x81) * 10 + (gbk_second - 0x30)) * 126 +
(gbk_third - 0x81)) * 10 + bite - 0x30);
}
gbk_first = 0x00;
gbk_second = 0x00;
gbk_third = 0x00;
if (code_point === null) {
byte_pointer.offset(-3);
return decoderError(fatal);
}
return code_point;
}
if (gbk_second !== 0x00) {
if (inRange(bite, 0x81, 0xFE)) {
gbk_third = bite;
return null;
}
byte_pointer.offset(-2);
gbk_first = 0x00;
gbk_second = 0x00;
return decoderError(fatal);
}
if (gbk_first !== 0x00) {
if (inRange(bite, 0x30, 0x39) && gb18030) {
gbk_second = bite;
return null;
}
var lead = gbk_first;
var pointer = null;
gbk_first = 0x00;
var offset = bite < 0x7F ? 0x40 : 0x41;
if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0x80, 0xFE)) {
pointer = (lead - 0x81) * 190 + (bite - offset);
}
code_point = pointer === null ? null :
indexCodePointFor(pointer, indexes['gbk']);
if (pointer === null) {
byte_pointer.offset(-1);
}
if (code_point === null) {
return decoderError(fatal);
}
return code_point;
}
if (inRange(bite, 0x00, 0x7F)) {
return bite;
}
if (bite === 0x80) {
return 0x20AC;
}
if (inRange(bite, 0x81, 0xFE)) {
gbk_first = bite;
return null;
}
return decoderError(fatal);
};
}
/**
* @constructor
* @param {boolean} gb18030 True if decoding gb18030, false otherwise.
* @param {{fatal: boolean}} options
*/
function GBKEncoder(gb18030, options) {
var fatal = options.fatal;
/**
* @param {ByteOutputStream} output_byte_stream Output byte stream.
* @param {CodePointInputStream} code_point_pointer Input stream.
* @return {number} The last byte emitted.
*/
this.encode = function(output_byte_stream, code_point_pointer) {
var code_point = code_point_pointer.get();
if (code_point === EOF_code_point) {
return EOF_byte;
}
code_point_pointer.offset(1);
if (inRange(code_point, 0x0000, 0x007F)) {
return output_byte_stream.emit(code_point);
}
var pointer = indexPointerFor(code_point, indexes['gbk']);
if (pointer !== null) {
var lead = div(pointer, 190) + 0x81;
var trail = pointer % 190;
var offset = trail < 0x3F ? 0x40 : 0x41;
return output_byte_stream.emit(lead, trail + offset);
}
if (pointer === null && !gb18030) {
return encoderError(code_point);
}
pointer = indexGB18030PointerFor(code_point);
var byte1 = div(div(div(pointer, 10), 126), 10);
pointer = pointer - byte1 * 10 * 126 * 10;
var byte2 = div(div(pointer, 10), 126);
pointer = pointer - byte2 * 10 * 126;
var byte3 = div(pointer, 10);
var byte4 = pointer - byte3 * 10;
return output_byte_stream.emit(byte1 + 0x81,
byte2 + 0x30,
byte3 + 0x81,
byte4 + 0x30);
};
}
name_to_encoding['gbk'].getEncoder = function(options) {
return new GBKEncoder(false, options);
};
name_to_encoding['gbk'].getDecoder = function(options) {
return new GBKDecoder(false, options);
};
// 9.2 gb18030
name_to_encoding['gb18030'].getEncoder = function(options) {
return new GBKEncoder(true, options);
};
name_to_encoding['gb18030'].getDecoder = function(options) {
return new GBKDecoder(true, options);
};
// 9.3 hz-gb-2312
/**
* @constructor
* @param {{fatal: boolean}} options
*/
function HZGB2312Decoder(options) {
var fatal = options.fatal;
var /** @type {boolean} */ hzgb2312 = false,
/** @type {number} */ hzgb2312_lead = 0x00;
/**
* @param {ByteInputStream} byte_pointer The byte stream to decode.
* @return {?number} The next code point decoded, or null if not enough
* data exists in the input stream to decode a complete code point.
*/
this.decode = function(byte_pointer) {
var bite = byte_pointer.get();
if (bite === EOF_byte && hzgb2312_lead === 0x00) {
return EOF_code_point;
}
if (bite === EOF_byte && hzgb2312_lead !== 0x00) {
hzgb2312_lead = 0x00;
return decoderError(fatal);
}
byte_pointer.offset(1);
if (hzgb2312_lead === 0x7E) {
hzgb2312_lead = 0x00;
if (bite === 0x7B) {
hzgb2312 = true;
return null;
}
if (bite === 0x7D) {
hzgb2312 = false;
return null;
}
if (bite === 0x7E) {
return 0x007E;
}
if (bite === 0x0A) {
return null;
}
byte_pointer.offset(-1);
return decoderError(fatal);
}
if (hzgb2312_lead !== 0x00) {
var lead = hzgb2312_lead;
hzgb2312_lead = 0x00;
var code_point = null;
if (inRange(bite, 0x21, 0x7E)) {
code_point = indexCodePointFor((lead - 1) * 190 +
(bite + 0x3F), indexes['gbk']);
}
if (bite === 0x0A) {
hzgb2312 = false;
}
if (code_point === null) {
return decoderError(fatal);
}
return code_point;
}
if (bite === 0x7E) {
hzgb2312_lead = 0x7E;
return null;
}
if (hzgb2312) {
if (inRange(bite, 0x20, 0x7F)) {
hzgb2312_lead = bite;
return null;
}
if (bite === 0x0A) {
hzgb2312 = false;
}
return decoderError(fatal);
}
if (inRange(bite, 0x00, 0x7F)) {
return bite;
}
return decoderError(fatal);
};
}
/**
* @constructor
* @param {{fatal: boolean}} options
*/
function HZGB2312Encoder(options) {
var fatal = options.fatal;
var hzgb2312 = false;
/**
* @param {ByteOutputStream} output_byte_stream Output byte stream.
* @param {CodePointInputStream} code_point_pointer Input stream.
* @return {number} The last byte emitted.
*/
this.encode = function(output_byte_stream, code_point_pointer) {
var code_point = code_point_pointer.get();
if (code_point === EOF_code_point) {
return EOF_byte;
}
code_point_pointer.offset(1);
if (inRange(code_point, 0x0000, 0x007F) && hzgb2312) {
code_point_pointer.offset(-1);
hzgb2312 = false;
return output_byte_stream.emit(0x7E, 0x7D);
}
if (code_point === 0x007E) {
return output_byte_stream.emit(0x7E, 0x7E);
}
if (inRange(code_point, 0x0000, 0x007F)) {
return output_byte_stream.emit(code_point);
}
if (!hzgb2312) {
code_point_pointer.offset(-1);
hzgb2312 = true;
return output_byte_stream.emit(0x7E, 0x7B);
}
var pointer = indexPointerFor(code_point, indexes['gbk']);
if (pointer === null) {
return encoderError(code_point);
}
var lead = div(pointer, 190) + 1;
var trail = pointer % 190 - 0x3F;
if (!inRange(lead, 0x21, 0x7E) || !inRange(trail, 0x21, 0x7E)) {
return encoderError(code_point);
}
return output_byte_stream.emit(lead, trail);
};
}
name_to_encoding['hz-gb-2312'].getEncoder = function(options) {
return new HZGB2312Encoder(options);
};
name_to_encoding['hz-gb-2312'].getDecoder = function(options) {
return new HZGB2312Decoder(options);
};
//
// 10. Legacy multi-byte Chinese (traditional) encodings
//
// 10.1 big5
/**
* @constructor
* @param {{fatal: boolean}} options
*/
function Big5Decoder(options) {
var fatal = options.fatal;
var /** @type {number} */ big5_lead = 0x00,
/** @type {?number} */ big5_pending = null;
/**
* @param {ByteInputStream} byte_pointer The byte steram to decode.
* @return {?number} The next code point decoded, or null if not enough
* data exists in the input stream to decode a complete code point.
*/
this.decode = function(byte_pointer) {
// NOTE: Hack to support emitting two code points
if (big5_pending !== null) {
var pending = big5_pending;
big5_pending = null;
return pending;
}
var bite = byte_pointer.get();
if (bite === EOF_byte && big5_lead === 0x00) {
return EOF_code_point;
}
if (bite === EOF_byte && big5_lead !== 0x00) {
big5_lead = 0x00;
return decoderError(fatal);
}
byte_pointer.offset(1);
if (big5_lead !== 0x00) {
var lead = big5_lead;
var pointer = null;
big5_lead = 0x00;
var offset = bite < 0x7F ? 0x40 : 0x62;
if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0xA1, 0xFE)) {
pointer = (lead - 0x81) * 157 + (bite - offset);
}
if (pointer === 1133) {
big5_pending = 0x0304;
return 0x00CA;
}
if (pointer === 1135) {
big5_pending = 0x030C;
return 0x00CA;
}
if (pointer === 1164) {
big5_pending = 0x0304;
return 0x00EA;
}
if (pointer === 1166) {
big5_pending = 0x030C;
return 0x00EA;
}
var code_point = (pointer === null) ? null :
indexCodePointFor(pointer, indexes['big5']);
if (pointer === null) {
byte_pointer.offset(-1);
}
if (code_point === null) {
return decoderError(fatal);
}
return code_point;
}
if (inRange(bite, 0x00, 0x7F)) {
return bite;
}
if (inRange(bite, 0x81, 0xFE)) {
big5_lead = bite;
return null;
}
return decoderError(fatal);
};
}
/**
* @constructor
* @param {{fatal: boolean}} options
*/
function Big5Encoder(options) {
var fatal = options.fatal;
/**
* @param {ByteOutputStream} output_byte_stream Output byte stream.
* @param {CodePointInputStream} code_point_pointer Input stream.
* @return {number} The last byte emitted.
*/
this.encode = function(output_byte_stream, code_point_pointer) {
var code_point = code_point_pointer.get();
if (code_point === EOF_code_point) {
return EOF_byte;
}
code_point_pointer.offset(1);
if (inRange(code_point, 0x0000, 0x007F)) {
return output_byte_stream.emit(code_point);
}
var pointer = indexPointerFor(code_point, indexes['big5']);
if (pointer === null) {
return encoderError(code_point);
}
var lead = div(pointer, 157) + 0x81;
//if (lead < 0xA1) {
// return encoderError(code_point);
//}
var trail = pointer % 157;
var offset = trail < 0x3F ? 0x40 : 0x62;
return output_byte_stream.emit(lead, trail + offset);
};
}
name_to_encoding['big5'].getEncoder = function(options) {
return new Big5Encoder(options);
};
name_to_encoding['big5'].getDecoder = function(options) {
return new Big5Decoder(options);
};
//
// 11. Legacy multi-byte Japanese encodings
//
// 11.1 euc.jp
/**
* @constructor
* @param {{fatal: boolean}} options
*/
function EUCJPDecoder(options) {
var fatal = options.fatal;
var /** @type {number} */ eucjp_first = 0x00,
/** @type {number} */ eucjp_second = 0x00;
/**
* @param {ByteInputStream} byte_pointer The byte stream to decode.
* @return {?number} The next code point decoded, or null if not enough
* data exists in the input stream to decode a complete code point.
*/
this.decode = function(byte_pointer) {
var bite = byte_pointer.get();
if (bite === EOF_byte) {
if (eucjp_first === 0x00 && eucjp_second === 0x00) {
return EOF_code_point;
}
eucjp_first = 0x00;
eucjp_second = 0x00;
return decoderError(fatal);
}
byte_pointer.offset(1);
var lead, code_point;
if (eucjp_second !== 0x00) {
lead = eucjp_second;
eucjp_second = 0x00;
code_point = null;
if (inRange(lead, 0xA1, 0xFE) && inRange(bite, 0xA1, 0xFE)) {
code_point = indexCodePointFor((lead - 0xA1) * 94 + bite - 0xA1,
indexes['jis0212']);
}
if (!inRange(bite, 0xA1, 0xFE)) {
byte_pointer.offset(-1);
}
if (code_point === null) {
return decoderError(fatal);
}
return code_point;
}
if (eucjp_first === 0x8E && inRange(bite, 0xA1, 0xDF)) {
eucjp_first = 0x00;
return 0xFF61 + bite - 0xA1;
}
if (eucjp_first === 0x8F && inRange(bite, 0xA1, 0xFE)) {
eucjp_first = 0x00;
eucjp_second = bite;
return null;
}
if (eucjp_first !== 0x00) {
lead = eucjp_first;
eucjp_first = 0x00;
code_point = null;
if (inRange(lead, 0xA1, 0xFE) && inRange(bite, 0xA1, 0xFE)) {
code_point = indexCodePointFor((lead - 0xA1) * 94 + bite - 0xA1,
indexes['jis0208']);
}
if (!inRange(bite, 0xA1, 0xFE)) {
byte_pointer.offset(-1);
}
if (code_point === null) {
return decoderError(fatal);
}
return code_point;
}
if (inRange(bite, 0x00, 0x7F)) {
return bite;
}
if (bite === 0x8E || bite === 0x8F || (inRange(bite, 0xA1, 0xFE))) {
eucjp_first = bite;
return null;
}
return decoderError(fatal);
};
}
/**
* @constructor
* @param {{fatal: boolean}} options
*/
function EUCJPEncoder(options) {
var fatal = options.fatal;
/**
* @param {ByteOutputStream} output_byte_stream Output byte stream.
* @param {CodePointInputStream} code_point_pointer Input stream.
* @return {number} The last byte emitted.
*/
this.encode = function(output_byte_stream, code_point_pointer) {
var code_point = code_point_pointer.get();
if (code_point === EOF_code_point) {
return EOF_byte;
}
code_point_pointer.offset(1);
if (inRange(code_point, 0x0000, 0x007F)) {
return output_byte_stream.emit(code_point);
}
if (code_point === 0x00A5) {
return output_byte_stream.emit(0x5C);
}
if (code_point === 0x203E) {
return output_byte_stream.emit(0x7E);
}
if (inRange(code_point, 0xFF61, 0xFF9F)) {
return output_byte_stream.emit(0x8E, code_point - 0xFF61 + 0xA1);
}
var pointer = indexPointerFor(code_point, indexes['jis0208']);
if (pointer === null) {
return encoderError(code_point);
}
var lead = div(pointer, 94) + 0xA1;
var trail = pointer % 94 + 0xA1;
return output_byte_stream.emit(lead, trail);
};
}
name_to_encoding['euc-jp'].getEncoder = function(options) {
return new EUCJPEncoder(options);
};
name_to_encoding['euc-jp'].getDecoder = function(options) {
return new EUCJPDecoder(options);
};
// 11.2 iso-2022-jp
/**
* @constructor
* @param {{fatal: boolean}} options
*/
function ISO2022JPDecoder(options) {
var fatal = options.fatal;
/** @enum */
var state = {
ASCII: 0,
escape_start: 1,
escape_middle: 2,
escape_final: 3,
lead: 4,
trail: 5,
Katakana: 6
};
var /** @type {number} */ iso2022jp_state = state.ASCII,
/** @type {boolean} */ iso2022jp_jis0212 = false,
/** @type {number} */ iso2022jp_lead = 0x00;
/**
* @param {ByteInputStream} byte_pointer The byte stream to decode.
* @return {?number} The next code point decoded, or null if not enough
* data exists in the input stream to decode a complete code point.
*/
this.decode = function(byte_pointer) {
var bite = byte_pointer.get();
if (bite !== EOF_byte) {
byte_pointer.offset(1);
}
switch (iso2022jp_state) {
default:
case state.ASCII:
if (bite === 0x1B) {
iso2022jp_state = state.escape_start;
return null;
}
if (inRange(bite, 0x00, 0x7F)) {
return bite;
}
if (bite === EOF_byte) {
return EOF_code_point;
}
return decoderError(fatal);
case state.escape_start:
if (bite === 0x24 || bite === 0x28) {
iso2022jp_lead = bite;
iso2022jp_state = state.escape_middle;
return null;
}
if (bite !== EOF_byte) {
byte_pointer.offset(-1);
}
iso2022jp_state = state.ASCII;
return decoderError(fatal);
case state.escape_middle:
var lead = iso2022jp_lead;
iso2022jp_lead = 0x00;
if (lead === 0x24 && (bite === 0x40 || bite === 0x42)) {
iso2022jp_jis0212 = false;
iso2022jp_state = state.lead;
return null;
}
if (lead === 0x24 && bite === 0x28) {
iso2022jp_state = state.escape_final;
return null;
}
if (lead === 0x28 && (bite === 0x42 || bite === 0x4A)) {
iso2022jp_state = state.ASCII;
return null;
}
if (lead === 0x28 && bite === 0x49) {
iso2022jp_state = state.Katakana;
return null;
}
if (bite === EOF_byte) {
byte_pointer.offset(-1);
} else {
byte_pointer.offset(-2);
}
iso2022jp_state = state.ASCII;
return decoderError(fatal);
case state.escape_final:
if (bite === 0x44) {
iso2022jp_jis0212 = true;
iso2022jp_state = state.lead;
return null;
}
if (bite === EOF_byte) {
byte_pointer.offset(-2);
} else {
byte_pointer.offset(-3);
}
iso2022jp_state = state.ASCII;
return decoderError(fatal);
case state.lead:
if (bite === 0x0A) {
iso2022jp_state = state.ASCII;
return decoderError(fatal, 0x000A);
}
if (bite === 0x1B) {
iso2022jp_state = state.escape_start;
return null;
}
if (bite === EOF_byte) {
return EOF_code_point;
}
iso2022jp_lead = bite;
iso2022jp_state = state.trail;
return null;
case state.trail:
iso2022jp_state = state.lead;
if (bite === EOF_byte) {
return decoderError(fatal);
}
var code_point = null;
var pointer = (iso2022jp_lead - 0x21) * 94 + bite - 0x21;
if (inRange(iso2022jp_lead, 0x21, 0x7E) &&
inRange(bite, 0x21, 0x7E)) {
code_point = (iso2022jp_jis0212 === false) ?
indexCodePointFor(pointer, indexes['jis0208']) :
indexCodePointFor(pointer, indexes['jis0212']);
}
if (code_point === null) {
return decoderError(fatal);
}
return code_point;
case state.Katakana:
if (bite === 0x1B) {
iso2022jp_state = state.escape_start;
return null;
}
if (inRange(bite, 0x21, 0x5F)) {
return 0xFF61 + bite - 0x21;
}
if (bite === EOF_byte) {
return EOF_code_point;
}
return decoderError(fatal);
}
};
}
/**
* @constructor
* @param {{fatal: boolean}} options
*/
function ISO2022JPEncoder(options) {
var fatal = options.fatal;
/** @enum */
var state = {
ASCII: 0,
lead: 1,
Katakana: 2
};
var /** @type {number} */ iso2022jp_state = state.ASCII;
/**
* @param {ByteOutputStream} output_byte_stream Output byte stream.
* @param {CodePointInputStream} code_point_pointer Input stream.
* @return {number} The last byte emitted.
*/
this.encode = function(output_byte_stream, code_point_pointer) {
var code_point = code_point_pointer.get();
if (code_point === EOF_code_point) {
return EOF_byte;
}
code_point_pointer.offset(1);
if ((inRange(code_point, 0x0000, 0x007F) ||
code_point === 0x00A5 || code_point === 0x203E) &&
iso2022jp_state !== state.ASCII) {
code_point_pointer.offset(-1);
iso2022jp_state = state.ASCII;
return output_byte_stream.emit(0x1B, 0x28, 0x42);
}
if (inRange(code_point, 0x0000, 0x007F)) {
return output_byte_stream.emit(code_point);
}
if (code_point === 0x00A5) {
return output_byte_stream.emit(0x5C);
}
if (code_point === 0x203E) {
return output_byte_stream.emit(0x7E);
}
if (inRange(code_point, 0xFF61, 0xFF9F) &&
iso2022jp_state !== state.Katakana) {
code_point_pointer.offset(-1);
iso2022jp_state = state.Katakana;
return output_byte_stream.emit(0x1B, 0x28, 0x49);
}
if (inRange(code_point, 0xFF61, 0xFF9F)) {
return output_byte_stream.emit(code_point - 0xFF61 - 0x21);
}
if (iso2022jp_state !== state.lead) {
code_point_pointer.offset(-1);
iso2022jp_state = state.lead;
return output_byte_stream.emit(0x1B, 0x24, 0x42);
}
var pointer = indexPointerFor(code_point, indexes['jis0208']);
if (pointer === null) {
return encoderError(code_point);
}
var lead = div(pointer, 94) + 0x21;
var trail = pointer % 94 + 0x21;
return output_byte_stream.emit(lead, trail);
};
}
name_to_encoding['iso-2022-jp'].getEncoder = function(options) {
return new ISO2022JPEncoder(options);
};
name_to_encoding['iso-2022-jp'].getDecoder = function(options) {
return new ISO2022JPDecoder(options);
};
// 11.3 shift_jis
/**
* @constructor
* @param {{fatal: boolean}} options
*/
function ShiftJISDecoder(options) {
var fatal = options.fatal;
var /** @type {number} */ shiftjis_lead = 0x00;
/**
* @param {ByteInputStream} byte_pointer The byte stream to decode.
* @return {?number} The next code point decoded, or null if not enough
* data exists in the input stream to decode a complete code point.
*/
this.decode = function(byte_pointer) {
var bite = byte_pointer.get();
if (bite === EOF_byte && shiftjis_lead === 0x00) {
return EOF_code_point;
}
if (bite === EOF_byte && shiftjis_lead !== 0x00) {
shiftjis_lead = 0x00;
return decoderError(fatal);
}
byte_pointer.offset(1);
if (shiftjis_lead !== 0x00) {
var lead = shiftjis_lead;
shiftjis_lead = 0x00;
if (inRange(bite, 0x40, 0x7E) || inRange(bite, 0x80, 0xFC)) {
var offset = (bite < 0x7F) ? 0x40 : 0x41;
var lead_offset = (lead < 0xA0) ? 0x81 : 0xC1;
var code_point = indexCodePointFor((lead - lead_offset) * 188 +
bite - offset, indexes['jis0208']);
if (code_point === null) {
return decoderError(fatal);
}
return code_point;
}
byte_pointer.offset(-1);
return decoderError(fatal);
}
if (inRange(bite, 0x00, 0x80)) {
return bite;
}
if (inRange(bite, 0xA1, 0xDF)) {
return 0xFF61 + bite - 0xA1;
}
if (inRange(bite, 0x81, 0x9F) || inRange(bite, 0xE0, 0xFC)) {
shiftjis_lead = bite;
return null;
}
return decoderError(fatal);
};
}
/**
* @constructor
* @param {{fatal: boolean}} options
*/
function ShiftJISEncoder(options) {
var fatal = options.fatal;
/**
* @param {ByteOutputStream} output_byte_stream Output byte stream.
* @param {CodePointInputStream} code_point_pointer Input stream.
* @return {number} The last byte emitted.
*/
this.encode = function(output_byte_stream, code_point_pointer) {
var code_point = code_point_pointer.get();
if (code_point === EOF_code_point) {
return EOF_byte;
}
code_point_pointer.offset(1);
if (inRange(code_point, 0x0000, 0x0080)) {
return output_byte_stream.emit(code_point);
}
if (code_point === 0x00A5) {
return output_byte_stream.emit(0x5C);
}
if (code_point === 0x203E) {
return output_byte_stream.emit(0x7E);
}
if (inRange(code_point, 0xFF61, 0xFF9F)) {
return output_byte_stream.emit(code_point - 0xFF61 + 0xA1);
}
var pointer = indexPointerFor(code_point, indexes['jis0208']);
if (pointer === null) {
return encoderError(code_point);
}
var lead = div(pointer, 188);
var lead_offset = lead < 0x1F ? 0x81 : 0xC1;
var trail = pointer % 188;
var offset = trail < 0x3F ? 0x40 : 0x41;
return output_byte_stream.emit(lead + lead_offset, trail + offset);
};
}
name_to_encoding['shift_jis'].getEncoder = function(options) {
return new ShiftJISEncoder(options);
};
name_to_encoding['shift_jis'].getDecoder = function(options) {
return new ShiftJISDecoder(options);
};
//
// 12. Legacy multi-byte Korean encodings
//
// 12.1 euc-kr
/**
* @constructor
* @param {{fatal: boolean}} options
*/
function EUCKRDecoder(options) {
var fatal = options.fatal;
var /** @type {number} */ euckr_lead = 0x00;
/**
* @param {ByteInputStream} byte_pointer The byte stream to decode.
* @return {?number} The next code point decoded, or null if not enough
* data exists in the input stream to decode a complete code point.
*/
this.decode = function(byte_pointer) {
var bite = byte_pointer.get();
if (bite === EOF_byte && euckr_lead === 0) {
return EOF_code_point;
}
if (bite === EOF_byte && euckr_lead !== 0) {
euckr_lead = 0x00;
return decoderError(fatal);
}
byte_pointer.offset(1);
if (euckr_lead !== 0x00) {
var lead = euckr_lead;
var pointer = null;
euckr_lead = 0x00;
if (inRange(lead, 0x81, 0xC6)) {
var temp = (26 + 26 + 126) * (lead - 0x81);
if (inRange(bite, 0x41, 0x5A)) {
pointer = temp + bite - 0x41;
} else if (inRange(bite, 0x61, 0x7A)) {
pointer = temp + 26 + bite - 0x61;
} else if (inRange(bite, 0x81, 0xFE)) {
pointer = temp + 26 + 26 + bite - 0x81;
}
}
if (inRange(lead, 0xC7, 0xFD) && inRange(bite, 0xA1, 0xFE)) {
pointer = (26 + 26 + 126) * (0xC7 - 0x81) + (lead - 0xC7) * 94 +
(bite - 0xA1);
}
var code_point = (pointer === null) ? null :
indexCodePointFor(pointer, indexes['euc-kr']);
if (pointer === null) {
byte_pointer.offset(-1);
}
if (code_point === null) {
return decoderError(fatal);
}
return code_point;
}
if (inRange(bite, 0x00, 0x7F)) {
return bite;
}
if (inRange(bite, 0x81, 0xFD)) {
euckr_lead = bite;
return null;
}
return decoderError(fatal);
};
}
/**
* @constructor
* @param {{fatal: boolean}} options
*/
function EUCKREncoder(options) {
var fatal = options.fatal;
/**
* @param {ByteOutputStream} output_byte_stream Output byte stream.
* @param {CodePointInputStream} code_point_pointer Input stream.
* @return {number} The last byte emitted.
*/
this.encode = function(output_byte_stream, code_point_pointer) {
var code_point = code_point_pointer.get();
if (code_point === EOF_code_point) {
return EOF_byte;
}
code_point_pointer.offset(1);
if (inRange(code_point, 0x0000, 0x007F)) {
return output_byte_stream.emit(code_point);
}
var pointer = indexPointerFor(code_point, indexes['euc-kr']);
if (pointer === null) {
return encoderError(code_point);
}
var lead, trail;
if (pointer < ((26 + 26 + 126) * (0xC7 - 0x81))) {
lead = div(pointer, (26 + 26 + 126)) + 0x81;
trail = pointer % (26 + 26 + 126);
var offset = trail < 26 ? 0x41 : trail < 26 + 26 ? 0x47 : 0x4D;
return output_byte_stream.emit(lead, trail + offset);
}
pointer = pointer - (26 + 26 + 126) * (0xC7 - 0x81);
lead = div(pointer, 94) + 0xC7;
trail = pointer % 94 + 0xA1;
return output_byte_stream.emit(lead, trail);
};
}
name_to_encoding['euc-kr'].getEncoder = function(options) {
return new EUCKREncoder(options);
};
name_to_encoding['euc-kr'].getDecoder = function(options) {
return new EUCKRDecoder(options);
};
//
// 13. Legacy utf-16 encodings
//
// 13.1 utf-16
/**
* @constructor
* @param {boolean} utf16_be True if big-endian, false if little-endian.
* @param {{fatal: boolean}} options
*/
function UTF16Decoder(utf16_be, options) {
var fatal = options.fatal;
var /** @type {?number} */ utf16_lead_byte = null,
/** @type {?number} */ utf16_lead_surrogate = null;
/**
* @param {ByteInputStream} byte_pointer The byte stream to decode.
* @return {?number} The next code point decoded, or null if not enough
* data exists in the input stream to decode a complete code point.
*/
this.decode = function(byte_pointer) {
var bite = byte_pointer.get();
if (bite === EOF_byte && utf16_lead_byte === null &&
utf16_lead_surrogate === null) {
return EOF_code_point;
}
if (bite === EOF_byte && (utf16_lead_byte !== null ||
utf16_lead_surrogate !== null)) {
return decoderError(fatal);
}
byte_pointer.offset(1);
if (utf16_lead_byte === null) {
utf16_lead_byte = bite;
return null;
}
var code_point;
if (utf16_be) {
code_point = (utf16_lead_byte << 8) + bite;
} else {
code_point = (bite << 8) + utf16_lead_byte;
}
utf16_lead_byte = null;
if (utf16_lead_surrogate !== null) {
var lead_surrogate = utf16_lead_surrogate;
utf16_lead_surrogate = null;
if (inRange(code_point, 0xDC00, 0xDFFF)) {
return 0x10000 + (lead_surrogate - 0xD800) * 0x400 +
(code_point - 0xDC00);
}
byte_pointer.offset(-2);
return decoderError(fatal);
}
if (inRange(code_point, 0xD800, 0xDBFF)) {
utf16_lead_surrogate = code_point;
return null;
}
if (inRange(code_point, 0xDC00, 0xDFFF)) {
return decoderError(fatal);
}
return code_point;
};
}
/**
* @constructor
* @param {boolean} utf16_be True if big-endian, false if little-endian.
* @param {{fatal: boolean}} options
*/
function UTF16Encoder(utf16_be, options) {
var fatal = options.fatal;
/**
* @param {ByteOutputStream} output_byte_stream Output byte stream.
* @param {CodePointInputStream} code_point_pointer Input stream.
* @return {number} The last byte emitted.
*/
this.encode = function(output_byte_stream, code_point_pointer) {
function convert_to_bytes(code_unit) {
var byte1 = code_unit >> 8;
var byte2 = code_unit & 0x00FF;
if (utf16_be) {
return output_byte_stream.emit(byte1, byte2);
}
return output_byte_stream.emit(byte2, byte1);
}
var code_point = code_point_pointer.get();
if (code_point === EOF_code_point) {
return EOF_byte;
}
code_point_pointer.offset(1);
if (inRange(code_point, 0xD800, 0xDFFF)) {
encoderError(code_point);
}
if (code_point <= 0xFFFF) {
return convert_to_bytes(code_point);
}
var lead = div((code_point - 0x10000), 0x400) + 0xD800;
var trail = ((code_point - 0x10000) % 0x400) + 0xDC00;
convert_to_bytes(lead);
return convert_to_bytes(trail);
};
}
name_to_encoding['utf-16le'].getEncoder = function(options) {
return new UTF16Encoder(false, options);
};
name_to_encoding['utf-16le'].getDecoder = function(options) {
return new UTF16Decoder(false, options);
};
// 13.2 utf-16be
name_to_encoding['utf-16be'].getEncoder = function(options) {
return new UTF16Encoder(true, options);
};
name_to_encoding['utf-16be'].getDecoder = function(options) {
return new UTF16Decoder(true, options);
};
// NOTE: currently unused
/**
* @param {string} label The encoding label.
* @param {ByteInputStream} input_stream The byte stream to test.
*/
function detectEncoding(label, input_stream) {
if (input_stream.match([0xFF, 0xFE])) {
input_stream.offset(2);
return 'utf-16le';
}
if (input_stream.match([0xFE, 0xFF])) {
input_stream.offset(2);
return 'utf-16be';
}
if (input_stream.match([0xEF, 0xBB, 0xBF])) {
input_stream.offset(3);
return 'utf-8';
}
return label;
}
//
// Implementation of Text Encoding Web API
//
/** @const */ var DEFAULT_ENCODING = 'utf-8';
/**
* @constructor
* @param {string=} opt_encoding The label of the encoding;
* defaults to 'utf-8'.
* @param {{fatal: boolean}=} options
*/
function TextEncoder(opt_encoding, options) {
if (!(this instanceof TextEncoder)) {
return new TextEncoder(opt_encoding, options);
}
opt_encoding = opt_encoding ? String(opt_encoding) : DEFAULT_ENCODING;
options = Object(options);
/** @private */
this._encoding = getEncoding(opt_encoding);
if (this._encoding === null || (this._encoding.name !== 'utf-8' &&
this._encoding.name !== 'utf-16le' &&
this._encoding.name !== 'utf-16be'))
throw new TypeError('Unknown encoding: ' + opt_encoding);
/** @private @type {boolean} */
this._streaming = false;
/** @private */
this._encoder = null;
/** @private @type {{fatal: boolean}=} */
this._options = { fatal: Boolean(options.fatal) };
if (Object.defineProperty) {
Object.defineProperty(
this, 'encoding',
{ get: function() { return this._encoding.name; } });
} else {
this.encoding = this._encoding.name;
}
return this;
}
TextEncoder.prototype = {
/**
* @param {string=} opt_string The string to encode.
* @param {{stream: boolean}=} options
*/
encode: function encode(opt_string, options) {
opt_string = opt_string ? String(opt_string) : '';
options = Object(options);
// TODO: any options?
if (!this._streaming) {
this._encoder = this._encoding.getEncoder(this._options);
}
this._streaming = Boolean(options.stream);
var bytes = [];
var output_stream = new ByteOutputStream(bytes);
var input_stream = new CodePointInputStream(opt_string);
while (input_stream.get() !== EOF_code_point) {
this._encoder.encode(output_stream, input_stream);
}
if (!this._streaming) {
var last_byte;
do {
last_byte = this._encoder.encode(output_stream, input_stream);
} while (last_byte !== EOF_byte);
this._encoder = null;
}
return new Buffer(bytes);
}
};
/**
* @constructor
* @param {string=} opt_encoding The label of the encoding;
* defaults to 'utf-8'.
* @param {{fatal: boolean}=} options
*/
function TextDecoder(opt_encoding, options) {
if (!(this instanceof TextDecoder)) {
return new TextDecoder(opt_encoding, options);
}
opt_encoding = opt_encoding ? String(opt_encoding) : DEFAULT_ENCODING;
options = Object(options);
/** @private */
this._encoding = getEncoding(opt_encoding);
if (this._encoding === null)
throw new TypeError('Unknown encoding: ' + opt_encoding);
/** @private @type {boolean} */
this._streaming = false;
/** @private */
this._decoder = null;
/** @private @type {{fatal: boolean}=} */
this._options = { fatal: Boolean(options.fatal) };
if (Object.defineProperty) {
Object.defineProperty(
this, 'encoding',
{ get: function() { return this._encoding.name; } });
} else {
this.encoding = this._encoding.name;
}
return this;
}
// TODO: Issue if input byte stream is offset by decoder
// TODO: BOM detection will not work if stream header spans multiple calls
// (last N bytes of previous stream may need to be retained?)
TextDecoder.prototype = {
/**
* @param {Buffer=} buf The buffer of bytes to decode.
* @param {{stream: boolean}=} options
*/
decode: function decode(buf, options) {
options = Object(options);
if (!this._streaming) {
this._decoder = this._encoding.getDecoder(this._options);
this._BOMseen = false;
}
this._streaming = Boolean(options.stream);
var input_stream = new ByteInputStream(buf);
var output_stream = new CodePointOutputStream(), code_point;
while (input_stream.get() !== EOF_byte) {
code_point = this._decoder.decode(input_stream);
if (code_point !== null && code_point !== EOF_code_point) {
output_stream.emit(code_point);
}
}
if (!this._streaming) {
do {
code_point = this._decoder.decode(input_stream);
if (code_point !== null && code_point !== EOF_code_point) {
output_stream.emit(code_point);
}
} while (code_point !== EOF_code_point &&
input_stream.get() != EOF_byte);
this._decoder = null;
}
var result = output_stream.string();
if (!this._BOMseen && result.length) {
this._BOMseen = true;
if (UTFs.indexOf(this.encoding) !== -1 &&
result.charCodeAt(0) === 0xFEFF) {
result = result.substring(1);
}
}
return result;
}
};
var UTFs = ['utf-8', 'utf-16le', 'utf-16be'];
exports.TextEncoder = TextEncoder;
exports.TextDecoder = TextDecoder;
exports.encodingExists = getEncoding;

2128
nodered/rootfs/data/node_modules/imap/lib/Connection.js generated vendored Normal file
View File

@@ -0,0 +1,2128 @@
var tls = require('tls'),
Socket = require('net').Socket,
EventEmitter = require('events').EventEmitter,
inherits = require('util').inherits,
inspect = require('util').inspect,
isDate = require('util').isDate,
utf7 = require('utf7').imap;
var Parser = require('./Parser').Parser,
parseExpr = require('./Parser').parseExpr,
parseHeader = require('./Parser').parseHeader;
var MAX_INT = 9007199254740992,
KEEPALIVE_INTERVAL = 10000,
MAX_IDLE_WAIT = 300000, // 5 minutes
MONTHS = ['Jan', 'Feb', 'Mar',
'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep',
'Oct', 'Nov', 'Dec'],
FETCH_ATTR_MAP = {
'RFC822.SIZE': 'size',
'BODY': 'struct',
'BODYSTRUCTURE': 'struct',
'ENVELOPE': 'envelope',
'INTERNALDATE': 'date'
},
SPECIAL_USE_ATTRIBUTES = [
'\\All',
'\\Archive',
'\\Drafts',
'\\Flagged',
'\\Important',
'\\Junk',
'\\Sent',
'\\Trash'
],
CRLF = '\r\n',
RE_CMD = /^([^ ]+)(?: |$)/,
RE_UIDCMD_HASRESULTS = /^UID (?:FETCH|SEARCH|SORT)/,
RE_IDLENOOPRES = /^(IDLE|NOOP) /,
RE_OPENBOX = /^EXAMINE|SELECT$/,
RE_BODYPART = /^BODY\[/,
RE_INVALID_KW_CHARS = /[\(\)\{\\\"\]\%\*\x00-\x20\x7F]/,
RE_NUM_RANGE = /^(?:[\d]+|\*):(?:[\d]+|\*)$/,
RE_BACKSLASH = /\\/g,
RE_DBLQUOTE = /"/g,
RE_ESCAPE = /\\\\/g,
RE_INTEGER = /^\d+$/;
function Connection(config) {
if (!(this instanceof Connection))
return new Connection(config);
EventEmitter.call(this);
config || (config = {});
this._config = {
localAddress: config.localAddress,
socket: config.socket,
socketTimeout: config.socketTimeout || 0,
host: config.host || 'localhost',
port: config.port || 143,
tls: config.tls,
tlsOptions: config.tlsOptions,
autotls: config.autotls,
user: config.user,
password: config.password,
xoauth: config.xoauth,
xoauth2: config.xoauth2,
connTimeout: config.connTimeout || 10000,
authTimeout: config.authTimeout || 5000,
keepalive: (config.keepalive === undefined || config.keepalive === null
? true
: config.keepalive)
};
this._sock = config.socket || undefined;
this._tagcount = 0;
this._tmrConn = undefined;
this._tmrKeepalive = undefined;
this._tmrAuth = undefined;
this._queue = [];
this._box = undefined;
this._idle = { started: undefined, enabled: false };
this._parser = undefined;
this._curReq = undefined;
this.delimiter = undefined;
this.namespaces = undefined;
this.state = 'disconnected';
this.debug = config.debug;
}
inherits(Connection, EventEmitter);
Connection.prototype.connect = function() {
var config = this._config,
self = this,
socket,
parser,
tlsOptions;
socket = config.socket || new Socket();
socket.setKeepAlive(true);
this._sock = undefined;
this._tagcount = 0;
this._tmrConn = undefined;
this._tmrKeepalive = undefined;
this._tmrAuth = undefined;
this._queue = [];
this._box = undefined;
this._idle = { started: undefined, enabled: false };
this._parser = undefined;
this._curReq = undefined;
this.delimiter = undefined;
this.namespaces = undefined;
this.state = 'disconnected';
if (config.tls) {
tlsOptions = {};
tlsOptions.host = config.host;
// Host name may be overridden the tlsOptions
for (var k in config.tlsOptions)
tlsOptions[k] = config.tlsOptions[k];
tlsOptions.socket = socket;
}
if (config.tls)
this._sock = tls.connect(tlsOptions, onconnect);
else {
socket.once('connect', onconnect);
this._sock = socket;
}
function onconnect() {
clearTimeout(self._tmrConn);
self.state = 'connected';
self.debug && self.debug('[connection] Connected to host');
self._tmrAuth = setTimeout(function() {
var err = new Error('Timed out while authenticating with server');
err.source = 'timeout-auth';
self.emit('error', err);
socket.destroy();
}, config.authTimeout);
}
this._onError = function(err) {
clearTimeout(self._tmrConn);
clearTimeout(self._tmrAuth);
self.debug && self.debug('[connection] Error: ' + err);
err.source = 'socket';
self.emit('error', err);
};
this._sock.on('error', this._onError);
this._onSocketTimeout = function() {
clearTimeout(self._tmrConn);
clearTimeout(self._tmrAuth);
clearTimeout(self._tmrKeepalive);
self.state = 'disconnected';
self.debug && self.debug('[connection] Socket timeout');
var err = new Error('Socket timed out while talking to server');
err.source = 'socket-timeout';
self.emit('error', err);
socket.destroy();
};
this._sock.on('timeout', this._onSocketTimeout);
socket.setTimeout(config.socketTimeout);
socket.once('close', function(had_err) {
clearTimeout(self._tmrConn);
clearTimeout(self._tmrAuth);
clearTimeout(self._tmrKeepalive);
self.state = 'disconnected';
self.debug && self.debug('[connection] Closed');
self.emit('close', had_err);
});
socket.once('end', function() {
clearTimeout(self._tmrConn);
clearTimeout(self._tmrAuth);
clearTimeout(self._tmrKeepalive);
self.state = 'disconnected';
self.debug && self.debug('[connection] Ended');
self.emit('end');
});
this._parser = parser = new Parser(this._sock, this.debug);
parser.on('untagged', function(info) {
self._resUntagged(info);
});
parser.on('tagged', function(info) {
self._resTagged(info);
});
parser.on('body', function(stream, info) {
var msg = self._curReq.fetchCache[info.seqno], toget;
if (msg === undefined) {
msg = self._curReq.fetchCache[info.seqno] = {
msgEmitter: new EventEmitter(),
toget: self._curReq.fetching.slice(0),
attrs: {},
ended: false
};
self._curReq.bodyEmitter.emit('message', msg.msgEmitter, info.seqno);
}
toget = msg.toget;
// here we compare the parsed version of the expression inside BODY[]
// because 'HEADER.FIELDS (TO FROM)' really is equivalent to
// 'HEADER.FIELDS ("TO" "FROM")' and some servers will actually send the
// quoted form even if the client did not use quotes
var thisbody = parseExpr(info.which);
for (var i = 0, len = toget.length; i < len; ++i) {
if (_deepEqual(thisbody, toget[i])) {
toget.splice(i, 1);
msg.msgEmitter.emit('body', stream, info);
return;
}
}
stream.resume(); // a body we didn't ask for?
});
parser.on('continue', function(info) {
var type = self._curReq.type;
if (type === 'IDLE') {
if (self._queue.length
&& self._idle.started === 0
&& self._curReq
&& self._curReq.type === 'IDLE'
&& self._sock
&& self._sock.writable
&& !self._idle.enabled) {
self.debug && self.debug('=> DONE');
self._sock.write('DONE' + CRLF);
return;
}
// now idling
self._idle.started = Date.now();
} else if (/^AUTHENTICATE XOAUTH/.test(self._curReq.fullcmd)) {
self._curReq.oauthError = new Buffer(info.text, 'base64').toString('utf8');
self.debug && self.debug('=> ' + inspect(CRLF));
self._sock.write(CRLF);
} else if (type === 'APPEND') {
self._sockWriteAppendData(self._curReq.appendData);
} else if (self._curReq.lines && self._curReq.lines.length) {
var line = self._curReq.lines.shift() + '\r\n';
self.debug && self.debug('=> ' + inspect(line));
self._sock.write(line, 'binary');
}
});
parser.on('other', function(line) {
var m;
if (m = RE_IDLENOOPRES.exec(line)) {
// no longer idling
self._idle.enabled = false;
self._idle.started = undefined;
clearTimeout(self._tmrKeepalive);
self._curReq = undefined;
if (self._queue.length === 0
&& self._config.keepalive
&& self.state === 'authenticated'
&& !self._idle.enabled) {
self._idle.enabled = true;
if (m[1] === 'NOOP')
self._doKeepaliveTimer();
else
self._doKeepaliveTimer(true);
}
self._processQueue();
}
});
this._tmrConn = setTimeout(function() {
var err = new Error('Timed out while connecting to server');
err.source = 'timeout';
self.emit('error', err);
socket.destroy();
}, config.connTimeout);
socket.connect({
port: config.port,
host: config.host,
localAddress: config.localAddress
});
};
Connection.prototype.serverSupports = function(cap) {
return (this._caps && this._caps.indexOf(cap) > -1);
};
Connection.prototype.destroy = function() {
this._queue = [];
this._curReq = undefined;
this._sock && this._sock.end();
};
Connection.prototype.end = function() {
var self = this;
this._enqueue('LOGOUT', function() {
self._queue = [];
self._curReq = undefined;
self._sock.end();
});
};
Connection.prototype.append = function(data, options, cb) {
var literal = this.serverSupports('LITERAL+');
if (typeof options === 'function') {
cb = options;
options = undefined;
}
options = options || {};
if (!options.mailbox) {
if (!this._box)
throw new Error('No mailbox specified or currently selected');
else
options.mailbox = this._box.name;
}
var cmd = 'APPEND "' + escape(utf7.encode(''+options.mailbox)) + '"';
if (options.flags) {
if (!Array.isArray(options.flags))
options.flags = [options.flags];
if (options.flags.length > 0) {
for (var i = 0, len = options.flags.length; i < len; ++i) {
if (options.flags[i][0] !== '$' && options.flags[i][0] !== '\\')
options.flags[i] = '\\' + options.flags[i];
}
cmd += ' (' + options.flags.join(' ') + ')';
}
}
if (options.date) {
if (!isDate(options.date))
throw new Error('`date` is not a Date object');
cmd += ' "';
cmd += options.date.getDate();
cmd += '-';
cmd += MONTHS[options.date.getMonth()];
cmd += '-';
cmd += options.date.getFullYear();
cmd += ' ';
cmd += ('0' + options.date.getHours()).slice(-2);
cmd += ':';
cmd += ('0' + options.date.getMinutes()).slice(-2);
cmd += ':';
cmd += ('0' + options.date.getSeconds()).slice(-2);
cmd += ((options.date.getTimezoneOffset() > 0) ? ' -' : ' +' );
cmd += ('0' + (-options.date.getTimezoneOffset() / 60)).slice(-2);
cmd += ('0' + (-options.date.getTimezoneOffset() % 60)).slice(-2);
cmd += '"';
}
cmd += ' {';
cmd += (Buffer.isBuffer(data) ? data.length : Buffer.byteLength(data));
cmd += (literal ? '+' : '') + '}';
this._enqueue(cmd, cb);
if (literal)
this._queue[this._queue.length - 1].literalAppendData = data;
else
this._queue[this._queue.length - 1].appendData = data;
};
Connection.prototype.getSpecialUseBoxes = function(cb) {
this._enqueue('XLIST "" "*"', cb);
};
Connection.prototype.getBoxes = function(namespace, cb) {
if (typeof namespace === 'function') {
cb = namespace;
namespace = '';
}
namespace = escape(utf7.encode(''+namespace));
this._enqueue('LIST "' + namespace + '" "*"', cb);
};
Connection.prototype.id = function(identification, cb) {
if (!this.serverSupports('ID'))
throw new Error('Server does not support ID');
var cmd = 'ID';
if ((identification === null) || (Object.keys(identification).length === 0))
cmd += ' NIL';
else {
if (Object.keys(identification).length > 30)
throw new Error('Max allowed number of keys is 30');
var kv = [];
for (var k in identification) {
if (Buffer.byteLength(k) > 30)
throw new Error('Max allowed key length is 30');
if (Buffer.byteLength(identification[k]) > 1024)
throw new Error('Max allowed value length is 1024');
kv.push('"' + escape(k) + '"');
kv.push('"' + escape(identification[k]) + '"');
}
cmd += ' (' + kv.join(' ') + ')';
}
this._enqueue(cmd, cb);
};
Connection.prototype.openBox = function(name, readOnly, cb) {
if (this.state !== 'authenticated')
throw new Error('Not authenticated');
if (typeof readOnly === 'function') {
cb = readOnly;
readOnly = false;
}
name = ''+name;
var encname = escape(utf7.encode(name)),
cmd = (readOnly ? 'EXAMINE' : 'SELECT'),
self = this;
cmd += ' "' + encname + '"';
if (this.serverSupports('CONDSTORE'))
cmd += ' (CONDSTORE)';
this._enqueue(cmd, function(err) {
if (err) {
self._box = undefined;
cb(err);
} else {
self._box.name = name;
cb(err, self._box);
}
});
};
Connection.prototype.closeBox = function(shouldExpunge, cb) {
if (this._box === undefined)
throw new Error('No mailbox is currently selected');
var self = this;
if (typeof shouldExpunge === 'function') {
cb = shouldExpunge;
shouldExpunge = true;
}
if (shouldExpunge) {
this._enqueue('CLOSE', function(err) {
if (!err)
self._box = undefined;
cb(err);
});
} else {
if (this.serverSupports('UNSELECT')) {
// use UNSELECT if available, as it claims to be "cleaner" than the
// alternative "hack"
this._enqueue('UNSELECT', function(err) {
if (!err)
self._box = undefined;
cb(err);
});
} else {
// "HACK": close the box without expunging by attempting to SELECT a
// non-existent mailbox
var badbox = 'NODEJSIMAPCLOSINGBOX' + Date.now();
this._enqueue('SELECT "' + badbox + '"', function(err) {
self._box = undefined;
cb();
});
}
}
};
Connection.prototype.addBox = function(name, cb) {
this._enqueue('CREATE "' + escape(utf7.encode(''+name)) + '"', cb);
};
Connection.prototype.delBox = function(name, cb) {
this._enqueue('DELETE "' + escape(utf7.encode(''+name)) + '"', cb);
};
Connection.prototype.renameBox = function(oldname, newname, cb) {
var encoldname = escape(utf7.encode(''+oldname)),
encnewname = escape(utf7.encode(''+newname)),
self = this;
this._enqueue('RENAME "' + encoldname + '" "' + encnewname + '"',
function(err) {
if (err)
return cb(err);
if (self._box
&& self._box.name === oldname
&& oldname.toUpperCase() !== 'INBOX') {
self._box.name = newname;
cb(err, self._box);
} else
cb();
}
);
};
Connection.prototype.subscribeBox = function(name, cb) {
this._enqueue('SUBSCRIBE "' + escape(utf7.encode(''+name)) + '"', cb);
};
Connection.prototype.unsubscribeBox = function(name, cb) {
this._enqueue('UNSUBSCRIBE "' + escape(utf7.encode(''+name)) + '"', cb);
};
Connection.prototype.getSubscribedBoxes = function(namespace, cb) {
if (typeof namespace === 'function') {
cb = namespace;
namespace = '';
}
namespace = escape(utf7.encode(''+namespace));
this._enqueue('LSUB "' + namespace + '" "*"', cb);
};
Connection.prototype.status = function(boxName, cb) {
if (this._box && this._box.name === boxName)
throw new Error('Cannot call status on currently selected mailbox');
boxName = escape(utf7.encode(''+boxName));
var info = [ 'MESSAGES', 'RECENT', 'UNSEEN', 'UIDVALIDITY', 'UIDNEXT' ];
if (this.serverSupports('CONDSTORE'))
info.push('HIGHESTMODSEQ');
info = info.join(' ');
this._enqueue('STATUS "' + boxName + '" (' + info + ')', cb);
};
Connection.prototype.expunge = function(uids, cb) {
if (typeof uids === 'function') {
cb = uids;
uids = undefined;
}
if (uids !== undefined) {
if (!Array.isArray(uids))
uids = [uids];
validateUIDList(uids);
if (uids.length === 0)
throw new Error('Empty uid list');
uids = uids.join(',');
if (!this.serverSupports('UIDPLUS'))
throw new Error('Server does not support this feature (UIDPLUS)');
this._enqueue('UID EXPUNGE ' + uids, cb);
} else
this._enqueue('EXPUNGE', cb);
};
Connection.prototype.search = function(criteria, cb) {
this._search('UID ', criteria, cb);
};
Connection.prototype._search = function(which, criteria, cb) {
if (this._box === undefined)
throw new Error('No mailbox is currently selected');
else if (!Array.isArray(criteria))
throw new Error('Expected array for search criteria');
var cmd = which + 'SEARCH',
info = { hasUTF8: false /*output*/ },
query = buildSearchQuery(criteria, this._caps, info),
lines;
if (info.hasUTF8) {
cmd += ' CHARSET UTF-8';
lines = query.split(CRLF);
query = lines.shift();
}
cmd += query;
this._enqueue(cmd, cb);
if (info.hasUTF8) {
var req = this._queue[this._queue.length - 1];
req.lines = lines;
}
};
Connection.prototype.addFlags = function(uids, flags, cb) {
this._store('UID ', uids, { mode: '+', flags: flags }, cb);
};
Connection.prototype.delFlags = function(uids, flags, cb) {
this._store('UID ', uids, { mode: '-', flags: flags }, cb);
};
Connection.prototype.setFlags = function(uids, flags, cb) {
this._store('UID ', uids, { mode: '', flags: flags }, cb);
};
Connection.prototype.addKeywords = function(uids, keywords, cb) {
this._store('UID ', uids, { mode: '+', keywords: keywords }, cb);
};
Connection.prototype.delKeywords = function(uids, keywords, cb) {
this._store('UID ', uids, { mode: '-', keywords: keywords }, cb);
};
Connection.prototype.setKeywords = function(uids, keywords, cb) {
this._store('UID ', uids, { mode: '', keywords: keywords }, cb);
};
Connection.prototype._store = function(which, uids, cfg, cb) {
var mode = cfg.mode,
isFlags = (cfg.flags !== undefined),
items = (isFlags ? cfg.flags : cfg.keywords);
if (this._box === undefined)
throw new Error('No mailbox is currently selected');
else if (uids === undefined)
throw new Error('No messages specified');
if (!Array.isArray(uids))
uids = [uids];
validateUIDList(uids);
if (uids.length === 0) {
throw new Error('Empty '
+ (which === '' ? 'sequence number' : 'uid')
+ 'list');
}
if ((!Array.isArray(items) && typeof items !== 'string')
|| (Array.isArray(items) && items.length === 0))
throw new Error((isFlags ? 'Flags' : 'Keywords')
+ ' argument must be a string or a non-empty Array');
if (!Array.isArray(items))
items = [items];
for (var i = 0, len = items.length; i < len; ++i) {
if (isFlags) {
if (items[i][0] !== '\\')
items[i] = '\\' + items[i];
} else {
// keyword contains any char except control characters (%x00-1F and %x7F)
// and: '(', ')', '{', ' ', '%', '*', '\', '"', ']'
if (RE_INVALID_KW_CHARS.test(items[i])) {
throw new Error('The keyword "' + items[i]
+ '" contains invalid characters');
}
}
}
items = items.join(' ');
uids = uids.join(',');
var modifiers = '';
if (cfg.modseq !== undefined && !this._box.nomodseq)
modifiers += 'UNCHANGEDSINCE ' + cfg.modseq + ' ';
this._enqueue(which + 'STORE ' + uids + ' '
+ modifiers
+ mode + 'FLAGS.SILENT (' + items + ')', cb);
};
Connection.prototype.copy = function(uids, boxTo, cb) {
this._copy('UID ', uids, boxTo, cb);
};
Connection.prototype._copy = function(which, uids, boxTo, cb) {
if (this._box === undefined)
throw new Error('No mailbox is currently selected');
if (!Array.isArray(uids))
uids = [uids];
validateUIDList(uids);
if (uids.length === 0) {
throw new Error('Empty '
+ (which === '' ? 'sequence number' : 'uid')
+ 'list');
}
boxTo = escape(utf7.encode(''+boxTo));
this._enqueue(which + 'COPY ' + uids.join(',') + ' "' + boxTo + '"', cb);
};
Connection.prototype.move = function(uids, boxTo, cb) {
this._move('UID ', uids, boxTo, cb);
};
Connection.prototype._move = function(which, uids, boxTo, cb) {
if (this._box === undefined)
throw new Error('No mailbox is currently selected');
if (this.serverSupports('MOVE')) {
if (!Array.isArray(uids))
uids = [uids];
validateUIDList(uids);
if (uids.length === 0) {
throw new Error('Empty '
+ (which === '' ? 'sequence number' : 'uid')
+ 'list');
}
uids = uids.join(',');
boxTo = escape(utf7.encode(''+boxTo));
this._enqueue(which + 'MOVE ' + uids + ' "' + boxTo + '"', cb);
} else if (this._box.permFlags.indexOf('\\Deleted') === -1
&& this._box.flags.indexOf('\\Deleted') === -1) {
throw new Error('Cannot move message: '
+ 'server does not allow deletion of messages');
} else {
var deletedUIDs, task = 0, self = this;
this._copy(which, uids, boxTo, function ccb(err, info) {
if (err)
return cb(err, info);
if (task === 0 && which && self.serverSupports('UIDPLUS')) {
// UIDPLUS gives us a 'UID EXPUNGE n' command to expunge a subset of
// messages with the \Deleted flag set. This allows us to skip some
// actions.
task = 2;
}
// Make sure we don't expunge any messages marked as Deleted except the
// one we are moving
if (task === 0) {
self.search(['DELETED'], function(e, result) {
++task;
deletedUIDs = result;
ccb(e, info);
});
} else if (task === 1) {
if (deletedUIDs.length) {
self.delFlags(deletedUIDs, '\\Deleted', function(e) {
++task;
ccb(e, info);
});
} else {
++task;
ccb(err, info);
}
} else if (task === 2) {
var cbMarkDel = function(e) {
++task;
ccb(e, info);
};
if (which)
self.addFlags(uids, '\\Deleted', cbMarkDel);
else
self.seq.addFlags(uids, '\\Deleted', cbMarkDel);
} else if (task === 3) {
if (which && self.serverSupports('UIDPLUS')) {
self.expunge(uids, function(e) {
cb(e, info);
});
} else {
self.expunge(function(e) {
++task;
ccb(e, info);
});
}
} else if (task === 4) {
if (deletedUIDs.length) {
self.addFlags(deletedUIDs, '\\Deleted', function(e) {
cb(e, info);
});
} else
cb(err, info);
}
});
}
};
Connection.prototype.fetch = function(uids, options) {
return this._fetch('UID ', uids, options);
};
Connection.prototype._fetch = function(which, uids, options) {
if (uids === undefined
|| uids === null
|| (Array.isArray(uids) && uids.length === 0))
throw new Error('Nothing to fetch');
if (!Array.isArray(uids))
uids = [uids];
validateUIDList(uids);
if (uids.length === 0) {
throw new Error('Empty '
+ (which === '' ? 'sequence number' : 'uid')
+ 'list');
}
uids = uids.join(',');
var cmd = which + 'FETCH ' + uids + ' (',
fetching = [],
i, len, key;
if (this.serverSupports('X-GM-EXT-1')) {
fetching.push('X-GM-THRID');
fetching.push('X-GM-MSGID');
fetching.push('X-GM-LABELS');
}
if (this.serverSupports('CONDSTORE') && !this._box.nomodseq)
fetching.push('MODSEQ');
fetching.push('UID');
fetching.push('FLAGS');
fetching.push('INTERNALDATE');
var modifiers;
if (options) {
modifiers = options.modifiers;
if (options.envelope)
fetching.push('ENVELOPE');
if (options.struct)
fetching.push('BODYSTRUCTURE');
if (options.size)
fetching.push('RFC822.SIZE');
if (Array.isArray(options.extensions)) {
options.extensions.forEach(function (extension) {
fetching.push(extension.toUpperCase());
});
}
cmd += fetching.join(' ');
if (options.bodies !== undefined) {
var bodies = options.bodies,
prefix = (options.markSeen ? '' : '.PEEK');
if (!Array.isArray(bodies))
bodies = [bodies];
for (i = 0, len = bodies.length; i < len; ++i) {
fetching.push(parseExpr(''+bodies[i]));
cmd += ' BODY' + prefix + '[' + bodies[i] + ']';
}
}
} else
cmd += fetching.join(' ');
cmd += ')';
var modkeys = (typeof modifiers === 'object' ? Object.keys(modifiers) : []),
modstr = ' (';
for (i = 0, len = modkeys.length, key; i < len; ++i) {
key = modkeys[i].toUpperCase();
if (key === 'CHANGEDSINCE' && this.serverSupports('CONDSTORE')
&& !this._box.nomodseq)
modstr += key + ' ' + modifiers[modkeys[i]] + ' ';
}
if (modstr.length > 2) {
cmd += modstr.substring(0, modstr.length - 1);
cmd += ')';
}
this._enqueue(cmd);
var req = this._queue[this._queue.length - 1];
req.fetchCache = {};
req.fetching = fetching;
return (req.bodyEmitter = new EventEmitter());
};
// Extension methods ===========================================================
Connection.prototype.setLabels = function(uids, labels, cb) {
this._storeLabels('UID ', uids, labels, '', cb);
};
Connection.prototype.addLabels = function(uids, labels, cb) {
this._storeLabels('UID ', uids, labels, '+', cb);
};
Connection.prototype.delLabels = function(uids, labels, cb) {
this._storeLabels('UID ', uids, labels, '-', cb);
};
Connection.prototype._storeLabels = function(which, uids, labels, mode, cb) {
if (!this.serverSupports('X-GM-EXT-1'))
throw new Error('Server must support X-GM-EXT-1 capability');
else if (this._box === undefined)
throw new Error('No mailbox is currently selected');
else if (uids === undefined)
throw new Error('No messages specified');
if (!Array.isArray(uids))
uids = [uids];
validateUIDList(uids);
if (uids.length === 0) {
throw new Error('Empty '
+ (which === '' ? 'sequence number' : 'uid')
+ 'list');
}
if ((!Array.isArray(labels) && typeof labels !== 'string')
|| (Array.isArray(labels) && labels.length === 0))
throw new Error('labels argument must be a string or a non-empty Array');
if (!Array.isArray(labels))
labels = [labels];
labels = labels.map(function(v) {
return '"' + escape(utf7.encode(''+v)) + '"';
}).join(' ');
uids = uids.join(',');
this._enqueue(which + 'STORE ' + uids + ' ' + mode
+ 'X-GM-LABELS.SILENT (' + labels + ')', cb);
};
Connection.prototype.sort = function(sorts, criteria, cb) {
this._sort('UID ', sorts, criteria, cb);
};
Connection.prototype._sort = function(which, sorts, criteria, cb) {
if (this._box === undefined)
throw new Error('No mailbox is currently selected');
else if (!Array.isArray(sorts) || !sorts.length)
throw new Error('Expected array with at least one sort criteria');
else if (!Array.isArray(criteria))
throw new Error('Expected array for search criteria');
else if (!this.serverSupports('SORT'))
throw new Error('Sort is not supported on the server');
sorts = sorts.map(function(c) {
if (typeof c !== 'string')
throw new Error('Unexpected sort criteria data type. '
+ 'Expected string. Got: ' + typeof criteria);
var modifier = '';
if (c[0] === '-') {
modifier = 'REVERSE ';
c = c.substring(1);
}
switch (c.toUpperCase()) {
case 'ARRIVAL':
case 'CC':
case 'DATE':
case 'FROM':
case 'SIZE':
case 'SUBJECT':
case 'TO':
break;
default:
throw new Error('Unexpected sort criteria: ' + c);
}
return modifier + c;
});
sorts = sorts.join(' ');
var info = { hasUTF8: false /*output*/ },
query = buildSearchQuery(criteria, this._caps, info),
charset = 'US-ASCII',
lines;
if (info.hasUTF8) {
charset = 'UTF-8';
lines = query.split(CRLF);
query = lines.shift();
}
this._enqueue(which + 'SORT (' + sorts + ') ' + charset + query, cb);
if (info.hasUTF8) {
var req = this._queue[this._queue.length - 1];
req.lines = lines;
}
};
Connection.prototype.esearch = function(criteria, options, cb) {
this._esearch('UID ', criteria, options, cb);
};
Connection.prototype._esearch = function(which, criteria, options, cb) {
if (this._box === undefined)
throw new Error('No mailbox is currently selected');
else if (!Array.isArray(criteria))
throw new Error('Expected array for search options');
var info = { hasUTF8: false /*output*/ },
query = buildSearchQuery(criteria, this._caps, info),
charset = '',
lines;
if (info.hasUTF8) {
charset = ' CHARSET UTF-8';
lines = query.split(CRLF);
query = lines.shift();
}
if (typeof options === 'function') {
cb = options;
options = '';
} else if (!options)
options = '';
if (Array.isArray(options))
options = options.join(' ');
this._enqueue(which + 'SEARCH RETURN (' + options + ')' + charset + query, cb);
if (info.hasUTF8) {
var req = this._queue[this._queue.length - 1];
req.lines = lines;
}
};
Connection.prototype.setQuota = function(quotaRoot, limits, cb) {
if (typeof limits === 'function') {
cb = limits;
limits = {};
}
var triplets = '';
for (var l in limits) {
if (triplets)
triplets += ' ';
triplets += l + ' ' + limits[l];
}
quotaRoot = escape(utf7.encode(''+quotaRoot));
this._enqueue('SETQUOTA "' + quotaRoot + '" (' + triplets + ')',
function(err, quotalist) {
if (err)
return cb(err);
cb(err, quotalist ? quotalist[0] : limits);
}
);
};
Connection.prototype.getQuota = function(quotaRoot, cb) {
quotaRoot = escape(utf7.encode(''+quotaRoot));
this._enqueue('GETQUOTA "' + quotaRoot + '"', function(err, quotalist) {
if (err)
return cb(err);
cb(err, quotalist[0]);
});
};
Connection.prototype.getQuotaRoot = function(boxName, cb) {
boxName = escape(utf7.encode(''+boxName));
this._enqueue('GETQUOTAROOT "' + boxName + '"', function(err, quotalist) {
if (err)
return cb(err);
var quotas = {};
if (quotalist) {
for (var i = 0, len = quotalist.length; i < len; ++i)
quotas[quotalist[i].root] = quotalist[i].resources;
}
cb(err, quotas);
});
};
Connection.prototype.thread = function(algorithm, criteria, cb) {
this._thread('UID ', algorithm, criteria, cb);
};
Connection.prototype._thread = function(which, algorithm, criteria, cb) {
algorithm = algorithm.toUpperCase();
if (!this.serverSupports('THREAD=' + algorithm))
throw new Error('Server does not support that threading algorithm');
var info = { hasUTF8: false /*output*/ },
query = buildSearchQuery(criteria, this._caps, info),
charset = 'US-ASCII',
lines;
if (info.hasUTF8) {
charset = 'UTF-8';
lines = query.split(CRLF);
query = lines.shift();
}
this._enqueue(which + 'THREAD ' + algorithm + ' ' + charset + query, cb);
if (info.hasUTF8) {
var req = this._queue[this._queue.length - 1];
req.lines = lines;
}
};
Connection.prototype.addFlagsSince = function(uids, flags, modseq, cb) {
this._store('UID ',
uids,
{ mode: '+', flags: flags, modseq: modseq },
cb);
};
Connection.prototype.delFlagsSince = function(uids, flags, modseq, cb) {
this._store('UID ',
uids,
{ mode: '-', flags: flags, modseq: modseq },
cb);
};
Connection.prototype.setFlagsSince = function(uids, flags, modseq, cb) {
this._store('UID ',
uids,
{ mode: '', flags: flags, modseq: modseq },
cb);
};
Connection.prototype.addKeywordsSince = function(uids, keywords, modseq, cb) {
this._store('UID ',
uids,
{ mode: '+', keywords: keywords, modseq: modseq },
cb);
};
Connection.prototype.delKeywordsSince = function(uids, keywords, modseq, cb) {
this._store('UID ',
uids,
{ mode: '-', keywords: keywords, modseq: modseq },
cb);
};
Connection.prototype.setKeywordsSince = function(uids, keywords, modseq, cb) {
this._store('UID ',
uids,
{ mode: '', keywords: keywords, modseq: modseq },
cb);
};
// END Extension methods =======================================================
// Namespace for seqno-based commands
Object.defineProperty(Connection.prototype, 'seq', { get: function() {
var self = this;
return {
delKeywords: function(seqnos, keywords, cb) {
self._store('', seqnos, { mode: '-', keywords: keywords }, cb);
},
addKeywords: function(seqnos, keywords, cb) {
self._store('', seqnos, { mode: '+', keywords: keywords }, cb);
},
setKeywords: function(seqnos, keywords, cb) {
self._store('', seqnos, { mode: '', keywords: keywords }, cb);
},
delFlags: function(seqnos, flags, cb) {
self._store('', seqnos, { mode: '-', flags: flags }, cb);
},
addFlags: function(seqnos, flags, cb) {
self._store('', seqnos, { mode: '+', flags: flags }, cb);
},
setFlags: function(seqnos, flags, cb) {
self._store('', seqnos, { mode: '', flags: flags }, cb);
},
move: function(seqnos, boxTo, cb) {
self._move('', seqnos, boxTo, cb);
},
copy: function(seqnos, boxTo, cb) {
self._copy('', seqnos, boxTo, cb);
},
fetch: function(seqnos, options) {
return self._fetch('', seqnos, options);
},
search: function(options, cb) {
self._search('', options, cb);
},
// Extensions ==============================================================
delLabels: function(seqnos, labels, cb) {
self._storeLabels('', seqnos, labels, '-', cb);
},
addLabels: function(seqnos, labels, cb) {
self._storeLabels('', seqnos, labels, '+', cb);
},
setLabels: function(seqnos, labels, cb) {
self._storeLabels('', seqnos, labels, '', cb);
},
esearch: function(criteria, options, cb) {
self._esearch('', criteria, options, cb);
},
sort: function(sorts, options, cb) {
self._sort('', sorts, options, cb);
},
thread: function(algorithm, criteria, cb) {
self._thread('', algorithm, criteria, cb);
},
delKeywordsSince: function(seqnos, keywords, modseq, cb) {
self._store('',
seqnos,
{ mode: '-', keywords: keywords, modseq: modseq },
cb);
},
addKeywordsSince: function(seqnos, keywords, modseq, cb) {
self._store('',
seqnos,
{ mode: '+', keywords: keywords, modseq: modseq },
cb);
},
setKeywordsSince: function(seqnos, keywords, modseq, cb) {
self._store('',
seqnos,
{ mode: '', keywords: keywords, modseq: modseq },
cb);
},
delFlagsSince: function(seqnos, flags, modseq, cb) {
self._store('',
seqnos,
{ mode: '-', flags: flags, modseq: modseq },
cb);
},
addFlagsSince: function(seqnos, flags, modseq, cb) {
self._store('',
seqnos,
{ mode: '+', flags: flags, modseq: modseq },
cb);
},
setFlagsSince: function(seqnos, flags, modseq, cb) {
self._store('',
seqnos,
{ mode: '', flags: flags, modseq: modseq },
cb);
}
};
}});
Connection.prototype._resUntagged = function(info) {
var type = info.type, i, len, box, attrs, key;
if (type === 'bye')
this._sock.end();
else if (type === 'namespace')
this.namespaces = info.text;
else if (type === 'id')
this._curReq.cbargs.push(info.text);
else if (type === 'capability')
this._caps = info.text.map(function(v) { return v.toUpperCase(); });
else if (type === 'preauth')
this.state = 'authenticated';
else if (type === 'sort' || type === 'thread' || type === 'esearch')
this._curReq.cbargs.push(info.text);
else if (type === 'search') {
if (info.text.results !== undefined) {
// CONDSTORE-modified search results
this._curReq.cbargs.push(info.text.results);
this._curReq.cbargs.push(info.text.modseq);
} else
this._curReq.cbargs.push(info.text);
} else if (type === 'quota') {
var cbargs = this._curReq.cbargs;
if (!cbargs.length)
cbargs.push([]);
cbargs[0].push(info.text);
} else if (type === 'recent') {
if (!this._box && RE_OPENBOX.test(this._curReq.type))
this._createCurrentBox();
if (this._box)
this._box.messages.new = info.num;
} else if (type === 'flags') {
if (!this._box && RE_OPENBOX.test(this._curReq.type))
this._createCurrentBox();
if (this._box)
this._box.flags = info.text;
} else if (type === 'bad' || type === 'no') {
if (this.state === 'connected' && !this._curReq) {
clearTimeout(this._tmrConn);
clearTimeout(this._tmrAuth);
var err = new Error('Received negative welcome: ' + info.text);
err.source = 'protocol';
this.emit('error', err);
this._sock.end();
}
} else if (type === 'exists') {
if (!this._box && RE_OPENBOX.test(this._curReq.type))
this._createCurrentBox();
if (this._box) {
var prev = this._box.messages.total,
now = info.num;
this._box.messages.total = now;
if (now > prev && this.state === 'authenticated') {
this._box.messages.new = now - prev;
this.emit('mail', this._box.messages.new);
}
}
} else if (type === 'expunge') {
if (this._box) {
if (this._box.messages.total > 0)
--this._box.messages.total;
this.emit('expunge', info.num);
}
} else if (type === 'ok') {
if (this.state === 'connected' && !this._curReq)
this._login();
else if (typeof info.textCode === 'string'
&& info.textCode.toUpperCase() === 'ALERT')
this.emit('alert', info.text);
else if (this._curReq
&& info.textCode
&& (RE_OPENBOX.test(this._curReq.type))) {
// we're opening a mailbox
if (!this._box)
this._createCurrentBox();
if (info.textCode.key)
key = info.textCode.key.toUpperCase();
else
key = info.textCode;
if (key === 'UIDVALIDITY')
this._box.uidvalidity = info.textCode.val;
else if (key === 'UIDNEXT')
this._box.uidnext = info.textCode.val;
else if (key === 'HIGHESTMODSEQ')
this._box.highestmodseq = ''+info.textCode.val;
else if (key === 'PERMANENTFLAGS') {
var idx, permFlags, keywords;
this._box.permFlags = permFlags = info.textCode.val;
if ((idx = this._box.permFlags.indexOf('\\*')) > -1) {
this._box.newKeywords = true;
permFlags.splice(idx, 1);
}
this._box.keywords = keywords = permFlags.filter(function(f) {
return (f[0] !== '\\');
});
for (i = 0, len = keywords.length; i < len; ++i)
permFlags.splice(permFlags.indexOf(keywords[i]), 1);
} else if (key === 'UIDNOTSTICKY')
this._box.persistentUIDs = false;
else if (key === 'NOMODSEQ')
this._box.nomodseq = true;
} else if (typeof info.textCode === 'string'
&& info.textCode.toUpperCase() === 'UIDVALIDITY')
this.emit('uidvalidity', info.text);
} else if (type === 'list' || type === 'lsub' || type === 'xlist') {
if (this.delimiter === undefined)
this.delimiter = info.text.delimiter;
else {
if (this._curReq.cbargs.length === 0)
this._curReq.cbargs.push({});
box = {
attribs: info.text.flags,
delimiter: info.text.delimiter,
children: null,
parent: null
};
for (i = 0, len = SPECIAL_USE_ATTRIBUTES.length; i < len; ++i)
if (box.attribs.indexOf(SPECIAL_USE_ATTRIBUTES[i]) > -1)
box.special_use_attrib = SPECIAL_USE_ATTRIBUTES[i];
var name = info.text.name,
curChildren = this._curReq.cbargs[0];
if (box.delimiter) {
var path = name.split(box.delimiter),
parent = null;
name = path.pop();
for (i = 0, len = path.length; i < len; ++i) {
if (!curChildren[path[i]])
curChildren[path[i]] = {};
if (!curChildren[path[i]].children)
curChildren[path[i]].children = {};
parent = curChildren[path[i]];
curChildren = curChildren[path[i]].children;
}
box.parent = parent;
}
if (curChildren[name])
box.children = curChildren[name].children;
curChildren[name] = box;
}
} else if (type === 'status') {
box = {
name: info.text.name,
uidnext: 0,
uidvalidity: 0,
messages: {
total: 0,
new: 0,
unseen: 0
}
};
attrs = info.text.attrs;
if (attrs) {
if (attrs.recent !== undefined)
box.messages.new = attrs.recent;
if (attrs.unseen !== undefined)
box.messages.unseen = attrs.unseen;
if (attrs.messages !== undefined)
box.messages.total = attrs.messages;
if (attrs.uidnext !== undefined)
box.uidnext = attrs.uidnext;
if (attrs.uidvalidity !== undefined)
box.uidvalidity = attrs.uidvalidity;
if (attrs.highestmodseq !== undefined) // CONDSTORE
box.highestmodseq = ''+attrs.highestmodseq;
}
this._curReq.cbargs.push(box);
} else if (type === 'fetch') {
if (/^(?:UID )?FETCH/.test(this._curReq.fullcmd)) {
// FETCH response sent as result of FETCH request
var msg = this._curReq.fetchCache[info.num],
keys = Object.keys(info.text),
keyslen = keys.length,
toget, msgEmitter, j;
if (msg === undefined) {
// simple case -- no bodies were streamed
toget = this._curReq.fetching.slice(0);
if (toget.length === 0)
return;
msgEmitter = new EventEmitter();
attrs = {};
this._curReq.bodyEmitter.emit('message', msgEmitter, info.num);
} else {
toget = msg.toget;
msgEmitter = msg.msgEmitter;
attrs = msg.attrs;
}
i = toget.length;
if (i === 0) {
if (msg && !msg.ended) {
msg.ended = true;
process.nextTick(function() {
msgEmitter.emit('end');
});
}
return;
}
if (keyslen > 0) {
while (--i >= 0) {
j = keyslen;
while (--j >= 0) {
if (keys[j].toUpperCase() === toget[i]) {
if (!RE_BODYPART.test(toget[i])) {
if (toget[i] === 'X-GM-LABELS') {
var labels = info.text[keys[j]];
for (var k = 0, lenk = labels.length; k < lenk; ++k)
labels[k] = (''+labels[k]).replace(RE_ESCAPE, '\\');
}
key = FETCH_ATTR_MAP[toget[i]];
if (!key)
key = toget[i].toLowerCase();
attrs[key] = info.text[keys[j]];
}
toget.splice(i, 1);
break;
}
}
}
}
if (toget.length === 0) {
if (msg)
msg.ended = true;
process.nextTick(function() {
msgEmitter.emit('attributes', attrs);
msgEmitter.emit('end');
});
} else if (msg === undefined) {
this._curReq.fetchCache[info.num] = {
msgEmitter: msgEmitter,
toget: toget,
attrs: attrs,
ended: false
};
}
} else {
// FETCH response sent as result of STORE request or sent unilaterally,
// treat them as the same for now for simplicity
this.emit('update', info.num, info.text);
}
}
};
Connection.prototype._resTagged = function(info) {
var req = this._curReq, err;
if (!req)
return;
this._curReq = undefined;
if (info.type === 'no' || info.type === 'bad') {
var errtext;
if (info.text)
errtext = info.text;
else
errtext = req.oauthError;
err = new Error(errtext);
err.type = info.type;
err.textCode = info.textCode;
err.source = 'protocol';
} else if (this._box) {
if (req.type === 'EXAMINE' || req.type === 'SELECT') {
this._box.readOnly = (typeof info.textCode === 'string'
&& info.textCode.toUpperCase() === 'READ-ONLY');
}
// According to RFC 3501, UID commands do not give errors for
// non-existant user-supplied UIDs, so give the callback empty results
// if we unexpectedly received no untagged responses.
if (RE_UIDCMD_HASRESULTS.test(req.fullcmd) && req.cbargs.length === 0)
req.cbargs.push([]);
}
if (req.bodyEmitter) {
var bodyEmitter = req.bodyEmitter;
if (err)
bodyEmitter.emit('error', err);
process.nextTick(function() {
bodyEmitter.emit('end');
});
} else {
req.cbargs.unshift(err);
if (info.textCode && info.textCode.key) {
var key = info.textCode.key.toUpperCase();
if (key === 'APPENDUID') // [uidvalidity, newUID]
req.cbargs.push(info.textCode.val[1]);
else if (key === 'COPYUID') // [uidvalidity, sourceUIDs, destUIDs]
req.cbargs.push(info.textCode.val[2]);
}
req.cb && req.cb.apply(this, req.cbargs);
}
if (this._queue.length === 0
&& this._config.keepalive
&& this.state === 'authenticated'
&& !this._idle.enabled) {
this._idle.enabled = true;
this._doKeepaliveTimer(true);
}
this._processQueue();
};
Connection.prototype._createCurrentBox = function() {
this._box = {
name: '',
flags: [],
readOnly: false,
uidvalidity: 0,
uidnext: 0,
permFlags: [],
keywords: [],
newKeywords: false,
persistentUIDs: true,
nomodseq: false,
messages: {
total: 0,
new: 0
}
};
};
Connection.prototype._doKeepaliveTimer = function(immediate) {
var self = this,
interval = this._config.keepalive.interval || KEEPALIVE_INTERVAL,
idleWait = this._config.keepalive.idleInterval || MAX_IDLE_WAIT,
forceNoop = this._config.keepalive.forceNoop || false,
timerfn = function() {
if (self._idle.enabled) {
// unlike NOOP, IDLE is only a valid command after authenticating
if (!self.serverSupports('IDLE')
|| self.state !== 'authenticated'
|| forceNoop)
self._enqueue('NOOP', true);
else {
if (self._idle.started === undefined) {
self._idle.started = 0;
self._enqueue('IDLE', true);
} else if (self._idle.started > 0) {
var timeDiff = Date.now() - self._idle.started;
if (timeDiff >= idleWait) {
self._idle.enabled = false;
self.debug && self.debug('=> DONE');
self._sock.write('DONE' + CRLF);
return;
}
}
self._tmrKeepalive = setTimeout(timerfn, interval);
}
}
};
if (immediate)
timerfn();
else
this._tmrKeepalive = setTimeout(timerfn, interval);
};
Connection.prototype._login = function() {
var self = this, checkedNS = false;
var reentry = function(err) {
clearTimeout(self._tmrAuth);
if (err) {
self.emit('error', err);
return self._sock.end();
}
// 2. Get the list of available namespaces (RFC2342)
if (!checkedNS && self.serverSupports('NAMESPACE')) {
checkedNS = true;
return self._enqueue('NAMESPACE', reentry);
}
// 3. Get the top-level mailbox hierarchy delimiter used by the server
self._enqueue('LIST "" ""', function() {
self.state = 'authenticated';
self.emit('ready');
});
};
// 1. Get the supported capabilities
self._enqueue('CAPABILITY', function() {
// No need to attempt the login sequence if we're on a PREAUTH connection.
if (self.state === 'connected') {
var err,
checkCaps = function(error) {
if (error) {
error.source = 'authentication';
return reentry(error);
}
if (self._caps === undefined) {
// Fetch server capabilities if they were not automatically
// provided after authentication
return self._enqueue('CAPABILITY', reentry);
} else
reentry();
};
if (self.serverSupports('STARTTLS')
&& (self._config.autotls === 'always'
|| (self._config.autotls === 'required'
&& self.serverSupports('LOGINDISABLED')))) {
self._starttls();
return;
}
if (self.serverSupports('LOGINDISABLED')) {
err = new Error('Logging in is disabled on this server');
err.source = 'authentication';
return reentry(err);
}
var cmd;
if (self.serverSupports('AUTH=XOAUTH') && self._config.xoauth) {
self._caps = undefined;
cmd = 'AUTHENTICATE XOAUTH';
// are there any servers that support XOAUTH/XOAUTH2 and not SASL-IR?
//if (self.serverSupports('SASL-IR'))
cmd += ' ' + escape(self._config.xoauth);
self._enqueue(cmd, checkCaps);
} else if (self.serverSupports('AUTH=XOAUTH2') && self._config.xoauth2) {
self._caps = undefined;
cmd = 'AUTHENTICATE XOAUTH2';
//if (self.serverSupports('SASL-IR'))
cmd += ' ' + escape(self._config.xoauth2);
self._enqueue(cmd, checkCaps);
} else if (self._config.user && self._config.password) {
self._caps = undefined;
self._enqueue('LOGIN "' + escape(self._config.user) + '" "'
+ escape(self._config.password) + '"', checkCaps);
} else {
err = new Error('No supported authentication method(s) available. '
+ 'Unable to login.');
err.source = 'authentication';
return reentry(err);
}
} else
reentry();
});
};
Connection.prototype._starttls = function() {
var self = this;
this._enqueue('STARTTLS', function(err) {
if (err) {
self.emit('error', err);
return self._sock.end();
}
self._caps = undefined;
self._sock.removeAllListeners('error');
var tlsOptions = {};
tlsOptions.host = this._config.host;
// Host name may be overridden the tlsOptions
for (var k in this._config.tlsOptions)
tlsOptions[k] = this._config.tlsOptions[k];
tlsOptions.socket = self._sock;
self._sock = tls.connect(tlsOptions, function() {
self._login();
});
self._sock.on('error', self._onError);
self._sock.on('timeout', self._onSocketTimeout);
self._sock.setTimeout(self._config.socketTimeout);
self._parser.setStream(self._sock);
});
};
Connection.prototype._processQueue = function() {
if (this._curReq || !this._queue.length || !this._sock || !this._sock.writable)
return;
this._curReq = this._queue.shift();
if (this._tagcount === MAX_INT)
this._tagcount = 0;
var prefix;
if (this._curReq.type === 'IDLE' || this._curReq.type === 'NOOP')
prefix = this._curReq.type;
else
prefix = 'A' + (this._tagcount++);
var out = prefix + ' ' + this._curReq.fullcmd;
this.debug && this.debug('=> ' + inspect(out));
this._sock.write(out + CRLF, 'utf8');
if (this._curReq.literalAppendData) {
// LITERAL+: we are appending a mesage, and not waiting for a reply
this._sockWriteAppendData(this._curReq.literalAppendData);
}
};
Connection.prototype._sockWriteAppendData = function(appendData)
{
var val = appendData;
if (Buffer.isBuffer(appendData))
val = val.toString('utf8');
this.debug && this.debug('=> ' + inspect(val));
this._sock.write(val);
this._sock.write(CRLF);
};
Connection.prototype._enqueue = function(fullcmd, promote, cb) {
if (typeof promote === 'function') {
cb = promote;
promote = false;
}
var info = {
type: fullcmd.match(RE_CMD)[1],
fullcmd: fullcmd,
cb: cb,
cbargs: []
},
self = this;
if (promote)
this._queue.unshift(info);
else
this._queue.push(info);
if (!this._curReq
&& this.state !== 'disconnected'
&& this.state !== 'upgrading') {
// defer until next tick for requests like APPEND and FETCH where access to
// the request object is needed immediately after enqueueing
process.nextTick(function() { self._processQueue(); });
} else if (this._curReq
&& this._curReq.type === 'IDLE'
&& this._sock
&& this._sock.writable
&& this._idle.enabled) {
this._idle.enabled = false;
clearTimeout(this._tmrKeepalive);
if (this._idle.started > 0) {
// we've seen the continuation for our IDLE
this.debug && this.debug('=> DONE');
this._sock.write('DONE' + CRLF);
}
}
};
Connection.parseHeader = parseHeader; // from Parser.js
module.exports = Connection;
// utilities -------------------------------------------------------------------
function escape(str) {
return str.replace(RE_BACKSLASH, '\\\\').replace(RE_DBLQUOTE, '\\"');
}
function validateUIDList(uids, noThrow) {
for (var i = 0, len = uids.length, intval; i < len; ++i) {
if (typeof uids[i] === 'string') {
if (uids[i] === '*' || uids[i] === '*:*') {
if (len > 1)
uids = ['*'];
break;
} else if (RE_NUM_RANGE.test(uids[i]))
continue;
}
intval = parseInt(''+uids[i], 10);
if (isNaN(intval)) {
var err = new Error('UID/seqno must be an integer, "*", or a range: '
+ uids[i]);
if (noThrow)
return err;
else
throw err;
} else if (intval <= 0) {
var err = new Error('UID/seqno must be greater than zero');
if (noThrow)
return err;
else
throw err;
} else if (typeof uids[i] !== 'number') {
uids[i] = intval;
}
}
}
function hasNonASCII(str) {
for (var i = 0, len = str.length; i < len; ++i) {
if (str.charCodeAt(i) > 0x7F)
return true;
}
return false;
}
function buildString(str) {
if (typeof str !== 'string')
str = ''+str;
if (hasNonASCII(str)) {
var buf = new Buffer(str, 'utf8');
return '{' + buf.length + '}\r\n' + buf.toString('binary');
} else
return '"' + escape(str) + '"';
}
function buildSearchQuery(options, extensions, info, isOrChild) {
var searchargs = '', err, val;
for (var i = 0, len = options.length; i < len; ++i) {
var criteria = (isOrChild ? options : options[i]),
args = null,
modifier = (isOrChild ? '' : ' ');
if (typeof criteria === 'string')
criteria = criteria.toUpperCase();
else if (Array.isArray(criteria)) {
if (criteria.length > 1)
args = criteria.slice(1);
if (criteria.length > 0)
criteria = criteria[0].toUpperCase();
} else
throw new Error('Unexpected search option data type. '
+ 'Expected string or array. Got: ' + typeof criteria);
if (criteria === 'OR') {
if (args.length !== 2)
throw new Error('OR must have exactly two arguments');
if (isOrChild)
searchargs += 'OR (';
else
searchargs += ' OR (';
searchargs += buildSearchQuery(args[0], extensions, info, true);
searchargs += ') (';
searchargs += buildSearchQuery(args[1], extensions, info, true);
searchargs += ')';
} else {
if (criteria[0] === '!') {
modifier += 'NOT ';
criteria = criteria.substr(1);
}
switch(criteria) {
// -- Standard criteria --
case 'ALL':
case 'ANSWERED':
case 'DELETED':
case 'DRAFT':
case 'FLAGGED':
case 'NEW':
case 'SEEN':
case 'RECENT':
case 'OLD':
case 'UNANSWERED':
case 'UNDELETED':
case 'UNDRAFT':
case 'UNFLAGGED':
case 'UNSEEN':
searchargs += modifier + criteria;
break;
case 'BCC':
case 'BODY':
case 'CC':
case 'FROM':
case 'SUBJECT':
case 'TEXT':
case 'TO':
if (!args || args.length !== 1)
throw new Error('Incorrect number of arguments for search option: '
+ criteria);
val = buildString(args[0]);
if (info && val[0] === '{')
info.hasUTF8 = true;
searchargs += modifier + criteria + ' ' + val;
break;
case 'BEFORE':
case 'ON':
case 'SENTBEFORE':
case 'SENTON':
case 'SENTSINCE':
case 'SINCE':
if (!args || args.length !== 1)
throw new Error('Incorrect number of arguments for search option: '
+ criteria);
else if (!(args[0] instanceof Date)) {
if ((args[0] = new Date(args[0])).toString() === 'Invalid Date')
throw new Error('Search option argument must be a Date object'
+ ' or a parseable date string');
}
searchargs += modifier + criteria + ' ' + args[0].getDate() + '-'
+ MONTHS[args[0].getMonth()] + '-'
+ args[0].getFullYear();
break;
case 'KEYWORD':
case 'UNKEYWORD':
if (!args || args.length !== 1)
throw new Error('Incorrect number of arguments for search option: '
+ criteria);
searchargs += modifier + criteria + ' ' + args[0];
break;
case 'LARGER':
case 'SMALLER':
if (!args || args.length !== 1)
throw new Error('Incorrect number of arguments for search option: '
+ criteria);
var num = parseInt(args[0], 10);
if (isNaN(num))
throw new Error('Search option argument must be a number');
searchargs += modifier + criteria + ' ' + args[0];
break;
case 'HEADER':
if (!args || args.length !== 2)
throw new Error('Incorrect number of arguments for search option: '
+ criteria);
val = buildString(args[1]);
if (info && val[0] === '{')
info.hasUTF8 = true;
searchargs += modifier + criteria + ' "' + escape(''+args[0])
+ '" ' + val;
break;
case 'UID':
if (!args)
throw new Error('Incorrect number of arguments for search option: '
+ criteria);
validateUIDList(args);
if (args.length === 0)
throw new Error('Empty uid list');
searchargs += modifier + criteria + ' ' + args.join(',');
break;
// Extensions ==========================================================
case 'X-GM-MSGID': // Gmail unique message ID
case 'X-GM-THRID': // Gmail thread ID
if (extensions.indexOf('X-GM-EXT-1') === -1)
throw new Error('IMAP extension not available for: ' + criteria);
if (!args || args.length !== 1)
throw new Error('Incorrect number of arguments for search option: '
+ criteria);
else {
val = ''+args[0];
if (!(RE_INTEGER.test(args[0])))
throw new Error('Invalid value');
}
searchargs += modifier + criteria + ' ' + val;
break;
case 'X-GM-RAW': // Gmail search syntax
if (extensions.indexOf('X-GM-EXT-1') === -1)
throw new Error('IMAP extension not available for: ' + criteria);
if (!args || args.length !== 1)
throw new Error('Incorrect number of arguments for search option: '
+ criteria);
val = buildString(args[0]);
if (info && val[0] === '{')
info.hasUTF8 = true;
searchargs += modifier + criteria + ' ' + val;
break;
case 'X-GM-LABELS': // Gmail labels
if (extensions.indexOf('X-GM-EXT-1') === -1)
throw new Error('IMAP extension not available for: ' + criteria);
if (!args || args.length !== 1)
throw new Error('Incorrect number of arguments for search option: '
+ criteria);
searchargs += modifier + criteria + ' ' + args[0];
break;
case 'MODSEQ':
if (extensions.indexOf('CONDSTORE') === -1)
throw new Error('IMAP extension not available for: ' + criteria);
if (!args || args.length !== 1)
throw new Error('Incorrect number of arguments for search option: '
+ criteria);
searchargs += modifier + criteria + ' ' + args[0];
break;
default:
// last hope it's a seqno set
// http://tools.ietf.org/html/rfc3501#section-6.4.4
var seqnos = (args ? [criteria].concat(args) : [criteria]);
if (!validateUIDList(seqnos, true)) {
if (seqnos.length === 0)
throw new Error('Empty sequence number list');
searchargs += modifier + seqnos.join(',');
} else
throw new Error('Unexpected search option: ' + criteria);
}
}
if (isOrChild)
break;
}
return searchargs;
}
// Pulled from assert.deepEqual:
var pSlice = Array.prototype.slice;
function _deepEqual(actual, expected) {
// 7.1. All identical values are equivalent, as determined by ===.
if (actual === expected) {
return true;
} else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) {
if (actual.length !== expected.length) return false;
for (var i = 0; i < actual.length; i++) {
if (actual[i] !== expected[i]) return false;
}
return true;
// 7.2. If the expected value is a Date object, the actual value is
// equivalent if it is also a Date object that refers to the same time.
} else if (actual instanceof Date && expected instanceof Date) {
return actual.getTime() === expected.getTime();
// 7.3 If the expected value is a RegExp object, the actual value is
// equivalent if it is also a RegExp object with the same source and
// properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
} else if (actual instanceof RegExp && expected instanceof RegExp) {
return actual.source === expected.source &&
actual.global === expected.global &&
actual.multiline === expected.multiline &&
actual.lastIndex === expected.lastIndex &&
actual.ignoreCase === expected.ignoreCase;
// 7.4. Other pairs that do not both pass typeof value == 'object',
// equivalence is determined by ==.
} else if (typeof actual !== 'object' && typeof expected !== 'object') {
return actual == expected;
// 7.5 For all other Object pairs, including Array objects, equivalence is
// determined by having the same number of owned properties (as verified
// with Object.prototype.hasOwnProperty.call), the same set of keys
// (although not necessarily the same order), equivalent values for every
// corresponding key, and an identical 'prototype' property. Note: this
// accounts for both named and indexed properties on Arrays.
} else {
return objEquiv(actual, expected);
}
}
function isUndefinedOrNull(value) {
return value === null || value === undefined;
}
function isArguments(object) {
return Object.prototype.toString.call(object) === '[object Arguments]';
}
function objEquiv(a, b) {
var ka, kb, key, i;
if (isUndefinedOrNull(a) || isUndefinedOrNull(b))
return false;
// an identical 'prototype' property.
if (a.prototype !== b.prototype) return false;
//~~~I've managed to break Object.keys through screwy arguments passing.
// Converting to array solves the problem.
if (isArguments(a)) {
if (!isArguments(b)) {
return false;
}
a = pSlice.call(a);
b = pSlice.call(b);
return _deepEqual(a, b);
}
try {
ka = Object.keys(a);
kb = Object.keys(b);
} catch (e) {//happens when one is a string literal and the other isn't
return false;
}
// having the same number of owned properties (keys incorporates
// hasOwnProperty)
if (ka.length !== kb.length)
return false;
//the same set of keys (although not necessarily the same order),
ka.sort();
kb.sort();
//~~~cheap key test
for (i = ka.length - 1; i >= 0; i--) {
if (ka[i] != kb[i])
return false;
}
//equivalent values for every corresponding key, and
//~~~possibly expensive deep test
for (i = ka.length - 1; i >= 0; i--) {
key = ka[i];
if (!_deepEqual(a[key], b[key])) return false;
}
return true;
}

1025
nodered/rootfs/data/node_modules/imap/lib/Parser.js generated vendored Normal file
View File

@@ -0,0 +1,1025 @@
var EventEmitter = require('events').EventEmitter,
ReadableStream = require('stream').Readable
|| require('readable-stream').Readable,
inherits = require('util').inherits,
inspect = require('util').inspect;
var utf7 = require('utf7').imap,
jsencoding; // lazy-loaded
var CH_LF = 10,
LITPLACEHOLDER = String.fromCharCode(0),
EMPTY_READCB = function(n) {},
RE_INTEGER = /^\d+$/,
RE_PRECEDING = /^(?:\* |A\d+ |\+ ?)/,
RE_BODYLITERAL = /BODY\[(.*)\] \{(\d+)\}$/i,
RE_BODYINLINEKEY = /^BODY\[(.*)\]$/i,
RE_SEQNO = /^\* (\d+)/,
RE_LISTCONTENT = /^\((.*)\)$/,
RE_LITERAL = /\{(\d+)\}$/,
RE_UNTAGGED = /^\* (?:(OK|NO|BAD|BYE|FLAGS|ID|LIST|XLIST|LSUB|SEARCH|STATUS|CAPABILITY|NAMESPACE|PREAUTH|SORT|THREAD|ESEARCH|QUOTA|QUOTAROOT)|(\d+) (EXPUNGE|FETCH|RECENT|EXISTS))(?:(?: \[([^\]]+)\])?(?: (.+))?)?$/i,
RE_TAGGED = /^A(\d+) (OK|NO|BAD) ?(?:\[([^\]]+)\] )?(.*)$/i,
RE_CONTINUE = /^\+(?: (?:\[([^\]]+)\] )?(.+))?$/i,
RE_CRLF = /\r\n/g,
RE_HDR = /^([^:]+):[ \t]?(.+)?$/,
RE_ENCWORD = /=\?([^?*]*?)(?:\*.*?)?\?([qb])\?(.*?)\?=/gi,
RE_ENCWORD_END = /=\?([^?*]*?)(?:\*.*?)?\?([qb])\?(.*?)\?=$/i,
RE_ENCWORD_BEGIN = /^[ \t]=\?([^?*]*?)(?:\*.*?)?\?([qb])\?(.*?)\?=/i,
RE_QENC = /(?:=([a-fA-F0-9]{2}))|_/g,
RE_SEARCH_MODSEQ = /^(.+) \(MODSEQ (.+?)\)$/i,
RE_LWS_ONLY = /^[ \t]*$/;
function Parser(stream, debug) {
if (!(this instanceof Parser))
return new Parser(stream, debug);
EventEmitter.call(this);
this._stream = undefined;
this._body = undefined;
this._literallen = 0;
this._literals = [];
this._buffer = '';
this._ignoreReadable = false;
this.debug = debug;
var self = this;
this._cbReadable = function() {
if (self._ignoreReadable)
return;
if (self._literallen > 0 && !self._body)
self._tryread(self._literallen);
else
self._tryread();
};
this.setStream(stream);
process.nextTick(this._cbReadable);
}
inherits(Parser, EventEmitter);
Parser.prototype.setStream = function(stream) {
if (this._stream)
this._stream.removeListener('readable', this._cbReadable);
if (/^v0\.8\./.test(process.version)) {
this._stream = (new ReadableStream()).wrap(stream);
// since Readable.wrap() proxies events, we need to remove at least the
// proxied 'error' event since this can cause problems and Parser doesn't
// care about such events
stream._events.error.pop();
} else
this._stream = stream;
this._stream.on('readable', this._cbReadable);
};
Parser.prototype._tryread = function(n) {
if (this._stream.readable) {
var r = this._stream.read(n);
r && this._parse(r);
}
};
Parser.prototype._parse = function(data) {
var i = 0, datalen = data.length, idxlf;
if (this._literallen > 0) {
if (this._body) {
var body = this._body;
if (datalen >= this._literallen) {
var litlen = this._literallen;
i = litlen;
this._literallen = 0;
this._body = undefined;
body._read = EMPTY_READCB;
if (datalen > litlen)
body.push(data.slice(0, litlen));
else
body.push(data);
body.push(null);
} else {
this._literallen -= datalen;
var r = body.push(data);
if (!r) {
body._read = this._cbReadable;
return;
}
i = datalen;
}
} else {
if (datalen > this._literallen)
this._literals.push(data.slice(0, this._literallen));
else
this._literals.push(data);
i = this._literallen;
this._literallen = 0;
}
}
while (i < datalen) {
idxlf = indexOfCh(data, datalen, i, CH_LF);
if (idxlf === -1) {
this._buffer += data.toString('utf8', i);
break;
} else {
this._buffer += data.toString('utf8', i, idxlf);
this._buffer = this._buffer.trim();
i = idxlf + 1;
this.debug && this.debug('<= ' + inspect(this._buffer));
if (RE_PRECEDING.test(this._buffer)) {
var firstChar = this._buffer[0];
if (firstChar === '*')
this._resUntagged();
else if (firstChar === 'A')
this._resTagged();
else if (firstChar === '+')
this._resContinue();
if (this._literallen > 0 && i < datalen) {
this._ignoreReadable = true;
// literal data included in this chunk -- put it back onto stream
this._stream.unshift(data.slice(i));
this._ignoreReadable = false;
i = datalen;
if (!this._body) {
// check if unshifted contents satisfies non-body literal length
this._tryread(this._literallen);
}
}
} else {
this.emit('other', this._buffer);
this._buffer = '';
}
}
}
if (this._literallen === 0 || this._body)
this._tryread();
};
Parser.prototype._resTagged = function() {
var m;
if (m = RE_LITERAL.exec(this._buffer)) {
// non-BODY literal -- buffer it
this._buffer = this._buffer.replace(RE_LITERAL, LITPLACEHOLDER);
this._literallen = parseInt(m[1], 10);
} else if (m = RE_TAGGED.exec(this._buffer)) {
this._buffer = '';
this._literals = [];
this.emit('tagged', {
type: m[2].toLowerCase(),
tagnum: parseInt(m[1], 10),
textCode: (m[3] ? parseTextCode(m[3], this._literals) : m[3]),
text: m[4]
});
} else
this._buffer = '';
};
Parser.prototype._resUntagged = function() {
var m;
if (m = RE_BODYLITERAL.exec(this._buffer)) {
// BODY literal -- stream it
var which = m[1], size = parseInt(m[2], 10);
this._literallen = size;
this._body = new ReadableStream();
this._body._readableState.sync = false;
this._body._read = EMPTY_READCB;
m = RE_SEQNO.exec(this._buffer);
this._buffer = this._buffer.replace(RE_BODYLITERAL, '');
this.emit('body', this._body, {
seqno: parseInt(m[1], 10),
which: which,
size: size
});
} else if (m = RE_LITERAL.exec(this._buffer)) {
// non-BODY literal -- buffer it
this._buffer = this._buffer.replace(RE_LITERAL, LITPLACEHOLDER);
this._literallen = parseInt(m[1], 10);
} else if (m = RE_UNTAGGED.exec(this._buffer)) {
this._buffer = '';
// normal single line response
// m[1] or m[3] = response type
// if m[3] is set, m[2] = sequence number (for FETCH) or count
// m[4] = response text code (optional)
// m[5] = response text (optional)
var type, num, textCode, val;
if (m[2] !== undefined)
num = parseInt(m[2], 10);
if (m[4] !== undefined)
textCode = parseTextCode(m[4], this._literals);
type = (m[1] || m[3]).toLowerCase();
if (type === 'flags'
|| type === 'search'
|| type === 'capability'
|| type === 'sort') {
if (m[5]) {
if (type === 'search' && RE_SEARCH_MODSEQ.test(m[5])) {
// CONDSTORE search response
var p = RE_SEARCH_MODSEQ.exec(m[5]);
val = {
results: p[1].split(' '),
modseq: p[2]
};
} else {
if (m[5][0] === '(')
val = RE_LISTCONTENT.exec(m[5])[1].split(' ');
else
val = m[5].split(' ');
if (type === 'search' || type === 'sort')
val = val.map(function(v) { return parseInt(v, 10); });
}
} else
val = [];
} else if (type === 'thread') {
if (m[5])
val = parseExpr(m[5], this._literals);
else
val = [];
} else if (type === 'list' || type === 'lsub' || type === 'xlist')
val = parseBoxList(m[5], this._literals);
else if (type === 'id')
val = parseId(m[5], this._literals);
else if (type === 'status')
val = parseStatus(m[5], this._literals);
else if (type === 'fetch')
val = parseFetch.call(this, m[5], this._literals, num);
else if (type === 'namespace')
val = parseNamespaces(m[5], this._literals);
else if (type === 'esearch')
val = parseESearch(m[5], this._literals);
else if (type === 'quota')
val = parseQuota(m[5], this._literals);
else if (type === 'quotaroot')
val = parseQuotaRoot(m[5], this._literals);
else
val = m[5];
this._literals = [];
this.emit('untagged', {
type: type,
num: num,
textCode: textCode,
text: val
});
} else
this._buffer = '';
};
Parser.prototype._resContinue = function() {
var m = RE_CONTINUE.exec(this._buffer),
textCode,
text;
this._buffer = '';
if (!m)
return;
text = m[2];
if (m[1] !== undefined)
textCode = parseTextCode(m[1], this._literals);
this.emit('continue', {
textCode: textCode,
text: text
});
};
function indexOfCh(buffer, len, i, ch) {
var r = -1;
for (; i < len; ++i) {
if (buffer[i] === ch) {
r = i;
break;
}
}
return r;
}
function parseTextCode(text, literals) {
var r = parseExpr(text, literals);
if (r.length === 1)
return r[0];
else
return { key: r[0], val: r.length === 2 ? r[1] : r.slice(1) };
}
function parseESearch(text, literals) {
var r = parseExpr(text.toUpperCase().replace('UID', ''), literals),
attrs = {};
// RFC4731 unfortunately is lacking on documentation, so we're going to
// assume that the response text always begins with (TAG "A123") and skip that
// part ...
for (var i = 1, len = r.length, key, val; i < len; i += 2) {
key = r[i].toLowerCase();
val = r[i + 1];
if (key === 'all')
val = val.toString().split(',');
attrs[key] = val;
}
return attrs;
}
function parseId(text, literals) {
var r = parseExpr(text, literals),
id = {};
if (r[0] === null)
return null;
for (var i = 0, len = r[0].length; i < len; i += 2)
id[r[0][i].toLowerCase()] = r[0][i + 1];
return id;
}
function parseQuota(text, literals) {
var r = parseExpr(text, literals),
resources = {};
for (var i = 0, len = r[1].length; i < len; i += 3) {
resources[r[1][i].toLowerCase()] = {
usage: r[1][i + 1],
limit: r[1][i + 2]
};
}
return {
root: r[0],
resources: resources
};
}
function parseQuotaRoot(text, literals) {
var r = parseExpr(text, literals);
return {
roots: r.slice(1),
mailbox: r[0]
};
}
function parseBoxList(text, literals) {
var r = parseExpr(text, literals);
return {
flags: r[0],
delimiter: r[1],
name: utf7.decode(''+r[2])
};
}
function parseNamespaces(text, literals) {
var r = parseExpr(text, literals), i, len, j, len2, ns, nsobj, namespaces, n;
for (n = 0; n < 3; ++n) {
if (r[n]) {
namespaces = [];
for (i = 0, len = r[n].length; i < len; ++i) {
ns = r[n][i];
nsobj = {
prefix: ns[0],
delimiter: ns[1],
extensions: undefined
};
if (ns.length > 2)
nsobj.extensions = {};
for (j = 2, len2 = ns.length; j < len2; j += 2)
nsobj.extensions[ns[j]] = ns[j + 1];
namespaces.push(nsobj);
}
r[n] = namespaces;
}
}
return {
personal: r[0],
other: r[1],
shared: r[2]
};
}
function parseStatus(text, literals) {
var r = parseExpr(text, literals), attrs = {};
// r[1] is [KEY1, VAL1, KEY2, VAL2, .... KEYn, VALn]
for (var i = 0, len = r[1].length; i < len; i += 2)
attrs[r[1][i].toLowerCase()] = r[1][i + 1];
return {
name: utf7.decode(''+r[0]),
attrs: attrs
};
}
function parseFetch(text, literals, seqno) {
var list = parseExpr(text, literals)[0], attrs = {}, m, body;
// list is [KEY1, VAL1, KEY2, VAL2, .... KEYn, VALn]
for (var i = 0, len = list.length, key, val; i < len; i += 2) {
key = list[i].toLowerCase();
val = list[i + 1];
if (key === 'envelope')
val = parseFetchEnvelope(val);
else if (key === 'internaldate')
val = new Date(val);
else if (key === 'modseq') // always a list of one value
val = ''+val[0];
else if (key === 'body' || key === 'bodystructure')
val = parseBodyStructure(val);
else if (m = RE_BODYINLINEKEY.exec(list[i])) {
// a body was sent as a non-literal
val = new Buffer(''+val);
body = new ReadableStream();
body._readableState.sync = false;
body._read = EMPTY_READCB;
this.emit('body', body, {
seqno: seqno,
which: m[1],
size: val.length
});
body.push(val);
body.push(null);
continue;
}
attrs[key] = val;
}
return attrs;
}
function parseBodyStructure(cur, literals, prefix, partID) {
var ret = [], i, len;
if (prefix === undefined) {
var result = (Array.isArray(cur) ? cur : parseExpr(cur, literals));
if (result.length)
ret = parseBodyStructure(result, literals, '', 1);
} else {
var part, partLen = cur.length, next;
if (Array.isArray(cur[0])) { // multipart
next = -1;
while (Array.isArray(cur[++next])) {
ret.push(parseBodyStructure(cur[next],
literals,
prefix + (prefix !== '' ? '.' : '')
+ (partID++).toString(), 1));
}
part = { type: cur[next++].toLowerCase() };
if (partLen > next) {
if (Array.isArray(cur[next])) {
part.params = {};
for (i = 0, len = cur[next].length; i < len; i += 2)
part.params[cur[next][i].toLowerCase()] = cur[next][i + 1];
} else
part.params = cur[next];
++next;
}
} else { // single part
next = 7;
if (typeof cur[1] === 'string') {
part = {
// the path identifier for this part, useful for fetching specific
// parts of a message
partID: (prefix !== '' ? prefix : '1'),
// required fields as per RFC 3501 -- null or otherwise
type: cur[0].toLowerCase(), subtype: cur[1].toLowerCase(),
params: null, id: cur[3], description: cur[4], encoding: cur[5],
size: cur[6]
};
} else {
// type information for malformed multipart body
part = { type: cur[0] ? cur[0].toLowerCase() : null, params: null };
cur.splice(1, 0, null);
++partLen;
next = 2;
}
if (Array.isArray(cur[2])) {
part.params = {};
for (i = 0, len = cur[2].length; i < len; i += 2)
part.params[cur[2][i].toLowerCase()] = cur[2][i + 1];
if (cur[1] === null)
++next;
}
if (part.type === 'message' && part.subtype === 'rfc822') {
// envelope
if (partLen > next && Array.isArray(cur[next]))
part.envelope = parseFetchEnvelope(cur[next]);
else
part.envelope = null;
++next;
// body
if (partLen > next && Array.isArray(cur[next]))
part.body = parseBodyStructure(cur[next], literals, prefix, 1);
else
part.body = null;
++next;
}
if ((part.type === 'text'
|| (part.type === 'message' && part.subtype === 'rfc822'))
&& partLen > next)
part.lines = cur[next++];
if (typeof cur[1] === 'string' && partLen > next)
part.md5 = cur[next++];
}
// add any extra fields that may or may not be omitted entirely
parseStructExtra(part, partLen, cur, next);
ret.unshift(part);
}
return ret;
}
function parseStructExtra(part, partLen, cur, next) {
if (partLen > next) {
// disposition
// null or a special k/v list with these kinds of values:
// e.g.: ['Foo', null]
// ['Foo', ['Bar', 'Baz']]
// ['Foo', ['Bar', 'Baz', 'Bam', 'Pow']]
var disposition = { type: null, params: null };
if (Array.isArray(cur[next])) {
disposition.type = cur[next][0];
if (Array.isArray(cur[next][1])) {
disposition.params = {};
for (var i = 0, len = cur[next][1].length, key; i < len; i += 2) {
key = cur[next][1][i].toLowerCase();
disposition.params[key] = cur[next][1][i + 1];
}
}
} else if (cur[next] !== null)
disposition.type = cur[next];
if (disposition.type === null)
part.disposition = null;
else
part.disposition = disposition;
++next;
}
if (partLen > next) {
// language can be a string or a list of one or more strings, so let's
// make this more consistent ...
if (cur[next] !== null)
part.language = (Array.isArray(cur[next]) ? cur[next] : [cur[next]]);
else
part.language = null;
++next;
}
if (partLen > next)
part.location = cur[next++];
if (partLen > next) {
// extension stuff introduced by later RFCs
// this can really be any value: a string, number, or (un)nested list
// let's not parse it for now ...
part.extensions = cur[next];
}
}
function parseFetchEnvelope(list) {
return {
date: new Date(list[0]),
subject: decodeWords(list[1]),
from: parseEnvelopeAddresses(list[2]),
sender: parseEnvelopeAddresses(list[3]),
replyTo: parseEnvelopeAddresses(list[4]),
to: parseEnvelopeAddresses(list[5]),
cc: parseEnvelopeAddresses(list[6]),
bcc: parseEnvelopeAddresses(list[7]),
inReplyTo: list[8],
messageId: list[9]
};
}
function parseEnvelopeAddresses(list) {
var addresses = null;
if (Array.isArray(list)) {
addresses = [];
var inGroup = false, curGroup;
for (var i = 0, len = list.length, addr; i < len; ++i) {
addr = list[i];
if (addr[2] === null) { // end of group addresses
inGroup = false;
if (curGroup) {
addresses.push(curGroup);
curGroup = undefined;
}
} else if (addr[3] === null) { // start of group addresses
inGroup = true;
curGroup = {
group: addr[2],
addresses: []
};
} else { // regular user address
var info = {
name: decodeWords(addr[0]),
mailbox: addr[2],
host: addr[3]
};
if (inGroup)
curGroup.addresses.push(info);
else if (!inGroup)
addresses.push(info);
}
list[i] = addr;
}
if (inGroup) {
// no end of group found, assume implicit end
addresses.push(curGroup);
}
}
return addresses;
}
function parseExpr(o, literals, result, start, useBrackets) {
start = start || 0;
var inQuote = false,
lastPos = start - 1,
isTop = false,
isBody = false,
escaping = false,
val;
if (useBrackets === undefined)
useBrackets = true;
if (!result)
result = [];
if (typeof o === 'string') {
o = { str: o };
isTop = true;
}
for (var i = start, len = o.str.length; i < len; ++i) {
if (!inQuote) {
if (isBody) {
if (o.str[i] === ']') {
val = convStr(o.str.substring(lastPos + 1, i + 1), literals);
result.push(val);
lastPos = i;
isBody = false;
}
} else if (o.str[i] === '"')
inQuote = true;
else if (o.str[i] === ' '
|| o.str[i] === ')'
|| (useBrackets && o.str[i] === ']')) {
if (i - (lastPos + 1) > 0) {
val = convStr(o.str.substring(lastPos + 1, i), literals);
result.push(val);
}
if ((o.str[i] === ')' || (useBrackets && o.str[i] === ']')) && !isTop)
return i;
lastPos = i;
} else if ((o.str[i] === '(' || (useBrackets && o.str[i] === '['))) {
if (o.str[i] === '['
&& i - 4 >= start
&& o.str.substring(i - 4, i).toUpperCase() === 'BODY') {
isBody = true;
lastPos = i - 5;
} else {
var innerResult = [];
i = parseExpr(o, literals, innerResult, i + 1, useBrackets);
lastPos = i;
result.push(innerResult);
}
}
} else if (o.str[i] === '\\')
escaping = !escaping;
else if (o.str[i] === '"') {
if (!escaping)
inQuote = false;
escaping = false;
}
if (i + 1 === len && len - (lastPos + 1) > 0)
result.push(convStr(o.str.substring(lastPos + 1), literals));
}
return (isTop ? result : start);
}
function convStr(str, literals) {
if (str[0] === '"') {
str = str.substring(1, str.length - 1);
var newstr = '', isEscaping = false, p = 0;
for (var i = 0, len = str.length; i < len; ++i) {
if (str[i] === '\\') {
if (!isEscaping)
isEscaping = true;
else {
isEscaping = false;
newstr += str.substring(p, i - 1);
p = i;
}
} else if (str[i] === '"') {
if (isEscaping) {
isEscaping = false;
newstr += str.substring(p, i - 1);
p = i;
}
}
}
if (p === 0)
return str;
else {
newstr += str.substring(p);
return newstr;
}
} else if (str === 'NIL')
return null;
else if (RE_INTEGER.test(str)) {
// some IMAP extensions utilize large (64-bit) integers, which JavaScript
// can't handle natively, so we'll just keep it as a string if it's too big
var val = parseInt(str, 10);
return (val.toString() === str ? val : str);
} else if (literals && literals.length && str === LITPLACEHOLDER) {
var l = literals.shift();
if (Buffer.isBuffer(l))
l = l.toString('utf8');
return l;
}
return str;
}
function repeat(chr, len) {
var s = '';
for (var i = 0; i < len; ++i)
s += chr;
return s;
}
function decodeBytes(buf, encoding, offset, mlen, pendoffset, state, nextBuf) {
if (!jsencoding)
jsencoding = require('../deps/encoding/encoding');
if (jsencoding.encodingExists(encoding)) {
if (state.buffer !== undefined) {
if (state.encoding === encoding && state.consecutive) {
// concatenate buffer + current bytes in hopes of finally having
// something that's decodable
var newbuf = new Buffer(state.buffer.length + buf.length);
state.buffer.copy(newbuf, 0);
buf.copy(newbuf, state.buffer.length);
buf = newbuf;
} else {
// either:
// - the current encoded word is not separated by the previous partial
// encoded word by linear whitespace, OR
// - the current encoded word and the previous partial encoded word
// use different encodings
state.buffer = state.encoding = undefined;
state.curReplace = undefined;
}
}
var ret, isPartial = false;
if (state.remainder !== undefined) {
// use cached remainder from the previous lookahead
ret = state.remainder;
state.remainder = undefined;
} else {
try {
ret = jsencoding.TextDecoder(encoding).decode(buf);
} catch (e) {
if (e.message.indexOf('Seeking') === 0)
isPartial = true;
}
}
if (!isPartial && nextBuf) {
// try to decode a lookahead buffer (current buffer + next buffer)
// and see if it starts with the decoded value of the current buffer.
// if not, the current buffer is partial
var lookahead, lookaheadBuf = new Buffer(buf.length + nextBuf.length);
buf.copy(lookaheadBuf);
nextBuf.copy(lookaheadBuf, buf.length);
try {
lookahead = jsencoding.TextDecoder(encoding).decode(lookaheadBuf);
} catch(e) {
// cannot decode the lookahead, do nothing
}
if (lookahead !== undefined) {
if (lookahead.indexOf(ret) === 0) {
// the current buffer is whole, cache the lookahead's remainder
state.remainder = lookahead.substring(ret.length);
} else {
isPartial = true;
ret = undefined;
}
}
}
if (ret !== undefined) {
if (state.curReplace) {
// we have some previous partials which were finally "satisfied" by the
// current encoded word, so replace from the beginning of the first
// partial to the end of the current encoded word
state.replaces.push({
fromOffset: state.curReplace[0].fromOffset,
toOffset: offset + mlen,
val: ret
});
state.replaces.splice(state.replaces.indexOf(state.curReplace), 1);
state.curReplace = undefined;
} else {
// normal case where there are no previous partials and we successfully
// decoded a single encoded word
state.replaces.push({
// we ignore linear whitespace between consecutive encoded words
fromOffset: state.consecutive ? pendoffset : offset,
toOffset: offset + mlen,
val: ret
});
}
state.buffer = state.encoding = undefined;
return;
} else if (isPartial) {
// RFC2047 says that each decoded encoded word "MUST represent an integral
// number of characters. A multi-octet character may not be split across
// adjacent encoded-words." However, some MUAs appear to go against this,
// so we join broken encoded words separated by linear white space until
// we can successfully decode or we see a change in encoding
state.encoding = encoding;
state.buffer = buf;
if (!state.curReplace)
state.replaces.push(state.curReplace = []);
state.curReplace.push({
fromOffset: offset,
toOffset: offset + mlen,
// the value we replace this encoded word with if it doesn't end up
// becoming part of a successful decode
val: repeat('\uFFFD', buf.length)
});
return;
}
}
// in case of unexpected error or unsupported encoding, just substitute the
// raw bytes
state.replaces.push({
fromOffset: offset,
toOffset: offset + mlen,
val: buf.toString('binary')
});
}
function qEncReplacer(match, byte) {
if (match === '_')
return ' ';
else
return String.fromCharCode(parseInt(byte, 16));
}
function decodeWords(str, state) {
var pendoffset = -1;
if (!state) {
state = {
buffer: undefined,
encoding: undefined,
consecutive: false,
replaces: undefined,
curReplace: undefined,
remainder: undefined
};
}
state.replaces = [];
var bytes, m, next, i, j, leni, lenj, seq, replaces = [], lastReplace = {};
// join consecutive q-encoded words that have the same charset first
while (m = RE_ENCWORD.exec(str)) {
seq = {
consecutive: (pendoffset > -1
? RE_LWS_ONLY.test(str.substring(pendoffset, m.index))
: false),
charset: m[1].toLowerCase(),
encoding: m[2].toLowerCase(),
chunk: m[3],
index: m.index,
length: m[0].length,
pendoffset: pendoffset,
buf: undefined
};
lastReplace = replaces.length && replaces[replaces.length - 1];
if (seq.consecutive
&& seq.charset === lastReplace.charset
&& seq.encoding === lastReplace.encoding
&& seq.encoding === 'q') {
lastReplace.length += seq.length + seq.index - pendoffset;
lastReplace.chunk += seq.chunk;
} else {
replaces.push(seq);
lastReplace = seq;
}
pendoffset = m.index + m[0].length;
}
// generate replacement substrings and their positions
for (i = 0, leni = replaces.length; i < leni; ++i) {
m = replaces[i];
state.consecutive = m.consecutive;
if (m.encoding === 'q') {
// q-encoding, similar to quoted-printable
bytes = new Buffer(m.chunk.replace(RE_QENC, qEncReplacer), 'binary');
next = undefined;
} else {
// base64
bytes = m.buf || new Buffer(m.chunk, 'base64');
next = replaces[i + 1];
if (next && next.consecutive && next.encoding === m.encoding
&& next.charset === m.charset) {
// we use the next base64 chunk, if any, to determine the integrity
// of the current chunk
next.buf = new Buffer(next.chunk, 'base64');
}
}
decodeBytes(bytes, m.charset, m.index, m.length, m.pendoffset, state,
next && next.buf);
}
// perform the actual replacements
for (i = state.replaces.length - 1; i >= 0; --i) {
seq = state.replaces[i];
if (Array.isArray(seq)) {
for (j = 0, lenj = seq.length; j < lenj; ++j) {
str = str.substring(0, seq[j].fromOffset)
+ seq[j].val
+ str.substring(seq[j].toOffset);
}
} else {
str = str.substring(0, seq.fromOffset)
+ seq.val
+ str.substring(seq.toOffset);
}
}
return str;
}
function parseHeader(str, noDecode) {
var lines = str.split(RE_CRLF),
len = lines.length,
header = {},
state = {
buffer: undefined,
encoding: undefined,
consecutive: false,
replaces: undefined,
curReplace: undefined,
remainder: undefined
},
m, h, i, val;
for (i = 0; i < len; ++i) {
if (lines[i].length === 0)
break; // empty line separates message's header and body
if (lines[i][0] === '\t' || lines[i][0] === ' ') {
if (!Array.isArray(header[h]))
continue; // ignore invalid first line
// folded header content
val = lines[i];
if (!noDecode) {
if (RE_ENCWORD_END.test(lines[i - 1])
&& RE_ENCWORD_BEGIN.test(val)) {
// RFC2047 says to *ignore* leading whitespace in folded header values
// for adjacent encoded-words ...
val = val.substring(1);
}
}
header[h][header[h].length - 1] += val;
} else {
m = RE_HDR.exec(lines[i]);
if (m) {
h = m[1].toLowerCase().trim();
if (m[2]) {
if (header[h] === undefined)
header[h] = [m[2]];
else
header[h].push(m[2]);
} else
header[h] = [''];
} else
break;
}
}
if (!noDecode) {
var hvs;
for (h in header) {
hvs = header[h];
for (i = 0, len = header[h].length; i < len; ++i)
hvs[i] = decodeWords(hvs[i], state);
}
}
return header;
}
exports.Parser = Parser;
exports.parseExpr = parseExpr;
exports.parseEnvelopeAddresses = parseEnvelopeAddresses;
exports.parseBodyStructure = parseBodyStructure;
exports.parseHeader = parseHeader;

View File

@@ -0,0 +1,54 @@
# isarray
`Array#isArray` for older browsers.
## Usage
```js
var isArray = require('isarray');
console.log(isArray([])); // => true
console.log(isArray({})); // => false
```
## Installation
With [npm](http://npmjs.org) do
```bash
$ npm install isarray
```
Then bundle for the browser with
[browserify](https://github.com/substack/browserify).
With [component](http://component.io) do
```bash
$ component install juliangruber/isarray
```
## License
(MIT)
Copyright (c) 2013 Julian Gruber &lt;julian@juliangruber.com&gt;
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.

View File

@@ -0,0 +1,209 @@
/**
* Require the given path.
*
* @param {String} path
* @return {Object} exports
* @api public
*/
function require(path, parent, orig) {
var resolved = require.resolve(path);
// lookup failed
if (null == resolved) {
orig = orig || path;
parent = parent || 'root';
var err = new Error('Failed to require "' + orig + '" from "' + parent + '"');
err.path = orig;
err.parent = parent;
err.require = true;
throw err;
}
var module = require.modules[resolved];
// perform real require()
// by invoking the module's
// registered function
if (!module.exports) {
module.exports = {};
module.client = module.component = true;
module.call(this, module.exports, require.relative(resolved), module);
}
return module.exports;
}
/**
* Registered modules.
*/
require.modules = {};
/**
* Registered aliases.
*/
require.aliases = {};
/**
* Resolve `path`.
*
* Lookup:
*
* - PATH/index.js
* - PATH.js
* - PATH
*
* @param {String} path
* @return {String} path or null
* @api private
*/
require.resolve = function(path) {
if (path.charAt(0) === '/') path = path.slice(1);
var index = path + '/index.js';
var paths = [
path,
path + '.js',
path + '.json',
path + '/index.js',
path + '/index.json'
];
for (var i = 0; i < paths.length; i++) {
var path = paths[i];
if (require.modules.hasOwnProperty(path)) return path;
}
if (require.aliases.hasOwnProperty(index)) {
return require.aliases[index];
}
};
/**
* Normalize `path` relative to the current path.
*
* @param {String} curr
* @param {String} path
* @return {String}
* @api private
*/
require.normalize = function(curr, path) {
var segs = [];
if ('.' != path.charAt(0)) return path;
curr = curr.split('/');
path = path.split('/');
for (var i = 0; i < path.length; ++i) {
if ('..' == path[i]) {
curr.pop();
} else if ('.' != path[i] && '' != path[i]) {
segs.push(path[i]);
}
}
return curr.concat(segs).join('/');
};
/**
* Register module at `path` with callback `definition`.
*
* @param {String} path
* @param {Function} definition
* @api private
*/
require.register = function(path, definition) {
require.modules[path] = definition;
};
/**
* Alias a module definition.
*
* @param {String} from
* @param {String} to
* @api private
*/
require.alias = function(from, to) {
if (!require.modules.hasOwnProperty(from)) {
throw new Error('Failed to alias "' + from + '", it does not exist');
}
require.aliases[to] = from;
};
/**
* Return a require function relative to the `parent` path.
*
* @param {String} parent
* @return {Function}
* @api private
*/
require.relative = function(parent) {
var p = require.normalize(parent, '..');
/**
* lastIndexOf helper.
*/
function lastIndexOf(arr, obj) {
var i = arr.length;
while (i--) {
if (arr[i] === obj) return i;
}
return -1;
}
/**
* The relative require() itself.
*/
function localRequire(path) {
var resolved = localRequire.resolve(path);
return require(resolved, parent, path);
}
/**
* Resolve relative to the parent.
*/
localRequire.resolve = function(path) {
var c = path.charAt(0);
if ('/' == c) return path.slice(1);
if ('.' == c) return require.normalize(p, path);
// resolve deps by returning
// the dep in the nearest "deps"
// directory
var segs = parent.split('/');
var i = lastIndexOf(segs, 'deps') + 1;
if (!i) i = 0;
path = segs.slice(0, i + 1).join('/') + '/deps/' + path;
return path;
};
/**
* Check if module is defined at `path`.
*/
localRequire.exists = function(path) {
return require.modules.hasOwnProperty(localRequire.resolve(path));
};
return localRequire;
};
require.register("isarray/index.js", function(exports, require, module){
module.exports = Array.isArray || function (arr) {
return Object.prototype.toString.call(arr) == '[object Array]';
};
});
require.alias("isarray/index.js", "isarray/index.js");

View File

@@ -0,0 +1,19 @@
{
"name" : "isarray",
"description" : "Array#isArray for older browsers",
"version" : "0.0.1",
"repository" : "juliangruber/isarray",
"homepage": "https://github.com/juliangruber/isarray",
"main" : "index.js",
"scripts" : [
"index.js"
],
"dependencies" : {},
"keywords": ["browser","isarray","array"],
"author": {
"name": "Julian Gruber",
"email": "mail@juliangruber.com",
"url": "http://juliangruber.com"
},
"license": "MIT"
}

View File

@@ -0,0 +1,3 @@
module.exports = Array.isArray || function (arr) {
return Object.prototype.toString.call(arr) == '[object Array]';
};

View File

@@ -0,0 +1,57 @@
{
"_from": "isarray@0.0.1",
"_id": "isarray@0.0.1",
"_inBundle": false,
"_integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
"_location": "/imap/isarray",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "isarray@0.0.1",
"name": "isarray",
"escapedName": "isarray",
"rawSpec": "0.0.1",
"saveSpec": null,
"fetchSpec": "0.0.1"
},
"_requiredBy": [
"/imap/readable-stream"
],
"_resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"_shasum": "8a18acfca9a8f4177e09abfc6038939b05d1eedf",
"_spec": "isarray@0.0.1",
"_where": "/data/node_modules/imap/node_modules/readable-stream",
"author": {
"name": "Julian Gruber",
"email": "mail@juliangruber.com",
"url": "http://juliangruber.com"
},
"bugs": {
"url": "https://github.com/juliangruber/isarray/issues"
},
"bundleDependencies": false,
"dependencies": {},
"deprecated": false,
"description": "Array#isArray for older browsers",
"devDependencies": {
"tap": "*"
},
"homepage": "https://github.com/juliangruber/isarray",
"keywords": [
"browser",
"isarray",
"array"
],
"license": "MIT",
"main": "index.js",
"name": "isarray",
"repository": {
"type": "git",
"url": "git://github.com/juliangruber/isarray.git"
},
"scripts": {
"test": "tap test/*.js"
},
"version": "0.0.1"
}

View File

@@ -0,0 +1,5 @@
build/
test/
examples/
fs.js
zlib.js

View File

@@ -0,0 +1,18 @@
Copyright Joyent, Inc. and other Node contributors. All rights reserved.
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.

View File

@@ -0,0 +1,15 @@
# readable-stream
***Node-core streams for userland***
[![NPM](https://nodei.co/npm/readable-stream.png?downloads=true&downloadRank=true)](https://nodei.co/npm/readable-stream/)
[![NPM](https://nodei.co/npm-dl/readable-stream.png&months=6&height=3)](https://nodei.co/npm/readable-stream/)
This package is a mirror of the Streams2 and Streams3 implementations in Node-core.
If you want to guarantee a stable streams base, regardless of what version of Node you, or the users of your libraries are using, use **readable-stream** *only* and avoid the *"stream"* module in Node-core.
**readable-stream** comes in two major versions, v1.0.x and v1.1.x. The former tracks the Streams2 implementation in Node 0.10, including bug-fixes and minor improvements as they are added. The latter tracks Streams3 as it develops in Node 0.11; we will likely see a v1.2.x branch for Node 0.12.
**readable-stream** uses proper patch-level versioning so if you pin to `"~1.0.0"` youll get the latest Node 0.10 Streams2 implementation, including any fixes and minor non-breaking improvements. The patch-level versions of 1.0.x and 1.1.x should mirror the patch-level versions of Node-core releases. You should prefer the **1.0.x** releases for now and when youre ready to start using Streams3, pin to `"~1.1.0"`

View File

@@ -0,0 +1 @@
module.exports = require("./lib/_stream_duplex.js")

View File

@@ -0,0 +1,923 @@
diff --git a/lib/_stream_duplex.js b/lib/_stream_duplex.js
index c5a741c..a2e0d8e 100644
--- a/lib/_stream_duplex.js
+++ b/lib/_stream_duplex.js
@@ -26,8 +26,8 @@
module.exports = Duplex;
var util = require('util');
-var Readable = require('_stream_readable');
-var Writable = require('_stream_writable');
+var Readable = require('./_stream_readable');
+var Writable = require('./_stream_writable');
util.inherits(Duplex, Readable);
diff --git a/lib/_stream_passthrough.js b/lib/_stream_passthrough.js
index a5e9864..330c247 100644
--- a/lib/_stream_passthrough.js
+++ b/lib/_stream_passthrough.js
@@ -25,7 +25,7 @@
module.exports = PassThrough;
-var Transform = require('_stream_transform');
+var Transform = require('./_stream_transform');
var util = require('util');
util.inherits(PassThrough, Transform);
diff --git a/lib/_stream_readable.js b/lib/_stream_readable.js
index 0c3fe3e..90a8298 100644
--- a/lib/_stream_readable.js
+++ b/lib/_stream_readable.js
@@ -23,10 +23,34 @@ module.exports = Readable;
Readable.ReadableState = ReadableState;
var EE = require('events').EventEmitter;
+if (!EE.listenerCount) EE.listenerCount = function(emitter, type) {
+ return emitter.listeners(type).length;
+};
+
+if (!global.setImmediate) global.setImmediate = function setImmediate(fn) {
+ return setTimeout(fn, 0);
+};
+if (!global.clearImmediate) global.clearImmediate = function clearImmediate(i) {
+ return clearTimeout(i);
+};
+
var Stream = require('stream');
var util = require('util');
+if (!util.isUndefined) {
+ var utilIs = require('core-util-is');
+ for (var f in utilIs) {
+ util[f] = utilIs[f];
+ }
+}
var StringDecoder;
-var debug = util.debuglog('stream');
+var debug;
+if (util.debuglog)
+ debug = util.debuglog('stream');
+else try {
+ debug = require('debuglog')('stream');
+} catch (er) {
+ debug = function() {};
+}
util.inherits(Readable, Stream);
@@ -380,7 +404,7 @@ function chunkInvalid(state, chunk) {
function onEofChunk(stream, state) {
- if (state.decoder && !state.ended) {
+ if (state.decoder && !state.ended && state.decoder.end) {
var chunk = state.decoder.end();
if (chunk && chunk.length) {
state.buffer.push(chunk);
diff --git a/lib/_stream_transform.js b/lib/_stream_transform.js
index b1f9fcc..b0caf57 100644
--- a/lib/_stream_transform.js
+++ b/lib/_stream_transform.js
@@ -64,8 +64,14 @@
module.exports = Transform;
-var Duplex = require('_stream_duplex');
+var Duplex = require('./_stream_duplex');
var util = require('util');
+if (!util.isUndefined) {
+ var utilIs = require('core-util-is');
+ for (var f in utilIs) {
+ util[f] = utilIs[f];
+ }
+}
util.inherits(Transform, Duplex);
diff --git a/lib/_stream_writable.js b/lib/_stream_writable.js
index ba2e920..f49288b 100644
--- a/lib/_stream_writable.js
+++ b/lib/_stream_writable.js
@@ -27,6 +27,12 @@ module.exports = Writable;
Writable.WritableState = WritableState;
var util = require('util');
+if (!util.isUndefined) {
+ var utilIs = require('core-util-is');
+ for (var f in utilIs) {
+ util[f] = utilIs[f];
+ }
+}
var Stream = require('stream');
util.inherits(Writable, Stream);
@@ -119,7 +125,7 @@ function WritableState(options, stream) {
function Writable(options) {
// Writable ctor is applied to Duplexes, though they're not
// instanceof Writable, they're instanceof Readable.
- if (!(this instanceof Writable) && !(this instanceof Stream.Duplex))
+ if (!(this instanceof Writable) && !(this instanceof require('./_stream_duplex')))
return new Writable(options);
this._writableState = new WritableState(options, this);
diff --git a/test/simple/test-stream-big-push.js b/test/simple/test-stream-big-push.js
index e3787e4..8cd2127 100644
--- a/test/simple/test-stream-big-push.js
+++ b/test/simple/test-stream-big-push.js
@@ -21,7 +21,7 @@
var common = require('../common');
var assert = require('assert');
-var stream = require('stream');
+var stream = require('../../');
var str = 'asdfasdfasdfasdfasdf';
var r = new stream.Readable({
diff --git a/test/simple/test-stream-end-paused.js b/test/simple/test-stream-end-paused.js
index bb73777..d40efc7 100644
--- a/test/simple/test-stream-end-paused.js
+++ b/test/simple/test-stream-end-paused.js
@@ -25,7 +25,7 @@ var gotEnd = false;
// Make sure we don't miss the end event for paused 0-length streams
-var Readable = require('stream').Readable;
+var Readable = require('../../').Readable;
var stream = new Readable();
var calledRead = false;
stream._read = function() {
diff --git a/test/simple/test-stream-pipe-after-end.js b/test/simple/test-stream-pipe-after-end.js
index b46ee90..0be8366 100644
--- a/test/simple/test-stream-pipe-after-end.js
+++ b/test/simple/test-stream-pipe-after-end.js
@@ -22,8 +22,8 @@
var common = require('../common');
var assert = require('assert');
-var Readable = require('_stream_readable');
-var Writable = require('_stream_writable');
+var Readable = require('../../lib/_stream_readable');
+var Writable = require('../../lib/_stream_writable');
var util = require('util');
util.inherits(TestReadable, Readable);
diff --git a/test/simple/test-stream-pipe-cleanup.js b/test/simple/test-stream-pipe-cleanup.js
deleted file mode 100644
index f689358..0000000
--- a/test/simple/test-stream-pipe-cleanup.js
+++ /dev/null
@@ -1,122 +0,0 @@
-// Copyright Joyent, Inc. and other Node contributors.
-//
-// 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.
-
-// This test asserts that Stream.prototype.pipe does not leave listeners
-// hanging on the source or dest.
-
-var common = require('../common');
-var stream = require('stream');
-var assert = require('assert');
-var util = require('util');
-
-function Writable() {
- this.writable = true;
- this.endCalls = 0;
- stream.Stream.call(this);
-}
-util.inherits(Writable, stream.Stream);
-Writable.prototype.end = function() {
- this.endCalls++;
-};
-
-Writable.prototype.destroy = function() {
- this.endCalls++;
-};
-
-function Readable() {
- this.readable = true;
- stream.Stream.call(this);
-}
-util.inherits(Readable, stream.Stream);
-
-function Duplex() {
- this.readable = true;
- Writable.call(this);
-}
-util.inherits(Duplex, Writable);
-
-var i = 0;
-var limit = 100;
-
-var w = new Writable();
-
-var r;
-
-for (i = 0; i < limit; i++) {
- r = new Readable();
- r.pipe(w);
- r.emit('end');
-}
-assert.equal(0, r.listeners('end').length);
-assert.equal(limit, w.endCalls);
-
-w.endCalls = 0;
-
-for (i = 0; i < limit; i++) {
- r = new Readable();
- r.pipe(w);
- r.emit('close');
-}
-assert.equal(0, r.listeners('close').length);
-assert.equal(limit, w.endCalls);
-
-w.endCalls = 0;
-
-r = new Readable();
-
-for (i = 0; i < limit; i++) {
- w = new Writable();
- r.pipe(w);
- w.emit('close');
-}
-assert.equal(0, w.listeners('close').length);
-
-r = new Readable();
-w = new Writable();
-var d = new Duplex();
-r.pipe(d); // pipeline A
-d.pipe(w); // pipeline B
-assert.equal(r.listeners('end').length, 2); // A.onend, A.cleanup
-assert.equal(r.listeners('close').length, 2); // A.onclose, A.cleanup
-assert.equal(d.listeners('end').length, 2); // B.onend, B.cleanup
-assert.equal(d.listeners('close').length, 3); // A.cleanup, B.onclose, B.cleanup
-assert.equal(w.listeners('end').length, 0);
-assert.equal(w.listeners('close').length, 1); // B.cleanup
-
-r.emit('end');
-assert.equal(d.endCalls, 1);
-assert.equal(w.endCalls, 0);
-assert.equal(r.listeners('end').length, 0);
-assert.equal(r.listeners('close').length, 0);
-assert.equal(d.listeners('end').length, 2); // B.onend, B.cleanup
-assert.equal(d.listeners('close').length, 2); // B.onclose, B.cleanup
-assert.equal(w.listeners('end').length, 0);
-assert.equal(w.listeners('close').length, 1); // B.cleanup
-
-d.emit('end');
-assert.equal(d.endCalls, 1);
-assert.equal(w.endCalls, 1);
-assert.equal(r.listeners('end').length, 0);
-assert.equal(r.listeners('close').length, 0);
-assert.equal(d.listeners('end').length, 0);
-assert.equal(d.listeners('close').length, 0);
-assert.equal(w.listeners('end').length, 0);
-assert.equal(w.listeners('close').length, 0);
diff --git a/test/simple/test-stream-pipe-error-handling.js b/test/simple/test-stream-pipe-error-handling.js
index c5d724b..c7d6b7d 100644
--- a/test/simple/test-stream-pipe-error-handling.js
+++ b/test/simple/test-stream-pipe-error-handling.js
@@ -21,7 +21,7 @@
var common = require('../common');
var assert = require('assert');
-var Stream = require('stream').Stream;
+var Stream = require('../../').Stream;
(function testErrorListenerCatches() {
var source = new Stream();
diff --git a/test/simple/test-stream-pipe-event.js b/test/simple/test-stream-pipe-event.js
index cb9d5fe..56f8d61 100644
--- a/test/simple/test-stream-pipe-event.js
+++ b/test/simple/test-stream-pipe-event.js
@@ -20,7 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common');
-var stream = require('stream');
+var stream = require('../../');
var assert = require('assert');
var util = require('util');
diff --git a/test/simple/test-stream-push-order.js b/test/simple/test-stream-push-order.js
index f2e6ec2..a5c9bf9 100644
--- a/test/simple/test-stream-push-order.js
+++ b/test/simple/test-stream-push-order.js
@@ -20,7 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common.js');
-var Readable = require('stream').Readable;
+var Readable = require('../../').Readable;
var assert = require('assert');
var s = new Readable({
diff --git a/test/simple/test-stream-push-strings.js b/test/simple/test-stream-push-strings.js
index 06f43dc..1701a9a 100644
--- a/test/simple/test-stream-push-strings.js
+++ b/test/simple/test-stream-push-strings.js
@@ -22,7 +22,7 @@
var common = require('../common');
var assert = require('assert');
-var Readable = require('stream').Readable;
+var Readable = require('../../').Readable;
var util = require('util');
util.inherits(MyStream, Readable);
diff --git a/test/simple/test-stream-readable-event.js b/test/simple/test-stream-readable-event.js
index ba6a577..a8e6f7b 100644
--- a/test/simple/test-stream-readable-event.js
+++ b/test/simple/test-stream-readable-event.js
@@ -22,7 +22,7 @@
var common = require('../common');
var assert = require('assert');
-var Readable = require('stream').Readable;
+var Readable = require('../../').Readable;
(function first() {
// First test, not reading when the readable is added.
diff --git a/test/simple/test-stream-readable-flow-recursion.js b/test/simple/test-stream-readable-flow-recursion.js
index 2891ad6..11689ba 100644
--- a/test/simple/test-stream-readable-flow-recursion.js
+++ b/test/simple/test-stream-readable-flow-recursion.js
@@ -27,7 +27,7 @@ var assert = require('assert');
// more data continuously, but without triggering a nextTick
// warning or RangeError.
-var Readable = require('stream').Readable;
+var Readable = require('../../').Readable;
// throw an error if we trigger a nextTick warning.
process.throwDeprecation = true;
diff --git a/test/simple/test-stream-unshift-empty-chunk.js b/test/simple/test-stream-unshift-empty-chunk.js
index 0c96476..7827538 100644
--- a/test/simple/test-stream-unshift-empty-chunk.js
+++ b/test/simple/test-stream-unshift-empty-chunk.js
@@ -24,7 +24,7 @@ var assert = require('assert');
// This test verifies that stream.unshift(Buffer(0)) or
// stream.unshift('') does not set state.reading=false.
-var Readable = require('stream').Readable;
+var Readable = require('../../').Readable;
var r = new Readable();
var nChunks = 10;
diff --git a/test/simple/test-stream-unshift-read-race.js b/test/simple/test-stream-unshift-read-race.js
index 83fd9fa..17c18aa 100644
--- a/test/simple/test-stream-unshift-read-race.js
+++ b/test/simple/test-stream-unshift-read-race.js
@@ -29,7 +29,7 @@ var assert = require('assert');
// 3. push() after the EOF signaling null is an error.
// 4. _read() is not called after pushing the EOF null chunk.
-var stream = require('stream');
+var stream = require('../../');
var hwm = 10;
var r = stream.Readable({ highWaterMark: hwm });
var chunks = 10;
@@ -51,7 +51,14 @@ r._read = function(n) {
function push(fast) {
assert(!pushedNull, 'push() after null push');
- var c = pos >= data.length ? null : data.slice(pos, pos + n);
+ var c;
+ if (pos >= data.length)
+ c = null;
+ else {
+ if (n + pos > data.length)
+ n = data.length - pos;
+ c = data.slice(pos, pos + n);
+ }
pushedNull = c === null;
if (fast) {
pos += n;
diff --git a/test/simple/test-stream-writev.js b/test/simple/test-stream-writev.js
index 5b49e6e..b5321f3 100644
--- a/test/simple/test-stream-writev.js
+++ b/test/simple/test-stream-writev.js
@@ -22,7 +22,7 @@
var common = require('../common');
var assert = require('assert');
-var stream = require('stream');
+var stream = require('../../');
var queue = [];
for (var decode = 0; decode < 2; decode++) {
diff --git a/test/simple/test-stream2-basic.js b/test/simple/test-stream2-basic.js
index 3814bf0..248c1be 100644
--- a/test/simple/test-stream2-basic.js
+++ b/test/simple/test-stream2-basic.js
@@ -21,7 +21,7 @@
var common = require('../common.js');
-var R = require('_stream_readable');
+var R = require('../../lib/_stream_readable');
var assert = require('assert');
var util = require('util');
diff --git a/test/simple/test-stream2-compatibility.js b/test/simple/test-stream2-compatibility.js
index 6cdd4e9..f0fa84b 100644
--- a/test/simple/test-stream2-compatibility.js
+++ b/test/simple/test-stream2-compatibility.js
@@ -21,7 +21,7 @@
var common = require('../common.js');
-var R = require('_stream_readable');
+var R = require('../../lib/_stream_readable');
var assert = require('assert');
var util = require('util');
diff --git a/test/simple/test-stream2-finish-pipe.js b/test/simple/test-stream2-finish-pipe.js
index 39b274f..006a19b 100644
--- a/test/simple/test-stream2-finish-pipe.js
+++ b/test/simple/test-stream2-finish-pipe.js
@@ -20,7 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common.js');
-var stream = require('stream');
+var stream = require('../../');
var Buffer = require('buffer').Buffer;
var r = new stream.Readable();
diff --git a/test/simple/test-stream2-fs.js b/test/simple/test-stream2-fs.js
deleted file mode 100644
index e162406..0000000
--- a/test/simple/test-stream2-fs.js
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright Joyent, Inc. and other Node contributors.
-//
-// 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.
-
-
-var common = require('../common.js');
-var R = require('_stream_readable');
-var assert = require('assert');
-
-var fs = require('fs');
-var FSReadable = fs.ReadStream;
-
-var path = require('path');
-var file = path.resolve(common.fixturesDir, 'x1024.txt');
-
-var size = fs.statSync(file).size;
-
-var expectLengths = [1024];
-
-var util = require('util');
-var Stream = require('stream');
-
-util.inherits(TestWriter, Stream);
-
-function TestWriter() {
- Stream.apply(this);
- this.buffer = [];
- this.length = 0;
-}
-
-TestWriter.prototype.write = function(c) {
- this.buffer.push(c.toString());
- this.length += c.length;
- return true;
-};
-
-TestWriter.prototype.end = function(c) {
- if (c) this.buffer.push(c.toString());
- this.emit('results', this.buffer);
-}
-
-var r = new FSReadable(file);
-var w = new TestWriter();
-
-w.on('results', function(res) {
- console.error(res, w.length);
- assert.equal(w.length, size);
- var l = 0;
- assert.deepEqual(res.map(function (c) {
- return c.length;
- }), expectLengths);
- console.log('ok');
-});
-
-r.pipe(w);
diff --git a/test/simple/test-stream2-httpclient-response-end.js b/test/simple/test-stream2-httpclient-response-end.js
deleted file mode 100644
index 15cffc2..0000000
--- a/test/simple/test-stream2-httpclient-response-end.js
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright Joyent, Inc. and other Node contributors.
-//
-// 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.
-
-var common = require('../common.js');
-var assert = require('assert');
-var http = require('http');
-var msg = 'Hello';
-var readable_event = false;
-var end_event = false;
-var server = http.createServer(function(req, res) {
- res.writeHead(200, {'Content-Type': 'text/plain'});
- res.end(msg);
-}).listen(common.PORT, function() {
- http.get({port: common.PORT}, function(res) {
- var data = '';
- res.on('readable', function() {
- console.log('readable event');
- readable_event = true;
- data += res.read();
- });
- res.on('end', function() {
- console.log('end event');
- end_event = true;
- assert.strictEqual(msg, data);
- server.close();
- });
- });
-});
-
-process.on('exit', function() {
- assert(readable_event);
- assert(end_event);
-});
-
diff --git a/test/simple/test-stream2-large-read-stall.js b/test/simple/test-stream2-large-read-stall.js
index 2fbfbca..667985b 100644
--- a/test/simple/test-stream2-large-read-stall.js
+++ b/test/simple/test-stream2-large-read-stall.js
@@ -30,7 +30,7 @@ var PUSHSIZE = 20;
var PUSHCOUNT = 1000;
var HWM = 50;
-var Readable = require('stream').Readable;
+var Readable = require('../../').Readable;
var r = new Readable({
highWaterMark: HWM
});
@@ -39,23 +39,23 @@ var rs = r._readableState;
r._read = push;
r.on('readable', function() {
- console.error('>> readable');
+ //console.error('>> readable');
do {
- console.error(' > read(%d)', READSIZE);
+ //console.error(' > read(%d)', READSIZE);
var ret = r.read(READSIZE);
- console.error(' < %j (%d remain)', ret && ret.length, rs.length);
+ //console.error(' < %j (%d remain)', ret && ret.length, rs.length);
} while (ret && ret.length === READSIZE);
- console.error('<< after read()',
- ret && ret.length,
- rs.needReadable,
- rs.length);
+ //console.error('<< after read()',
+ // ret && ret.length,
+ // rs.needReadable,
+ // rs.length);
});
var endEmitted = false;
r.on('end', function() {
endEmitted = true;
- console.error('end');
+ //console.error('end');
});
var pushes = 0;
@@ -64,11 +64,11 @@ function push() {
return;
if (pushes++ === PUSHCOUNT) {
- console.error(' push(EOF)');
+ //console.error(' push(EOF)');
return r.push(null);
}
- console.error(' push #%d', pushes);
+ //console.error(' push #%d', pushes);
if (r.push(new Buffer(PUSHSIZE)))
setTimeout(push);
}
diff --git a/test/simple/test-stream2-objects.js b/test/simple/test-stream2-objects.js
index 3e6931d..ff47d89 100644
--- a/test/simple/test-stream2-objects.js
+++ b/test/simple/test-stream2-objects.js
@@ -21,8 +21,8 @@
var common = require('../common.js');
-var Readable = require('_stream_readable');
-var Writable = require('_stream_writable');
+var Readable = require('../../lib/_stream_readable');
+var Writable = require('../../lib/_stream_writable');
var assert = require('assert');
// tiny node-tap lookalike.
diff --git a/test/simple/test-stream2-pipe-error-handling.js b/test/simple/test-stream2-pipe-error-handling.js
index cf7531c..e3f3e4e 100644
--- a/test/simple/test-stream2-pipe-error-handling.js
+++ b/test/simple/test-stream2-pipe-error-handling.js
@@ -21,7 +21,7 @@
var common = require('../common');
var assert = require('assert');
-var stream = require('stream');
+var stream = require('../../');
(function testErrorListenerCatches() {
var count = 1000;
diff --git a/test/simple/test-stream2-pipe-error-once-listener.js b/test/simple/test-stream2-pipe-error-once-listener.js
index 5e8e3cb..53b2616 100755
--- a/test/simple/test-stream2-pipe-error-once-listener.js
+++ b/test/simple/test-stream2-pipe-error-once-listener.js
@@ -24,7 +24,7 @@ var common = require('../common.js');
var assert = require('assert');
var util = require('util');
-var stream = require('stream');
+var stream = require('../../');
var Read = function() {
diff --git a/test/simple/test-stream2-push.js b/test/simple/test-stream2-push.js
index b63edc3..eb2b0e9 100644
--- a/test/simple/test-stream2-push.js
+++ b/test/simple/test-stream2-push.js
@@ -20,7 +20,7 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common.js');
-var stream = require('stream');
+var stream = require('../../');
var Readable = stream.Readable;
var Writable = stream.Writable;
var assert = require('assert');
diff --git a/test/simple/test-stream2-read-sync-stack.js b/test/simple/test-stream2-read-sync-stack.js
index e8a7305..9740a47 100644
--- a/test/simple/test-stream2-read-sync-stack.js
+++ b/test/simple/test-stream2-read-sync-stack.js
@@ -21,7 +21,7 @@
var common = require('../common');
var assert = require('assert');
-var Readable = require('stream').Readable;
+var Readable = require('../../').Readable;
var r = new Readable();
var N = 256 * 1024;
diff --git a/test/simple/test-stream2-readable-empty-buffer-no-eof.js b/test/simple/test-stream2-readable-empty-buffer-no-eof.js
index cd30178..4b1659d 100644
--- a/test/simple/test-stream2-readable-empty-buffer-no-eof.js
+++ b/test/simple/test-stream2-readable-empty-buffer-no-eof.js
@@ -22,10 +22,9 @@
var common = require('../common');
var assert = require('assert');
-var Readable = require('stream').Readable;
+var Readable = require('../../').Readable;
test1();
-test2();
function test1() {
var r = new Readable();
@@ -88,31 +87,3 @@ function test1() {
console.log('ok');
});
}
-
-function test2() {
- var r = new Readable({ encoding: 'base64' });
- var reads = 5;
- r._read = function(n) {
- if (!reads--)
- return r.push(null); // EOF
- else
- return r.push(new Buffer('x'));
- };
-
- var results = [];
- function flow() {
- var chunk;
- while (null !== (chunk = r.read()))
- results.push(chunk + '');
- }
- r.on('readable', flow);
- r.on('end', function() {
- results.push('EOF');
- });
- flow();
-
- process.on('exit', function() {
- assert.deepEqual(results, [ 'eHh4', 'eHg=', 'EOF' ]);
- console.log('ok');
- });
-}
diff --git a/test/simple/test-stream2-readable-from-list.js b/test/simple/test-stream2-readable-from-list.js
index 7c96ffe..04a96f5 100644
--- a/test/simple/test-stream2-readable-from-list.js
+++ b/test/simple/test-stream2-readable-from-list.js
@@ -21,7 +21,7 @@
var assert = require('assert');
var common = require('../common.js');
-var fromList = require('_stream_readable')._fromList;
+var fromList = require('../../lib/_stream_readable')._fromList;
// tiny node-tap lookalike.
var tests = [];
diff --git a/test/simple/test-stream2-readable-legacy-drain.js b/test/simple/test-stream2-readable-legacy-drain.js
index 675da8e..51fd3d5 100644
--- a/test/simple/test-stream2-readable-legacy-drain.js
+++ b/test/simple/test-stream2-readable-legacy-drain.js
@@ -22,7 +22,7 @@
var common = require('../common');
var assert = require('assert');
-var Stream = require('stream');
+var Stream = require('../../');
var Readable = Stream.Readable;
var r = new Readable();
diff --git a/test/simple/test-stream2-readable-non-empty-end.js b/test/simple/test-stream2-readable-non-empty-end.js
index 7314ae7..c971898 100644
--- a/test/simple/test-stream2-readable-non-empty-end.js
+++ b/test/simple/test-stream2-readable-non-empty-end.js
@@ -21,7 +21,7 @@
var assert = require('assert');
var common = require('../common.js');
-var Readable = require('_stream_readable');
+var Readable = require('../../lib/_stream_readable');
var len = 0;
var chunks = new Array(10);
diff --git a/test/simple/test-stream2-readable-wrap-empty.js b/test/simple/test-stream2-readable-wrap-empty.js
index 2e5cf25..fd8a3dc 100644
--- a/test/simple/test-stream2-readable-wrap-empty.js
+++ b/test/simple/test-stream2-readable-wrap-empty.js
@@ -22,7 +22,7 @@
var common = require('../common');
var assert = require('assert');
-var Readable = require('_stream_readable');
+var Readable = require('../../lib/_stream_readable');
var EE = require('events').EventEmitter;
var oldStream = new EE();
diff --git a/test/simple/test-stream2-readable-wrap.js b/test/simple/test-stream2-readable-wrap.js
index 90eea01..6b177f7 100644
--- a/test/simple/test-stream2-readable-wrap.js
+++ b/test/simple/test-stream2-readable-wrap.js
@@ -22,8 +22,8 @@
var common = require('../common');
var assert = require('assert');
-var Readable = require('_stream_readable');
-var Writable = require('_stream_writable');
+var Readable = require('../../lib/_stream_readable');
+var Writable = require('../../lib/_stream_writable');
var EE = require('events').EventEmitter;
var testRuns = 0, completedRuns = 0;
diff --git a/test/simple/test-stream2-set-encoding.js b/test/simple/test-stream2-set-encoding.js
index 5d2c32a..685531b 100644
--- a/test/simple/test-stream2-set-encoding.js
+++ b/test/simple/test-stream2-set-encoding.js
@@ -22,7 +22,7 @@
var common = require('../common.js');
var assert = require('assert');
-var R = require('_stream_readable');
+var R = require('../../lib/_stream_readable');
var util = require('util');
// tiny node-tap lookalike.
diff --git a/test/simple/test-stream2-transform.js b/test/simple/test-stream2-transform.js
index 9c9ddd8..a0cacc6 100644
--- a/test/simple/test-stream2-transform.js
+++ b/test/simple/test-stream2-transform.js
@@ -21,8 +21,8 @@
var assert = require('assert');
var common = require('../common.js');
-var PassThrough = require('_stream_passthrough');
-var Transform = require('_stream_transform');
+var PassThrough = require('../../').PassThrough;
+var Transform = require('../../').Transform;
// tiny node-tap lookalike.
var tests = [];
diff --git a/test/simple/test-stream2-unpipe-drain.js b/test/simple/test-stream2-unpipe-drain.js
index d66dc3c..365b327 100644
--- a/test/simple/test-stream2-unpipe-drain.js
+++ b/test/simple/test-stream2-unpipe-drain.js
@@ -22,7 +22,7 @@
var common = require('../common.js');
var assert = require('assert');
-var stream = require('stream');
+var stream = require('../../');
var crypto = require('crypto');
var util = require('util');
diff --git a/test/simple/test-stream2-unpipe-leak.js b/test/simple/test-stream2-unpipe-leak.js
index 99f8746..17c92ae 100644
--- a/test/simple/test-stream2-unpipe-leak.js
+++ b/test/simple/test-stream2-unpipe-leak.js
@@ -22,7 +22,7 @@
var common = require('../common.js');
var assert = require('assert');
-var stream = require('stream');
+var stream = require('../../');
var chunk = new Buffer('hallo');
diff --git a/test/simple/test-stream2-writable.js b/test/simple/test-stream2-writable.js
index 704100c..209c3a6 100644
--- a/test/simple/test-stream2-writable.js
+++ b/test/simple/test-stream2-writable.js
@@ -20,8 +20,8 @@
// USE OR OTHER DEALINGS IN THE SOFTWARE.
var common = require('../common.js');
-var W = require('_stream_writable');
-var D = require('_stream_duplex');
+var W = require('../../').Writable;
+var D = require('../../').Duplex;
var assert = require('assert');
var util = require('util');
diff --git a/test/simple/test-stream3-pause-then-read.js b/test/simple/test-stream3-pause-then-read.js
index b91bde3..2f72c15 100644
--- a/test/simple/test-stream3-pause-then-read.js
+++ b/test/simple/test-stream3-pause-then-read.js
@@ -22,7 +22,7 @@
var common = require('../common');
var assert = require('assert');
-var stream = require('stream');
+var stream = require('../../');
var Readable = stream.Readable;
var Writable = stream.Writable;

View File

@@ -0,0 +1,89 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// 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.
// a duplex stream is just a stream that is both readable and writable.
// Since JS doesn't have multiple prototypal inheritance, this class
// prototypally inherits from Readable, and then parasitically from
// Writable.
module.exports = Duplex;
/*<replacement>*/
var objectKeys = Object.keys || function (obj) {
var keys = [];
for (var key in obj) keys.push(key);
return keys;
}
/*</replacement>*/
/*<replacement>*/
var util = require('core-util-is');
util.inherits = require('inherits');
/*</replacement>*/
var Readable = require('./_stream_readable');
var Writable = require('./_stream_writable');
util.inherits(Duplex, Readable);
forEach(objectKeys(Writable.prototype), function(method) {
if (!Duplex.prototype[method])
Duplex.prototype[method] = Writable.prototype[method];
});
function Duplex(options) {
if (!(this instanceof Duplex))
return new Duplex(options);
Readable.call(this, options);
Writable.call(this, options);
if (options && options.readable === false)
this.readable = false;
if (options && options.writable === false)
this.writable = false;
this.allowHalfOpen = true;
if (options && options.allowHalfOpen === false)
this.allowHalfOpen = false;
this.once('end', onend);
}
// the no-half-open enforcer
function onend() {
// if we allow half-open state, or if the writable side ended,
// then we're ok.
if (this.allowHalfOpen || this._writableState.ended)
return;
// no more data can be written.
// But allow more writes to happen in this tick.
process.nextTick(this.end.bind(this));
}
function forEach (xs, f) {
for (var i = 0, l = xs.length; i < l; i++) {
f(xs[i], i);
}
}

View File

@@ -0,0 +1,46 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// 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.
// a passthrough stream.
// basically just the most minimal sort of Transform stream.
// Every written chunk gets output as-is.
module.exports = PassThrough;
var Transform = require('./_stream_transform');
/*<replacement>*/
var util = require('core-util-is');
util.inherits = require('inherits');
/*</replacement>*/
util.inherits(PassThrough, Transform);
function PassThrough(options) {
if (!(this instanceof PassThrough))
return new PassThrough(options);
Transform.call(this, options);
}
PassThrough.prototype._transform = function(chunk, encoding, cb) {
cb(null, chunk);
};

View File

@@ -0,0 +1,951 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// 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.
module.exports = Readable;
/*<replacement>*/
var isArray = require('isarray');
/*</replacement>*/
/*<replacement>*/
var Buffer = require('buffer').Buffer;
/*</replacement>*/
Readable.ReadableState = ReadableState;
var EE = require('events').EventEmitter;
/*<replacement>*/
if (!EE.listenerCount) EE.listenerCount = function(emitter, type) {
return emitter.listeners(type).length;
};
/*</replacement>*/
var Stream = require('stream');
/*<replacement>*/
var util = require('core-util-is');
util.inherits = require('inherits');
/*</replacement>*/
var StringDecoder;
/*<replacement>*/
var debug = require('util');
if (debug && debug.debuglog) {
debug = debug.debuglog('stream');
} else {
debug = function () {};
}
/*</replacement>*/
util.inherits(Readable, Stream);
function ReadableState(options, stream) {
var Duplex = require('./_stream_duplex');
options = options || {};
// the point at which it stops calling _read() to fill the buffer
// Note: 0 is a valid value, means "don't call _read preemptively ever"
var hwm = options.highWaterMark;
var defaultHwm = options.objectMode ? 16 : 16 * 1024;
this.highWaterMark = (hwm || hwm === 0) ? hwm : defaultHwm;
// cast to ints.
this.highWaterMark = ~~this.highWaterMark;
this.buffer = [];
this.length = 0;
this.pipes = null;
this.pipesCount = 0;
this.flowing = null;
this.ended = false;
this.endEmitted = false;
this.reading = false;
// a flag to be able to tell if the onwrite cb is called immediately,
// or on a later tick. We set this to true at first, because any
// actions that shouldn't happen until "later" should generally also
// not happen before the first write call.
this.sync = true;
// whenever we return null, then we set a flag to say
// that we're awaiting a 'readable' event emission.
this.needReadable = false;
this.emittedReadable = false;
this.readableListening = false;
// object stream flag. Used to make read(n) ignore n and to
// make all the buffer merging and length checks go away
this.objectMode = !!options.objectMode;
if (stream instanceof Duplex)
this.objectMode = this.objectMode || !!options.readableObjectMode;
// Crypto is kind of old and crusty. Historically, its default string
// encoding is 'binary' so we have to make this configurable.
// Everything else in the universe uses 'utf8', though.
this.defaultEncoding = options.defaultEncoding || 'utf8';
// when piping, we only care about 'readable' events that happen
// after read()ing all the bytes and not getting any pushback.
this.ranOut = false;
// the number of writers that are awaiting a drain event in .pipe()s
this.awaitDrain = 0;
// if true, a maybeReadMore has been scheduled
this.readingMore = false;
this.decoder = null;
this.encoding = null;
if (options.encoding) {
if (!StringDecoder)
StringDecoder = require('string_decoder/').StringDecoder;
this.decoder = new StringDecoder(options.encoding);
this.encoding = options.encoding;
}
}
function Readable(options) {
var Duplex = require('./_stream_duplex');
if (!(this instanceof Readable))
return new Readable(options);
this._readableState = new ReadableState(options, this);
// legacy
this.readable = true;
Stream.call(this);
}
// Manually shove something into the read() buffer.
// This returns true if the highWaterMark has not been hit yet,
// similar to how Writable.write() returns true if you should
// write() some more.
Readable.prototype.push = function(chunk, encoding) {
var state = this._readableState;
if (util.isString(chunk) && !state.objectMode) {
encoding = encoding || state.defaultEncoding;
if (encoding !== state.encoding) {
chunk = new Buffer(chunk, encoding);
encoding = '';
}
}
return readableAddChunk(this, state, chunk, encoding, false);
};
// Unshift should *always* be something directly out of read()
Readable.prototype.unshift = function(chunk) {
var state = this._readableState;
return readableAddChunk(this, state, chunk, '', true);
};
function readableAddChunk(stream, state, chunk, encoding, addToFront) {
var er = chunkInvalid(state, chunk);
if (er) {
stream.emit('error', er);
} else if (util.isNullOrUndefined(chunk)) {
state.reading = false;
if (!state.ended)
onEofChunk(stream, state);
} else if (state.objectMode || chunk && chunk.length > 0) {
if (state.ended && !addToFront) {
var e = new Error('stream.push() after EOF');
stream.emit('error', e);
} else if (state.endEmitted && addToFront) {
var e = new Error('stream.unshift() after end event');
stream.emit('error', e);
} else {
if (state.decoder && !addToFront && !encoding)
chunk = state.decoder.write(chunk);
if (!addToFront)
state.reading = false;
// if we want the data now, just emit it.
if (state.flowing && state.length === 0 && !state.sync) {
stream.emit('data', chunk);
stream.read(0);
} else {
// update the buffer info.
state.length += state.objectMode ? 1 : chunk.length;
if (addToFront)
state.buffer.unshift(chunk);
else
state.buffer.push(chunk);
if (state.needReadable)
emitReadable(stream);
}
maybeReadMore(stream, state);
}
} else if (!addToFront) {
state.reading = false;
}
return needMoreData(state);
}
// if it's past the high water mark, we can push in some more.
// Also, if we have no data yet, we can stand some
// more bytes. This is to work around cases where hwm=0,
// such as the repl. Also, if the push() triggered a
// readable event, and the user called read(largeNumber) such that
// needReadable was set, then we ought to push more, so that another
// 'readable' event will be triggered.
function needMoreData(state) {
return !state.ended &&
(state.needReadable ||
state.length < state.highWaterMark ||
state.length === 0);
}
// backwards compatibility.
Readable.prototype.setEncoding = function(enc) {
if (!StringDecoder)
StringDecoder = require('string_decoder/').StringDecoder;
this._readableState.decoder = new StringDecoder(enc);
this._readableState.encoding = enc;
return this;
};
// Don't raise the hwm > 128MB
var MAX_HWM = 0x800000;
function roundUpToNextPowerOf2(n) {
if (n >= MAX_HWM) {
n = MAX_HWM;
} else {
// Get the next highest power of 2
n--;
for (var p = 1; p < 32; p <<= 1) n |= n >> p;
n++;
}
return n;
}
function howMuchToRead(n, state) {
if (state.length === 0 && state.ended)
return 0;
if (state.objectMode)
return n === 0 ? 0 : 1;
if (isNaN(n) || util.isNull(n)) {
// only flow one buffer at a time
if (state.flowing && state.buffer.length)
return state.buffer[0].length;
else
return state.length;
}
if (n <= 0)
return 0;
// If we're asking for more than the target buffer level,
// then raise the water mark. Bump up to the next highest
// power of 2, to prevent increasing it excessively in tiny
// amounts.
if (n > state.highWaterMark)
state.highWaterMark = roundUpToNextPowerOf2(n);
// don't have that much. return null, unless we've ended.
if (n > state.length) {
if (!state.ended) {
state.needReadable = true;
return 0;
} else
return state.length;
}
return n;
}
// you can override either this method, or the async _read(n) below.
Readable.prototype.read = function(n) {
debug('read', n);
var state = this._readableState;
var nOrig = n;
if (!util.isNumber(n) || n > 0)
state.emittedReadable = false;
// if we're doing read(0) to trigger a readable event, but we
// already have a bunch of data in the buffer, then just trigger
// the 'readable' event and move on.
if (n === 0 &&
state.needReadable &&
(state.length >= state.highWaterMark || state.ended)) {
debug('read: emitReadable', state.length, state.ended);
if (state.length === 0 && state.ended)
endReadable(this);
else
emitReadable(this);
return null;
}
n = howMuchToRead(n, state);
// if we've ended, and we're now clear, then finish it up.
if (n === 0 && state.ended) {
if (state.length === 0)
endReadable(this);
return null;
}
// All the actual chunk generation logic needs to be
// *below* the call to _read. The reason is that in certain
// synthetic stream cases, such as passthrough streams, _read
// may be a completely synchronous operation which may change
// the state of the read buffer, providing enough data when
// before there was *not* enough.
//
// So, the steps are:
// 1. Figure out what the state of things will be after we do
// a read from the buffer.
//
// 2. If that resulting state will trigger a _read, then call _read.
// Note that this may be asynchronous, or synchronous. Yes, it is
// deeply ugly to write APIs this way, but that still doesn't mean
// that the Readable class should behave improperly, as streams are
// designed to be sync/async agnostic.
// Take note if the _read call is sync or async (ie, if the read call
// has returned yet), so that we know whether or not it's safe to emit
// 'readable' etc.
//
// 3. Actually pull the requested chunks out of the buffer and return.
// if we need a readable event, then we need to do some reading.
var doRead = state.needReadable;
debug('need readable', doRead);
// if we currently have less than the highWaterMark, then also read some
if (state.length === 0 || state.length - n < state.highWaterMark) {
doRead = true;
debug('length less than watermark', doRead);
}
// however, if we've ended, then there's no point, and if we're already
// reading, then it's unnecessary.
if (state.ended || state.reading) {
doRead = false;
debug('reading or ended', doRead);
}
if (doRead) {
debug('do read');
state.reading = true;
state.sync = true;
// if the length is currently zero, then we *need* a readable event.
if (state.length === 0)
state.needReadable = true;
// call internal read method
this._read(state.highWaterMark);
state.sync = false;
}
// If _read pushed data synchronously, then `reading` will be false,
// and we need to re-evaluate how much data we can return to the user.
if (doRead && !state.reading)
n = howMuchToRead(nOrig, state);
var ret;
if (n > 0)
ret = fromList(n, state);
else
ret = null;
if (util.isNull(ret)) {
state.needReadable = true;
n = 0;
}
state.length -= n;
// If we have nothing in the buffer, then we want to know
// as soon as we *do* get something into the buffer.
if (state.length === 0 && !state.ended)
state.needReadable = true;
// If we tried to read() past the EOF, then emit end on the next tick.
if (nOrig !== n && state.ended && state.length === 0)
endReadable(this);
if (!util.isNull(ret))
this.emit('data', ret);
return ret;
};
function chunkInvalid(state, chunk) {
var er = null;
if (!util.isBuffer(chunk) &&
!util.isString(chunk) &&
!util.isNullOrUndefined(chunk) &&
!state.objectMode) {
er = new TypeError('Invalid non-string/buffer chunk');
}
return er;
}
function onEofChunk(stream, state) {
if (state.decoder && !state.ended) {
var chunk = state.decoder.end();
if (chunk && chunk.length) {
state.buffer.push(chunk);
state.length += state.objectMode ? 1 : chunk.length;
}
}
state.ended = true;
// emit 'readable' now to make sure it gets picked up.
emitReadable(stream);
}
// Don't emit readable right away in sync mode, because this can trigger
// another read() call => stack overflow. This way, it might trigger
// a nextTick recursion warning, but that's not so bad.
function emitReadable(stream) {
var state = stream._readableState;
state.needReadable = false;
if (!state.emittedReadable) {
debug('emitReadable', state.flowing);
state.emittedReadable = true;
if (state.sync)
process.nextTick(function() {
emitReadable_(stream);
});
else
emitReadable_(stream);
}
}
function emitReadable_(stream) {
debug('emit readable');
stream.emit('readable');
flow(stream);
}
// at this point, the user has presumably seen the 'readable' event,
// and called read() to consume some data. that may have triggered
// in turn another _read(n) call, in which case reading = true if
// it's in progress.
// However, if we're not ended, or reading, and the length < hwm,
// then go ahead and try to read some more preemptively.
function maybeReadMore(stream, state) {
if (!state.readingMore) {
state.readingMore = true;
process.nextTick(function() {
maybeReadMore_(stream, state);
});
}
}
function maybeReadMore_(stream, state) {
var len = state.length;
while (!state.reading && !state.flowing && !state.ended &&
state.length < state.highWaterMark) {
debug('maybeReadMore read 0');
stream.read(0);
if (len === state.length)
// didn't get any data, stop spinning.
break;
else
len = state.length;
}
state.readingMore = false;
}
// abstract method. to be overridden in specific implementation classes.
// call cb(er, data) where data is <= n in length.
// for virtual (non-string, non-buffer) streams, "length" is somewhat
// arbitrary, and perhaps not very meaningful.
Readable.prototype._read = function(n) {
this.emit('error', new Error('not implemented'));
};
Readable.prototype.pipe = function(dest, pipeOpts) {
var src = this;
var state = this._readableState;
switch (state.pipesCount) {
case 0:
state.pipes = dest;
break;
case 1:
state.pipes = [state.pipes, dest];
break;
default:
state.pipes.push(dest);
break;
}
state.pipesCount += 1;
debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts);
var doEnd = (!pipeOpts || pipeOpts.end !== false) &&
dest !== process.stdout &&
dest !== process.stderr;
var endFn = doEnd ? onend : cleanup;
if (state.endEmitted)
process.nextTick(endFn);
else
src.once('end', endFn);
dest.on('unpipe', onunpipe);
function onunpipe(readable) {
debug('onunpipe');
if (readable === src) {
cleanup();
}
}
function onend() {
debug('onend');
dest.end();
}
// when the dest drains, it reduces the awaitDrain counter
// on the source. This would be more elegant with a .once()
// handler in flow(), but adding and removing repeatedly is
// too slow.
var ondrain = pipeOnDrain(src);
dest.on('drain', ondrain);
function cleanup() {
debug('cleanup');
// cleanup event handlers once the pipe is broken
dest.removeListener('close', onclose);
dest.removeListener('finish', onfinish);
dest.removeListener('drain', ondrain);
dest.removeListener('error', onerror);
dest.removeListener('unpipe', onunpipe);
src.removeListener('end', onend);
src.removeListener('end', cleanup);
src.removeListener('data', ondata);
// if the reader is waiting for a drain event from this
// specific writer, then it would cause it to never start
// flowing again.
// So, if this is awaiting a drain, then we just call it now.
// If we don't know, then assume that we are waiting for one.
if (state.awaitDrain &&
(!dest._writableState || dest._writableState.needDrain))
ondrain();
}
src.on('data', ondata);
function ondata(chunk) {
debug('ondata');
var ret = dest.write(chunk);
if (false === ret) {
debug('false write response, pause',
src._readableState.awaitDrain);
src._readableState.awaitDrain++;
src.pause();
}
}
// if the dest has an error, then stop piping into it.
// however, don't suppress the throwing behavior for this.
function onerror(er) {
debug('onerror', er);
unpipe();
dest.removeListener('error', onerror);
if (EE.listenerCount(dest, 'error') === 0)
dest.emit('error', er);
}
// This is a brutally ugly hack to make sure that our error handler
// is attached before any userland ones. NEVER DO THIS.
if (!dest._events || !dest._events.error)
dest.on('error', onerror);
else if (isArray(dest._events.error))
dest._events.error.unshift(onerror);
else
dest._events.error = [onerror, dest._events.error];
// Both close and finish should trigger unpipe, but only once.
function onclose() {
dest.removeListener('finish', onfinish);
unpipe();
}
dest.once('close', onclose);
function onfinish() {
debug('onfinish');
dest.removeListener('close', onclose);
unpipe();
}
dest.once('finish', onfinish);
function unpipe() {
debug('unpipe');
src.unpipe(dest);
}
// tell the dest that it's being piped to
dest.emit('pipe', src);
// start the flow if it hasn't been started already.
if (!state.flowing) {
debug('pipe resume');
src.resume();
}
return dest;
};
function pipeOnDrain(src) {
return function() {
var state = src._readableState;
debug('pipeOnDrain', state.awaitDrain);
if (state.awaitDrain)
state.awaitDrain--;
if (state.awaitDrain === 0 && EE.listenerCount(src, 'data')) {
state.flowing = true;
flow(src);
}
};
}
Readable.prototype.unpipe = function(dest) {
var state = this._readableState;
// if we're not piping anywhere, then do nothing.
if (state.pipesCount === 0)
return this;
// just one destination. most common case.
if (state.pipesCount === 1) {
// passed in one, but it's not the right one.
if (dest && dest !== state.pipes)
return this;
if (!dest)
dest = state.pipes;
// got a match.
state.pipes = null;
state.pipesCount = 0;
state.flowing = false;
if (dest)
dest.emit('unpipe', this);
return this;
}
// slow case. multiple pipe destinations.
if (!dest) {
// remove all.
var dests = state.pipes;
var len = state.pipesCount;
state.pipes = null;
state.pipesCount = 0;
state.flowing = false;
for (var i = 0; i < len; i++)
dests[i].emit('unpipe', this);
return this;
}
// try to find the right one.
var i = indexOf(state.pipes, dest);
if (i === -1)
return this;
state.pipes.splice(i, 1);
state.pipesCount -= 1;
if (state.pipesCount === 1)
state.pipes = state.pipes[0];
dest.emit('unpipe', this);
return this;
};
// set up data events if they are asked for
// Ensure readable listeners eventually get something
Readable.prototype.on = function(ev, fn) {
var res = Stream.prototype.on.call(this, ev, fn);
// If listening to data, and it has not explicitly been paused,
// then call resume to start the flow of data on the next tick.
if (ev === 'data' && false !== this._readableState.flowing) {
this.resume();
}
if (ev === 'readable' && this.readable) {
var state = this._readableState;
if (!state.readableListening) {
state.readableListening = true;
state.emittedReadable = false;
state.needReadable = true;
if (!state.reading) {
var self = this;
process.nextTick(function() {
debug('readable nexttick read 0');
self.read(0);
});
} else if (state.length) {
emitReadable(this, state);
}
}
}
return res;
};
Readable.prototype.addListener = Readable.prototype.on;
// pause() and resume() are remnants of the legacy readable stream API
// If the user uses them, then switch into old mode.
Readable.prototype.resume = function() {
var state = this._readableState;
if (!state.flowing) {
debug('resume');
state.flowing = true;
if (!state.reading) {
debug('resume read 0');
this.read(0);
}
resume(this, state);
}
return this;
};
function resume(stream, state) {
if (!state.resumeScheduled) {
state.resumeScheduled = true;
process.nextTick(function() {
resume_(stream, state);
});
}
}
function resume_(stream, state) {
state.resumeScheduled = false;
stream.emit('resume');
flow(stream);
if (state.flowing && !state.reading)
stream.read(0);
}
Readable.prototype.pause = function() {
debug('call pause flowing=%j', this._readableState.flowing);
if (false !== this._readableState.flowing) {
debug('pause');
this._readableState.flowing = false;
this.emit('pause');
}
return this;
};
function flow(stream) {
var state = stream._readableState;
debug('flow', state.flowing);
if (state.flowing) {
do {
var chunk = stream.read();
} while (null !== chunk && state.flowing);
}
}
// wrap an old-style stream as the async data source.
// This is *not* part of the readable stream interface.
// It is an ugly unfortunate mess of history.
Readable.prototype.wrap = function(stream) {
var state = this._readableState;
var paused = false;
var self = this;
stream.on('end', function() {
debug('wrapped end');
if (state.decoder && !state.ended) {
var chunk = state.decoder.end();
if (chunk && chunk.length)
self.push(chunk);
}
self.push(null);
});
stream.on('data', function(chunk) {
debug('wrapped data');
if (state.decoder)
chunk = state.decoder.write(chunk);
if (!chunk || !state.objectMode && !chunk.length)
return;
var ret = self.push(chunk);
if (!ret) {
paused = true;
stream.pause();
}
});
// proxy all the other methods.
// important when wrapping filters and duplexes.
for (var i in stream) {
if (util.isFunction(stream[i]) && util.isUndefined(this[i])) {
this[i] = function(method) { return function() {
return stream[method].apply(stream, arguments);
}}(i);
}
}
// proxy certain important events.
var events = ['error', 'close', 'destroy', 'pause', 'resume'];
forEach(events, function(ev) {
stream.on(ev, self.emit.bind(self, ev));
});
// when we try to consume some more bytes, simply unpause the
// underlying stream.
self._read = function(n) {
debug('wrapped _read', n);
if (paused) {
paused = false;
stream.resume();
}
};
return self;
};
// exposed for testing purposes only.
Readable._fromList = fromList;
// Pluck off n bytes from an array of buffers.
// Length is the combined lengths of all the buffers in the list.
function fromList(n, state) {
var list = state.buffer;
var length = state.length;
var stringMode = !!state.decoder;
var objectMode = !!state.objectMode;
var ret;
// nothing in the list, definitely empty.
if (list.length === 0)
return null;
if (length === 0)
ret = null;
else if (objectMode)
ret = list.shift();
else if (!n || n >= length) {
// read it all, truncate the array.
if (stringMode)
ret = list.join('');
else
ret = Buffer.concat(list, length);
list.length = 0;
} else {
// read just some of it.
if (n < list[0].length) {
// just take a part of the first list item.
// slice is the same for buffers and strings.
var buf = list[0];
ret = buf.slice(0, n);
list[0] = buf.slice(n);
} else if (n === list[0].length) {
// first list is a perfect match
ret = list.shift();
} else {
// complex case.
// we have enough to cover it, but it spans past the first buffer.
if (stringMode)
ret = '';
else
ret = new Buffer(n);
var c = 0;
for (var i = 0, l = list.length; i < l && c < n; i++) {
var buf = list[0];
var cpy = Math.min(n - c, buf.length);
if (stringMode)
ret += buf.slice(0, cpy);
else
buf.copy(ret, c, 0, cpy);
if (cpy < buf.length)
list[0] = buf.slice(cpy);
else
list.shift();
c += cpy;
}
}
}
return ret;
}
function endReadable(stream) {
var state = stream._readableState;
// If we get here before consuming all the bytes, then that is a
// bug in node. Should never happen.
if (state.length > 0)
throw new Error('endReadable called on non-empty stream');
if (!state.endEmitted) {
state.ended = true;
process.nextTick(function() {
// Check that we didn't get one last unshift.
if (!state.endEmitted && state.length === 0) {
state.endEmitted = true;
stream.readable = false;
stream.emit('end');
}
});
}
}
function forEach (xs, f) {
for (var i = 0, l = xs.length; i < l; i++) {
f(xs[i], i);
}
}
function indexOf (xs, x) {
for (var i = 0, l = xs.length; i < l; i++) {
if (xs[i] === x) return i;
}
return -1;
}

View File

@@ -0,0 +1,209 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// 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.
// a transform stream is a readable/writable stream where you do
// something with the data. Sometimes it's called a "filter",
// but that's not a great name for it, since that implies a thing where
// some bits pass through, and others are simply ignored. (That would
// be a valid example of a transform, of course.)
//
// While the output is causally related to the input, it's not a
// necessarily symmetric or synchronous transformation. For example,
// a zlib stream might take multiple plain-text writes(), and then
// emit a single compressed chunk some time in the future.
//
// Here's how this works:
//
// The Transform stream has all the aspects of the readable and writable
// stream classes. When you write(chunk), that calls _write(chunk,cb)
// internally, and returns false if there's a lot of pending writes
// buffered up. When you call read(), that calls _read(n) until
// there's enough pending readable data buffered up.
//
// In a transform stream, the written data is placed in a buffer. When
// _read(n) is called, it transforms the queued up data, calling the
// buffered _write cb's as it consumes chunks. If consuming a single
// written chunk would result in multiple output chunks, then the first
// outputted bit calls the readcb, and subsequent chunks just go into
// the read buffer, and will cause it to emit 'readable' if necessary.
//
// This way, back-pressure is actually determined by the reading side,
// since _read has to be called to start processing a new chunk. However,
// a pathological inflate type of transform can cause excessive buffering
// here. For example, imagine a stream where every byte of input is
// interpreted as an integer from 0-255, and then results in that many
// bytes of output. Writing the 4 bytes {ff,ff,ff,ff} would result in
// 1kb of data being output. In this case, you could write a very small
// amount of input, and end up with a very large amount of output. In
// such a pathological inflating mechanism, there'd be no way to tell
// the system to stop doing the transform. A single 4MB write could
// cause the system to run out of memory.
//
// However, even in such a pathological case, only a single written chunk
// would be consumed, and then the rest would wait (un-transformed) until
// the results of the previous transformed chunk were consumed.
module.exports = Transform;
var Duplex = require('./_stream_duplex');
/*<replacement>*/
var util = require('core-util-is');
util.inherits = require('inherits');
/*</replacement>*/
util.inherits(Transform, Duplex);
function TransformState(options, stream) {
this.afterTransform = function(er, data) {
return afterTransform(stream, er, data);
};
this.needTransform = false;
this.transforming = false;
this.writecb = null;
this.writechunk = null;
}
function afterTransform(stream, er, data) {
var ts = stream._transformState;
ts.transforming = false;
var cb = ts.writecb;
if (!cb)
return stream.emit('error', new Error('no writecb in Transform class'));
ts.writechunk = null;
ts.writecb = null;
if (!util.isNullOrUndefined(data))
stream.push(data);
if (cb)
cb(er);
var rs = stream._readableState;
rs.reading = false;
if (rs.needReadable || rs.length < rs.highWaterMark) {
stream._read(rs.highWaterMark);
}
}
function Transform(options) {
if (!(this instanceof Transform))
return new Transform(options);
Duplex.call(this, options);
this._transformState = new TransformState(options, this);
// when the writable side finishes, then flush out anything remaining.
var stream = this;
// start out asking for a readable event once data is transformed.
this._readableState.needReadable = true;
// we have implemented the _read method, and done the other things
// that Readable wants before the first _read call, so unset the
// sync guard flag.
this._readableState.sync = false;
this.once('prefinish', function() {
if (util.isFunction(this._flush))
this._flush(function(er) {
done(stream, er);
});
else
done(stream);
});
}
Transform.prototype.push = function(chunk, encoding) {
this._transformState.needTransform = false;
return Duplex.prototype.push.call(this, chunk, encoding);
};
// This is the part where you do stuff!
// override this function in implementation classes.
// 'chunk' is an input chunk.
//
// Call `push(newChunk)` to pass along transformed output
// to the readable side. You may call 'push' zero or more times.
//
// Call `cb(err)` when you are done with this chunk. If you pass
// an error, then that'll put the hurt on the whole operation. If you
// never call cb(), then you'll never get another chunk.
Transform.prototype._transform = function(chunk, encoding, cb) {
throw new Error('not implemented');
};
Transform.prototype._write = function(chunk, encoding, cb) {
var ts = this._transformState;
ts.writecb = cb;
ts.writechunk = chunk;
ts.writeencoding = encoding;
if (!ts.transforming) {
var rs = this._readableState;
if (ts.needTransform ||
rs.needReadable ||
rs.length < rs.highWaterMark)
this._read(rs.highWaterMark);
}
};
// Doesn't matter what the args are here.
// _transform does all the work.
// That we got here means that the readable side wants more data.
Transform.prototype._read = function(n) {
var ts = this._transformState;
if (!util.isNull(ts.writechunk) && ts.writecb && !ts.transforming) {
ts.transforming = true;
this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform);
} else {
// mark that we need a transform, so that any data that comes in
// will get processed, now that we've asked for it.
ts.needTransform = true;
}
};
function done(stream, er) {
if (er)
return stream.emit('error', er);
// if there's nothing in the write buffer, then that means
// that nothing more will ever be provided
var ws = stream._writableState;
var ts = stream._transformState;
if (ws.length)
throw new Error('calling transform done when ws.length != 0');
if (ts.transforming)
throw new Error('calling transform done when still transforming');
return stream.push(null);
}

View File

@@ -0,0 +1,477 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// 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.
// A bit simpler than readable streams.
// Implement an async ._write(chunk, cb), and it'll handle all
// the drain event emission and buffering.
module.exports = Writable;
/*<replacement>*/
var Buffer = require('buffer').Buffer;
/*</replacement>*/
Writable.WritableState = WritableState;
/*<replacement>*/
var util = require('core-util-is');
util.inherits = require('inherits');
/*</replacement>*/
var Stream = require('stream');
util.inherits(Writable, Stream);
function WriteReq(chunk, encoding, cb) {
this.chunk = chunk;
this.encoding = encoding;
this.callback = cb;
}
function WritableState(options, stream) {
var Duplex = require('./_stream_duplex');
options = options || {};
// the point at which write() starts returning false
// Note: 0 is a valid value, means that we always return false if
// the entire buffer is not flushed immediately on write()
var hwm = options.highWaterMark;
var defaultHwm = options.objectMode ? 16 : 16 * 1024;
this.highWaterMark = (hwm || hwm === 0) ? hwm : defaultHwm;
// object stream flag to indicate whether or not this stream
// contains buffers or objects.
this.objectMode = !!options.objectMode;
if (stream instanceof Duplex)
this.objectMode = this.objectMode || !!options.writableObjectMode;
// cast to ints.
this.highWaterMark = ~~this.highWaterMark;
this.needDrain = false;
// at the start of calling end()
this.ending = false;
// when end() has been called, and returned
this.ended = false;
// when 'finish' is emitted
this.finished = false;
// should we decode strings into buffers before passing to _write?
// this is here so that some node-core streams can optimize string
// handling at a lower level.
var noDecode = options.decodeStrings === false;
this.decodeStrings = !noDecode;
// Crypto is kind of old and crusty. Historically, its default string
// encoding is 'binary' so we have to make this configurable.
// Everything else in the universe uses 'utf8', though.
this.defaultEncoding = options.defaultEncoding || 'utf8';
// not an actual buffer we keep track of, but a measurement
// of how much we're waiting to get pushed to some underlying
// socket or file.
this.length = 0;
// a flag to see when we're in the middle of a write.
this.writing = false;
// when true all writes will be buffered until .uncork() call
this.corked = 0;
// a flag to be able to tell if the onwrite cb is called immediately,
// or on a later tick. We set this to true at first, because any
// actions that shouldn't happen until "later" should generally also
// not happen before the first write call.
this.sync = true;
// a flag to know if we're processing previously buffered items, which
// may call the _write() callback in the same tick, so that we don't
// end up in an overlapped onwrite situation.
this.bufferProcessing = false;
// the callback that's passed to _write(chunk,cb)
this.onwrite = function(er) {
onwrite(stream, er);
};
// the callback that the user supplies to write(chunk,encoding,cb)
this.writecb = null;
// the amount that is being written when _write is called.
this.writelen = 0;
this.buffer = [];
// number of pending user-supplied write callbacks
// this must be 0 before 'finish' can be emitted
this.pendingcb = 0;
// emit prefinish if the only thing we're waiting for is _write cbs
// This is relevant for synchronous Transform streams
this.prefinished = false;
// True if the error was already emitted and should not be thrown again
this.errorEmitted = false;
}
function Writable(options) {
var Duplex = require('./_stream_duplex');
// Writable ctor is applied to Duplexes, though they're not
// instanceof Writable, they're instanceof Readable.
if (!(this instanceof Writable) && !(this instanceof Duplex))
return new Writable(options);
this._writableState = new WritableState(options, this);
// legacy.
this.writable = true;
Stream.call(this);
}
// Otherwise people can pipe Writable streams, which is just wrong.
Writable.prototype.pipe = function() {
this.emit('error', new Error('Cannot pipe. Not readable.'));
};
function writeAfterEnd(stream, state, cb) {
var er = new Error('write after end');
// TODO: defer error events consistently everywhere, not just the cb
stream.emit('error', er);
process.nextTick(function() {
cb(er);
});
}
// If we get something that is not a buffer, string, null, or undefined,
// and we're not in objectMode, then that's an error.
// Otherwise stream chunks are all considered to be of length=1, and the
// watermarks determine how many objects to keep in the buffer, rather than
// how many bytes or characters.
function validChunk(stream, state, chunk, cb) {
var valid = true;
if (!util.isBuffer(chunk) &&
!util.isString(chunk) &&
!util.isNullOrUndefined(chunk) &&
!state.objectMode) {
var er = new TypeError('Invalid non-string/buffer chunk');
stream.emit('error', er);
process.nextTick(function() {
cb(er);
});
valid = false;
}
return valid;
}
Writable.prototype.write = function(chunk, encoding, cb) {
var state = this._writableState;
var ret = false;
if (util.isFunction(encoding)) {
cb = encoding;
encoding = null;
}
if (util.isBuffer(chunk))
encoding = 'buffer';
else if (!encoding)
encoding = state.defaultEncoding;
if (!util.isFunction(cb))
cb = function() {};
if (state.ended)
writeAfterEnd(this, state, cb);
else if (validChunk(this, state, chunk, cb)) {
state.pendingcb++;
ret = writeOrBuffer(this, state, chunk, encoding, cb);
}
return ret;
};
Writable.prototype.cork = function() {
var state = this._writableState;
state.corked++;
};
Writable.prototype.uncork = function() {
var state = this._writableState;
if (state.corked) {
state.corked--;
if (!state.writing &&
!state.corked &&
!state.finished &&
!state.bufferProcessing &&
state.buffer.length)
clearBuffer(this, state);
}
};
function decodeChunk(state, chunk, encoding) {
if (!state.objectMode &&
state.decodeStrings !== false &&
util.isString(chunk)) {
chunk = new Buffer(chunk, encoding);
}
return chunk;
}
// if we're already writing something, then just put this
// in the queue, and wait our turn. Otherwise, call _write
// If we return false, then we need a drain event, so set that flag.
function writeOrBuffer(stream, state, chunk, encoding, cb) {
chunk = decodeChunk(state, chunk, encoding);
if (util.isBuffer(chunk))
encoding = 'buffer';
var len = state.objectMode ? 1 : chunk.length;
state.length += len;
var ret = state.length < state.highWaterMark;
// we must ensure that previous needDrain will not be reset to false.
if (!ret)
state.needDrain = true;
if (state.writing || state.corked)
state.buffer.push(new WriteReq(chunk, encoding, cb));
else
doWrite(stream, state, false, len, chunk, encoding, cb);
return ret;
}
function doWrite(stream, state, writev, len, chunk, encoding, cb) {
state.writelen = len;
state.writecb = cb;
state.writing = true;
state.sync = true;
if (writev)
stream._writev(chunk, state.onwrite);
else
stream._write(chunk, encoding, state.onwrite);
state.sync = false;
}
function onwriteError(stream, state, sync, er, cb) {
if (sync)
process.nextTick(function() {
state.pendingcb--;
cb(er);
});
else {
state.pendingcb--;
cb(er);
}
stream._writableState.errorEmitted = true;
stream.emit('error', er);
}
function onwriteStateUpdate(state) {
state.writing = false;
state.writecb = null;
state.length -= state.writelen;
state.writelen = 0;
}
function onwrite(stream, er) {
var state = stream._writableState;
var sync = state.sync;
var cb = state.writecb;
onwriteStateUpdate(state);
if (er)
onwriteError(stream, state, sync, er, cb);
else {
// Check if we're actually ready to finish, but don't emit yet
var finished = needFinish(stream, state);
if (!finished &&
!state.corked &&
!state.bufferProcessing &&
state.buffer.length) {
clearBuffer(stream, state);
}
if (sync) {
process.nextTick(function() {
afterWrite(stream, state, finished, cb);
});
} else {
afterWrite(stream, state, finished, cb);
}
}
}
function afterWrite(stream, state, finished, cb) {
if (!finished)
onwriteDrain(stream, state);
state.pendingcb--;
cb();
finishMaybe(stream, state);
}
// Must force callback to be called on nextTick, so that we don't
// emit 'drain' before the write() consumer gets the 'false' return
// value, and has a chance to attach a 'drain' listener.
function onwriteDrain(stream, state) {
if (state.length === 0 && state.needDrain) {
state.needDrain = false;
stream.emit('drain');
}
}
// if there's something in the buffer waiting, then process it
function clearBuffer(stream, state) {
state.bufferProcessing = true;
if (stream._writev && state.buffer.length > 1) {
// Fast case, write everything using _writev()
var cbs = [];
for (var c = 0; c < state.buffer.length; c++)
cbs.push(state.buffer[c].callback);
// count the one we are adding, as well.
// TODO(isaacs) clean this up
state.pendingcb++;
doWrite(stream, state, true, state.length, state.buffer, '', function(err) {
for (var i = 0; i < cbs.length; i++) {
state.pendingcb--;
cbs[i](err);
}
});
// Clear buffer
state.buffer = [];
} else {
// Slow case, write chunks one-by-one
for (var c = 0; c < state.buffer.length; c++) {
var entry = state.buffer[c];
var chunk = entry.chunk;
var encoding = entry.encoding;
var cb = entry.callback;
var len = state.objectMode ? 1 : chunk.length;
doWrite(stream, state, false, len, chunk, encoding, cb);
// if we didn't call the onwrite immediately, then
// it means that we need to wait until it does.
// also, that means that the chunk and cb are currently
// being processed, so move the buffer counter past them.
if (state.writing) {
c++;
break;
}
}
if (c < state.buffer.length)
state.buffer = state.buffer.slice(c);
else
state.buffer.length = 0;
}
state.bufferProcessing = false;
}
Writable.prototype._write = function(chunk, encoding, cb) {
cb(new Error('not implemented'));
};
Writable.prototype._writev = null;
Writable.prototype.end = function(chunk, encoding, cb) {
var state = this._writableState;
if (util.isFunction(chunk)) {
cb = chunk;
chunk = null;
encoding = null;
} else if (util.isFunction(encoding)) {
cb = encoding;
encoding = null;
}
if (!util.isNullOrUndefined(chunk))
this.write(chunk, encoding);
// .end() fully uncorks
if (state.corked) {
state.corked = 1;
this.uncork();
}
// ignore unnecessary end() calls.
if (!state.ending && !state.finished)
endWritable(this, state, cb);
};
function needFinish(stream, state) {
return (state.ending &&
state.length === 0 &&
!state.finished &&
!state.writing);
}
function prefinish(stream, state) {
if (!state.prefinished) {
state.prefinished = true;
stream.emit('prefinish');
}
}
function finishMaybe(stream, state) {
var need = needFinish(stream, state);
if (need) {
if (state.pendingcb === 0) {
prefinish(stream, state);
state.finished = true;
stream.emit('finish');
} else
prefinish(stream, state);
}
return need;
}
function endWritable(stream, state, cb) {
state.ending = true;
finishMaybe(stream, state);
if (cb) {
if (state.finished)
process.nextTick(cb);
else
stream.once('finish', cb);
}
state.ended = true;
}

View File

@@ -0,0 +1,65 @@
{
"_from": "readable-stream@1.1.x",
"_id": "readable-stream@1.1.14",
"_inBundle": false,
"_integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"_location": "/imap/readable-stream",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "readable-stream@1.1.x",
"name": "readable-stream",
"escapedName": "readable-stream",
"rawSpec": "1.1.x",
"saveSpec": null,
"fetchSpec": "1.1.x"
},
"_requiredBy": [
"/imap"
],
"_resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"_shasum": "7cf4c54ef648e3813084c636dd2079e166c081d9",
"_spec": "readable-stream@1.1.x",
"_where": "/data/node_modules/imap",
"author": {
"name": "Isaac Z. Schlueter",
"email": "i@izs.me",
"url": "http://blog.izs.me/"
},
"browser": {
"util": false
},
"bugs": {
"url": "https://github.com/isaacs/readable-stream/issues"
},
"bundleDependencies": false,
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
"isarray": "0.0.1",
"string_decoder": "~0.10.x"
},
"deprecated": false,
"description": "Streams3, a user-land copy of the stream library from Node.js v0.11.x",
"devDependencies": {
"tap": "~0.2.6"
},
"homepage": "https://github.com/isaacs/readable-stream#readme",
"keywords": [
"readable",
"stream",
"pipe"
],
"license": "MIT",
"main": "readable.js",
"name": "readable-stream",
"repository": {
"type": "git",
"url": "git://github.com/isaacs/readable-stream.git"
},
"scripts": {
"test": "tap test/simple/*.js"
},
"version": "1.1.14"
}

View File

@@ -0,0 +1 @@
module.exports = require("./lib/_stream_passthrough.js")

View File

@@ -0,0 +1,10 @@
exports = module.exports = require('./lib/_stream_readable.js');
exports.Stream = require('stream');
exports.Readable = exports;
exports.Writable = require('./lib/_stream_writable.js');
exports.Duplex = require('./lib/_stream_duplex.js');
exports.Transform = require('./lib/_stream_transform.js');
exports.PassThrough = require('./lib/_stream_passthrough.js');
if (!process.browser && process.env.READABLE_STREAM === 'disable') {
module.exports = require('stream');
}

View File

@@ -0,0 +1 @@
module.exports = require("./lib/_stream_transform.js")

View File

@@ -0,0 +1 @@
module.exports = require("./lib/_stream_writable.js")

View File

@@ -0,0 +1,2 @@
build
test

View File

@@ -0,0 +1,20 @@
Copyright Joyent, Inc. and other Node contributors.
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.

View File

@@ -0,0 +1,7 @@
**string_decoder.js** (`require('string_decoder')`) from Node.js core
Copyright Joyent, Inc. and other Node contributors. See LICENCE file for details.
Version numbers match the versions found in Node core, e.g. 0.10.24 matches Node 0.10.24, likewise 0.11.10 matches Node 0.11.10. **Prefer the stable version over the unstable.**
The *build/* directory contains a build script that will scrape the source from the [joyent/node](https://github.com/joyent/node) repo given a specific Node version.

View File

@@ -0,0 +1,221 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// 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.
var Buffer = require('buffer').Buffer;
var isBufferEncoding = Buffer.isEncoding
|| function(encoding) {
switch (encoding && encoding.toLowerCase()) {
case 'hex': case 'utf8': case 'utf-8': case 'ascii': case 'binary': case 'base64': case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': case 'raw': return true;
default: return false;
}
}
function assertEncoding(encoding) {
if (encoding && !isBufferEncoding(encoding)) {
throw new Error('Unknown encoding: ' + encoding);
}
}
// StringDecoder provides an interface for efficiently splitting a series of
// buffers into a series of JS strings without breaking apart multi-byte
// characters. CESU-8 is handled as part of the UTF-8 encoding.
//
// @TODO Handling all encodings inside a single object makes it very difficult
// to reason about this code, so it should be split up in the future.
// @TODO There should be a utf8-strict encoding that rejects invalid UTF-8 code
// points as used by CESU-8.
var StringDecoder = exports.StringDecoder = function(encoding) {
this.encoding = (encoding || 'utf8').toLowerCase().replace(/[-_]/, '');
assertEncoding(encoding);
switch (this.encoding) {
case 'utf8':
// CESU-8 represents each of Surrogate Pair by 3-bytes
this.surrogateSize = 3;
break;
case 'ucs2':
case 'utf16le':
// UTF-16 represents each of Surrogate Pair by 2-bytes
this.surrogateSize = 2;
this.detectIncompleteChar = utf16DetectIncompleteChar;
break;
case 'base64':
// Base-64 stores 3 bytes in 4 chars, and pads the remainder.
this.surrogateSize = 3;
this.detectIncompleteChar = base64DetectIncompleteChar;
break;
default:
this.write = passThroughWrite;
return;
}
// Enough space to store all bytes of a single character. UTF-8 needs 4
// bytes, but CESU-8 may require up to 6 (3 bytes per surrogate).
this.charBuffer = new Buffer(6);
// Number of bytes received for the current incomplete multi-byte character.
this.charReceived = 0;
// Number of bytes expected for the current incomplete multi-byte character.
this.charLength = 0;
};
// write decodes the given buffer and returns it as JS string that is
// guaranteed to not contain any partial multi-byte characters. Any partial
// character found at the end of the buffer is buffered up, and will be
// returned when calling write again with the remaining bytes.
//
// Note: Converting a Buffer containing an orphan surrogate to a String
// currently works, but converting a String to a Buffer (via `new Buffer`, or
// Buffer#write) will replace incomplete surrogates with the unicode
// replacement character. See https://codereview.chromium.org/121173009/ .
StringDecoder.prototype.write = function(buffer) {
var charStr = '';
// if our last write ended with an incomplete multibyte character
while (this.charLength) {
// determine how many remaining bytes this buffer has to offer for this char
var available = (buffer.length >= this.charLength - this.charReceived) ?
this.charLength - this.charReceived :
buffer.length;
// add the new bytes to the char buffer
buffer.copy(this.charBuffer, this.charReceived, 0, available);
this.charReceived += available;
if (this.charReceived < this.charLength) {
// still not enough chars in this buffer? wait for more ...
return '';
}
// remove bytes belonging to the current character from the buffer
buffer = buffer.slice(available, buffer.length);
// get the character that was split
charStr = this.charBuffer.slice(0, this.charLength).toString(this.encoding);
// CESU-8: lead surrogate (D800-DBFF) is also the incomplete character
var charCode = charStr.charCodeAt(charStr.length - 1);
if (charCode >= 0xD800 && charCode <= 0xDBFF) {
this.charLength += this.surrogateSize;
charStr = '';
continue;
}
this.charReceived = this.charLength = 0;
// if there are no more bytes in this buffer, just emit our char
if (buffer.length === 0) {
return charStr;
}
break;
}
// determine and set charLength / charReceived
this.detectIncompleteChar(buffer);
var end = buffer.length;
if (this.charLength) {
// buffer the incomplete character bytes we got
buffer.copy(this.charBuffer, 0, buffer.length - this.charReceived, end);
end -= this.charReceived;
}
charStr += buffer.toString(this.encoding, 0, end);
var end = charStr.length - 1;
var charCode = charStr.charCodeAt(end);
// CESU-8: lead surrogate (D800-DBFF) is also the incomplete character
if (charCode >= 0xD800 && charCode <= 0xDBFF) {
var size = this.surrogateSize;
this.charLength += size;
this.charReceived += size;
this.charBuffer.copy(this.charBuffer, size, 0, size);
buffer.copy(this.charBuffer, 0, 0, size);
return charStr.substring(0, end);
}
// or just emit the charStr
return charStr;
};
// detectIncompleteChar determines if there is an incomplete UTF-8 character at
// the end of the given buffer. If so, it sets this.charLength to the byte
// length that character, and sets this.charReceived to the number of bytes
// that are available for this character.
StringDecoder.prototype.detectIncompleteChar = function(buffer) {
// determine how many bytes we have to check at the end of this buffer
var i = (buffer.length >= 3) ? 3 : buffer.length;
// Figure out if one of the last i bytes of our buffer announces an
// incomplete char.
for (; i > 0; i--) {
var c = buffer[buffer.length - i];
// See http://en.wikipedia.org/wiki/UTF-8#Description
// 110XXXXX
if (i == 1 && c >> 5 == 0x06) {
this.charLength = 2;
break;
}
// 1110XXXX
if (i <= 2 && c >> 4 == 0x0E) {
this.charLength = 3;
break;
}
// 11110XXX
if (i <= 3 && c >> 3 == 0x1E) {
this.charLength = 4;
break;
}
}
this.charReceived = i;
};
StringDecoder.prototype.end = function(buffer) {
var res = '';
if (buffer && buffer.length)
res = this.write(buffer);
if (this.charReceived) {
var cr = this.charReceived;
var buf = this.charBuffer;
var enc = this.encoding;
res += buf.slice(0, cr).toString(enc);
}
return res;
};
function passThroughWrite(buffer) {
return buffer.toString(this.encoding);
}
function utf16DetectIncompleteChar(buffer) {
this.charReceived = buffer.length % 2;
this.charLength = this.charReceived ? 2 : 0;
}
function base64DetectIncompleteChar(buffer) {
this.charReceived = buffer.length % 3;
this.charLength = this.charReceived ? 3 : 0;
}

View File

@@ -0,0 +1,53 @@
{
"_from": "string_decoder@~0.10.x",
"_id": "string_decoder@0.10.31",
"_inBundle": false,
"_integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
"_location": "/imap/string_decoder",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "string_decoder@~0.10.x",
"name": "string_decoder",
"escapedName": "string_decoder",
"rawSpec": "~0.10.x",
"saveSpec": null,
"fetchSpec": "~0.10.x"
},
"_requiredBy": [
"/imap/readable-stream"
],
"_resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"_shasum": "62e203bc41766c6c28c9fc84301dab1c5310fa94",
"_spec": "string_decoder@~0.10.x",
"_where": "/data/node_modules/imap/node_modules/readable-stream",
"bugs": {
"url": "https://github.com/rvagg/string_decoder/issues"
},
"bundleDependencies": false,
"dependencies": {},
"deprecated": false,
"description": "The string_decoder module from Node core",
"devDependencies": {
"tap": "~0.4.8"
},
"homepage": "https://github.com/rvagg/string_decoder",
"keywords": [
"string",
"decoder",
"browser",
"browserify"
],
"license": "MIT",
"main": "index.js",
"name": "string_decoder",
"repository": {
"type": "git",
"url": "git://github.com/rvagg/string_decoder.git"
},
"scripts": {
"test": "tap test/simple/*.js"
},
"version": "0.10.31"
}

69
nodered/rootfs/data/node_modules/imap/package.json generated vendored Normal file
View File

@@ -0,0 +1,69 @@
{
"_from": "imap@^0.8.19",
"_id": "imap@0.8.19",
"_inBundle": false,
"_integrity": "sha1-NniHOTSrCc6mukh0HyhNoq9Z2NU=",
"_location": "/imap",
"_phantomChildren": {
"core-util-is": "1.0.2",
"inherits": "2.0.1"
},
"_requested": {
"type": "range",
"registry": true,
"raw": "imap@^0.8.19",
"name": "imap",
"escapedName": "imap",
"rawSpec": "^0.8.19",
"saveSpec": null,
"fetchSpec": "^0.8.19"
},
"_requiredBy": [
"/node-red-node-email"
],
"_resolved": "https://registry.npmjs.org/imap/-/imap-0.8.19.tgz",
"_shasum": "3678873934ab09cea6ba48741f284da2af59d8d5",
"_spec": "imap@^0.8.19",
"_where": "/data/node_modules/node-red-node-email",
"author": {
"name": "Brian White",
"email": "mscdex@mscdex.net"
},
"bugs": {
"url": "https://github.com/mscdex/node-imap/issues"
},
"bundleDependencies": false,
"dependencies": {
"readable-stream": "1.1.x",
"utf7": ">=1.0.2"
},
"deprecated": false,
"description": "An IMAP module for node.js that makes communicating with IMAP servers easy",
"engines": {
"node": ">=0.8.0"
},
"homepage": "https://github.com/mscdex/node-imap#readme",
"keywords": [
"imap",
"mail",
"email",
"reader",
"client"
],
"licenses": [
{
"type": "MIT",
"url": "http://github.com/mscdex/node-imap/raw/master/LICENSE"
}
],
"main": "./lib/Connection",
"name": "imap",
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/mscdex/node-imap.git"
},
"scripts": {
"test": "node test/test.js"
},
"version": "0.8.19"
}

View File

@@ -0,0 +1,92 @@
var assert = require('assert'),
net = require('net'),
Imap = require('../lib/Connection'),
result;
var CRLF = '\r\n';
var RESPONSES = [
['* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA CHILDREN',
'A0 OK Thats all she wrote!',
''
].join(CRLF),
['* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA CHILDREN UIDPLUS MOVE',
'A1 OK authenticated (Success)',
''
].join(CRLF),
['* NAMESPACE (("" "/")) NIL NIL',
'A2 OK Success',
''
].join(CRLF),
['* LIST (\\Noselect) "/" "/"',
'A3 OK Success',
''
].join(CRLF),
['* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)',
'* OK [PERMANENTFLAGS ()] Flags permitted.',
'* OK [UIDVALIDITY 2] UIDs valid.',
'* 685 EXISTS',
'* 0 RECENT',
'* OK [UIDNEXT 4422] Predicted next UID.',
'A4 OK [READ-ONLY] INBOX selected. (Success)',
''
].join(CRLF),
['* 1 FETCH (UID 1)',
'* 1 FETCH (INTERNALDATE "05-Sep-2004 00:38:03 +0000" UID 1000)',
'* 1 FETCH (FLAGS (\\Seen))',
'A5 OK Success',
''
].join(CRLF),
['* BYE LOGOUT Requested',
'A6 OK good day (Success)',
''
].join(CRLF)
];
var srv = net.createServer(function(sock) {
sock.write('* OK asdf\r\n');
var buf = '', lines;
sock.on('data', function(data) {
buf += data.toString('utf8');
if (buf.indexOf(CRLF) > -1) {
lines = buf.split(CRLF);
buf = lines.pop();
lines.forEach(function() {
sock.write(RESPONSES.shift());
});
}
});
});
srv.listen(0, '127.0.0.1', function() {
var port = srv.address().port;
var imap = new Imap({
user: 'foo',
password: 'bar',
host: '127.0.0.1',
port: port,
keepalive: false
});
imap.on('ready', function() {
imap.openBox('INBOX', true, function() {
var f = imap.seq.fetch(1);
f.on('message', function(m) {
m.once('attributes', function(attrs) {
result = attrs;
});
});
f.on('end', function() {
srv.close();
imap.end();
});
});
});
imap.connect();
});
process.once('exit', function() {
assert.deepEqual(result, {
uid: 1,
date: new Date('05-Sep-2004 00:38:03 +0000'),
flags: [ '\\Seen' ]
});
});

View File

@@ -0,0 +1,92 @@
var assert = require('assert'),
net = require('net'),
Imap = require('../lib/Connection'),
result;
var CRLF = '\r\n';
var RESPONSES = [
['* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA CHILDREN',
'A0 OK Thats all she wrote!',
''
].join(CRLF),
['* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA CHILDREN UIDPLUS MOVE',
'A1 OK authenticated (Success)',
''
].join(CRLF),
['* NAMESPACE (("" "/")) NIL NIL',
'A2 OK Success',
''
].join(CRLF),
['* LIST (\\Noselect) "/" "/"',
'A3 OK Success',
''
].join(CRLF),
['* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)',
'* OK [PERMANENTFLAGS ()] Flags permitted.',
'* OK [UIDVALIDITY 2] UIDs valid.',
'* 685 EXISTS',
'* 0 RECENT',
'* OK [UIDNEXT 4422] Predicted next UID.',
'A4 OK [READ-ONLY] INBOX selected. (Success)',
''
].join(CRLF),
['* 1 FETCH (UID 1)',
'* 1 FETCH (INTERNALDATE "05-Sep-2004 00:38:03 +0000")',
'* 1 FETCH (FLAGS (\\Seen))',
'A5 OK Success',
''
].join(CRLF),
['* BYE LOGOUT Requested',
'A6 OK good day (Success)',
''
].join(CRLF)
];
var srv = net.createServer(function(sock) {
sock.write('* OK asdf\r\n');
var buf = '', lines;
sock.on('data', function(data) {
buf += data.toString('utf8');
if (buf.indexOf(CRLF) > -1) {
lines = buf.split(CRLF);
buf = lines.pop();
lines.forEach(function() {
sock.write(RESPONSES.shift());
});
}
});
});
srv.listen(0, '127.0.0.1', function() {
var port = srv.address().port;
var imap = new Imap({
user: 'foo',
password: 'bar',
host: '127.0.0.1',
port: port,
keepalive: false
});
imap.on('ready', function() {
imap.openBox('INBOX', true, function() {
var f = imap.seq.fetch(1);
f.on('message', function(m) {
m.once('attributes', function(attrs) {
result = attrs;
});
});
f.on('end', function() {
srv.close();
imap.end();
});
});
});
imap.connect();
});
process.once('exit', function() {
assert.deepEqual(result, {
uid: 1,
date: new Date('05-Sep-2004 00:38:03 +0000'),
flags: [ '\\Seen' ]
});
});

View File

@@ -0,0 +1,176 @@
// Test for _at least_ GH Issues #345, #379, #392, #411
var assert = require('assert'),
net = require('net'),
crypto = require('crypto'),
Imap = require('../lib/Connection');
var result = [],
body = [],
bodyInfo = [];
var CRLF = '\r\n';
// generate data larger than highWaterMark
var bytes = crypto.pseudoRandomBytes(10240).toString('hex');
var RESPONSES = [
['* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA CHILDREN',
'A0 OK Thats all she wrote!',
''
].join(CRLF),
['* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA CHILDREN UIDPLUS MOVE',
'A1 OK authenticated (Success)',
''
].join(CRLF),
['* NAMESPACE (("" "/")) NIL NIL',
'A2 OK Success',
''
].join(CRLF),
['* LIST (\\Noselect) "/" "/"',
'A3 OK Success',
''
].join(CRLF),
['* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)',
'* OK [PERMANENTFLAGS ()] Flags permitted.',
'* OK [UIDVALIDITY 2] UIDs valid.',
'* 685 EXISTS',
'* 0 RECENT',
'* OK [UIDNEXT 4422] Predicted next UID.',
'A4 OK [READ-ONLY] INBOX selected. (Success)',
''
].join(CRLF),
'* 1 FETCH (UID 1000 FLAGS (\\Seen) INTERNALDATE "05-Sep-2004 00:38:03 +0000" BODY[TEXT] {'
+ bytes.length
+ '}'
+ CRLF
+ bytes.substring(0, 20000),
['* BYE LOGOUT Requested',
'A6 OK good day (Success)',
''
].join(CRLF)
];
var EXPECTED = [
'A0 CAPABILITY',
'A1 LOGIN "foo" "bar"',
'A2 NAMESPACE',
'A3 LIST "" ""',
'A4 EXAMINE "INBOX"',
'A5 FETCH 1,2 (UID FLAGS INTERNALDATE BODY.PEEK[TEXT])',
'A6 LOGOUT'
];
var exp = -1,
res = -1;
var srv = net.createServer(function(sock) {
sock.write('* OK asdf\r\n');
var buf = '', lines;
sock.on('data', function(data) {
buf += data.toString('utf8');
if (buf.indexOf(CRLF) > -1) {
lines = buf.split(CRLF);
buf = lines.pop();
lines.forEach(function(l) {
assert(l === EXPECTED[++exp], 'Unexpected client request: ' + l);
assert(RESPONSES[++res], 'No response for client request: ' + l);
sock.write(RESPONSES[res]);
});
}
});
});
srv.listen(0, '127.0.0.1', function() {
var port = srv.address().port;
var imap = new Imap({
user: 'foo',
password: 'bar',
host: '127.0.0.1',
port: port,
keepalive: false
});
imap.on('ready', function() {
srv.close();
imap.openBox('INBOX', true, function() {
var f = imap.seq.fetch([1,2], { bodies: ['TEXT'] });
var nbody = -1;
f.on('message', function(m) {
m.on('body', function(stream, info) {
++nbody;
bodyInfo.push(info);
body[nbody] = '';
if (nbody === 0) {
// first allow body.push() to return false in parser.js
setTimeout(function() {
stream.on('data', function(chunk) {
body[nbody] += chunk.toString('binary');
});
setTimeout(function() {
var oldRead = stream._read,
readCalled = false;
stream._read = function(n) {
readCalled = true;
stream._read = oldRead;
imap._sock.push(bytes.substring(100, 200)
+ ')'
+ CRLF
+ 'A5 OK Success'
+ CRLF);
imap._parser._tryread();
};
imap._sock.push(bytes.substring(20000)
+ ')'
+ CRLF
+ '* 2 FETCH (UID 1001 FLAGS (\\Seen) INTERNALDATE "05-Sep-2004 00:38:13 +0000" BODY[TEXT] {200}'
+ CRLF
+ bytes.substring(0, 100));
// if we got this far, then we didn't get an exception and we
// are running with the bug fix in place
if (!readCalled) {
imap._sock.push(bytes.substring(100, 200)
+ ')'
+ CRLF
+ 'A5 OK Success'
+ CRLF);
}
}, 100);
}, 100);
} else {
stream.on('data', function(chunk) {
body[nbody] += chunk.toString('binary');
});
}
});
m.on('attributes', function(attrs) {
result.push(attrs);
});
});
f.on('end', function() {
imap.end();
});
});
});
imap.connect();
});
process.once('exit', function() {
assert.deepEqual(result, [{
uid: 1000,
date: new Date('05-Sep-2004 00:38:03 +0000'),
flags: [ '\\Seen' ]
}, {
uid: 1001,
date: new Date('05-Sep-2004 00:38:13 +0000'),
flags: [ '\\Seen' ]
}]);
assert.deepEqual(body, [bytes, bytes.substring(0, 200)]);
assert.deepEqual(bodyInfo, [{
seqno: 1,
which: 'TEXT',
size: bytes.length
}, {
seqno: 2,
which: 'TEXT',
size: 200
}]);
});

View File

@@ -0,0 +1,104 @@
var assert = require('assert'),
net = require('net'),
Imap = require('../lib/Connection');
var result, body = '', bodyInfo;
var CRLF = '\r\n';
var RESPONSES = [
['* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA CHILDREN',
'A0 OK Thats all she wrote!',
''
].join(CRLF),
['* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA CHILDREN UIDPLUS MOVE',
'A1 OK authenticated (Success)',
''
].join(CRLF),
['* NAMESPACE (("" "/")) NIL NIL',
'A2 OK Success',
''
].join(CRLF),
['* LIST (\\Noselect) "/" "/"',
'A3 OK Success',
''
].join(CRLF),
['* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)',
'* OK [PERMANENTFLAGS ()] Flags permitted.',
'* OK [UIDVALIDITY 2] UIDs valid.',
'* 685 EXISTS',
'* 0 RECENT',
'* OK [UIDNEXT 4422] Predicted next UID.',
'A4 OK [READ-ONLY] INBOX selected. (Success)',
''
].join(CRLF),
['* 1 FETCH (UID 1)',
'* 1 FETCH (INTERNALDATE "05-Sep-2004 00:38:03 +0000" UID 1000)',
'* 1 FETCH (BODY[TEXT] "IMAP is terrible")',
'* 1 FETCH (FLAGS (\\Seen))',
'A5 OK Success',
''
].join(CRLF),
['* BYE LOGOUT Requested',
'A6 OK good day (Success)',
''
].join(CRLF)
];
var srv = net.createServer(function(sock) {
sock.write('* OK asdf\r\n');
var buf = '', lines;
sock.on('data', function(data) {
buf += data.toString('utf8');
if (buf.indexOf(CRLF) > -1) {
lines = buf.split(CRLF);
buf = lines.pop();
lines.forEach(function() {
sock.write(RESPONSES.shift());
});
}
});
});
srv.listen(0, '127.0.0.1', function() {
var port = srv.address().port;
var imap = new Imap({
user: 'foo',
password: 'bar',
host: '127.0.0.1',
port: port,
keepalive: false
});
imap.on('ready', function() {
imap.openBox('INBOX', true, function() {
var f = imap.seq.fetch(1, { bodies: ['TEXT'] });
f.on('message', function(m) {
m.on('body', function(stream, info) {
bodyInfo = info;
stream.on('data', function(chunk) { body += chunk.toString('utf8'); });
});
m.on('attributes', function(attrs) {
result = attrs;
});
});
f.on('end', function() {
srv.close();
imap.end();
});
});
});
imap.connect();
});
process.once('exit', function() {
assert.deepEqual(result, {
uid: 1,
date: new Date('05-Sep-2004 00:38:03 +0000'),
flags: [ '\\Seen' ]
});
assert.equal(body, 'IMAP is terrible');
assert.deepEqual(bodyInfo, {
seqno: 1,
which: 'TEXT',
size: 16
});
});

View File

@@ -0,0 +1,144 @@
var assert = require('assert'),
net = require('net'),
Imap = require('../lib/Connection');
var result, body = '', bodyInfo;
var CRLF = '\r\n';
var RESPONSES = [
['* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA CHILDREN',
'A0 OK Thats all she wrote!',
''
].join(CRLF),
['* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA CHILDREN UIDPLUS MOVE',
'A1 OK authenticated (Success)',
''
].join(CRLF),
['* NAMESPACE (("" "/")) NIL NIL',
'A2 OK Success',
''
].join(CRLF),
['* LIST (\\Noselect) "/" "/"',
'A3 OK Success',
''
].join(CRLF),
['* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)',
'* OK [PERMANENTFLAGS ()] Flags permitted.',
'* OK [UIDVALIDITY 2] UIDs valid.',
'* 685 EXISTS',
'* 0 RECENT',
'* OK [UIDNEXT 4422] Predicted next UID.',
'A4 OK [READ-ONLY] INBOX selected. (Success)',
''
].join(CRLF),
['* 1 FETCH (UID 1)',
'* 1 FETCH (INTERNALDATE "05-Sep-2004 00:38:03 +0000" UID 1000)',
'* 1 FETCH (BODY[TEXT] "IMAP is terrible")',
'* 1 FETCH (FLAGS (\\Seen))',
'A5 OK Success',
''
].join(CRLF),
['* STATUS test (MESSAGES 231 RECENT 0 UNSEEN 0 UIDVALIDITY 123 UIDNEXT 442)',
'A6 OK STATUS completed',
''
].join(CRLF),
['* BYE LOGOUT Requested',
'A7 OK good day (Success)',
''
].join(CRLF)
];
var EXPECTED = [
'A0 CAPABILITY',
'A1 LOGIN "foo" "bar"',
'A2 NAMESPACE',
'A3 LIST "" ""',
'A4 EXAMINE "INBOX"',
'A5 FETCH 1 (UID FLAGS INTERNALDATE BODY.PEEK[TEXT])',
'IDLE IDLE',
'DONE',
'A6 STATUS "test" (MESSAGES RECENT UNSEEN UIDVALIDITY UIDNEXT)',
'A7 LOGOUT'
];
var exp = -1,
res = -1,
sentCont = false;
var srv = net.createServer(function(sock) {
sock.write('* OK asdf\r\n');
var buf = '', lines;
sock.on('data', function(data) {
buf += data.toString('utf8');
if (buf.indexOf(CRLF) > -1) {
lines = buf.split(CRLF);
buf = lines.pop();
lines.forEach(function(l) {
assert(l === EXPECTED[++exp], 'Unexpected client request: ' + l);
if (l === 'DONE') {
assert(sentCont, 'DONE seen before continuation sent');
sock.write('IDLE ok\r\n');
} else if (l === 'IDLE IDLE') {
setTimeout(function() {
sentCont = true;
sock.write('+ idling\r\n');
}, 100);
} else {
assert(RESPONSES[++res], 'No response for client request: ' + l);
sock.write(RESPONSES[res]);
}
});
}
});
});
srv.listen(0, '127.0.0.1', function() {
var port = srv.address().port;
var imap = new Imap({
user: 'foo',
password: 'bar',
host: '127.0.0.1',
port: port,
keepalive: true
});
imap.on('ready', function() {
srv.close();
imap.openBox('INBOX', true, function() {
var f = imap.seq.fetch(1, { bodies: ['TEXT'] });
f.on('message', function(m) {
m.on('body', function(stream, info) {
bodyInfo = info;
stream.on('data', function(chunk) { body += chunk.toString('utf8'); });
});
m.on('attributes', function(attrs) {
result = attrs;
});
});
f.on('end', function() {
setTimeout(function() {
var timeout = setTimeout(function() {
assert(false, 'Timed out waiting for STATUS');
}, 500);
imap.status('test', function(err, status) {
clearTimeout(timeout);
imap.end();
});
}, 500);
});
});
});
imap.connect();
});
process.once('exit', function() {
assert.deepEqual(result, {
uid: 1,
date: new Date('05-Sep-2004 00:38:03 +0000'),
flags: [ '\\Seen' ]
});
assert.equal(body, 'IMAP is terrible');
assert.deepEqual(bodyInfo, {
seqno: 1,
which: 'TEXT',
size: 16
});
});

View File

@@ -0,0 +1,138 @@
var assert = require('assert'),
net = require('net'),
Imap = require('../lib/Connection');
var result, body = '', bodyInfo;
var CRLF = '\r\n';
var RESPONSES = [
['* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA CHILDREN',
'A0 OK Thats all she wrote!',
''
].join(CRLF),
['* CAPABILITY IMAP4rev1 UNSELECT IDLE NAMESPACE QUOTA CHILDREN UIDPLUS MOVE',
'A1 OK authenticated (Success)',
''
].join(CRLF),
['* NAMESPACE (("" "/")) NIL NIL',
'A2 OK Success',
''
].join(CRLF),
['* LIST (\\Noselect) "/" "/"',
'A3 OK Success',
''
].join(CRLF),
['* FLAGS (\\Answered \\Flagged \\Draft \\Deleted \\Seen)',
'* OK [PERMANENTFLAGS ()] Flags permitted.',
'* OK [UIDVALIDITY 2] UIDs valid.',
'* 685 EXISTS',
'* 0 RECENT',
'* OK [UIDNEXT 4422] Predicted next UID.',
'A4 OK [READ-ONLY] INBOX selected. (Success)',
''
].join(CRLF),
['* 1 FETCH (UID 1)',
'* 1 FETCH (INTERNALDATE "05-Sep-2004 00:38:03 +0000" UID 1000)',
'* 1 FETCH (BODY[TEXT] "IMAP is terrible")',
'* 1 FETCH (FLAGS (\\Seen))',
'A5 OK Success',
''
].join(CRLF),
['* STATUS test (MESSAGES 231 RECENT 0 UNSEEN 0 UIDVALIDITY 123 UIDNEXT 442)',
'A6 OK STATUS completed',
''
].join(CRLF),
['* BYE LOGOUT Requested',
'A7 OK good day (Success)',
''
].join(CRLF)
];
var EXPECTED = [
'A0 CAPABILITY',
'A1 LOGIN "foo" "bar"',
'A2 NAMESPACE',
'A3 LIST "" ""',
'A4 EXAMINE "INBOX"',
'A5 FETCH 1 (UID FLAGS INTERNALDATE BODY.PEEK[TEXT])',
'IDLE IDLE',
'DONE',
'A6 STATUS "test" (MESSAGES RECENT UNSEEN UIDVALIDITY UIDNEXT)',
'A7 LOGOUT'
];
var exp = -1,
res = -1,
sentCont = false;
var srv = net.createServer(function(sock) {
sock.write('* OK asdf\r\n');
var buf = '', lines;
sock.on('data', function(data) {
buf += data.toString('utf8');
if (buf.indexOf(CRLF) > -1) {
lines = buf.split(CRLF);
buf = lines.pop();
lines.forEach(function(l) {
assert(l === EXPECTED[++exp], 'Unexpected client request: ' + l);
if (l === 'DONE') {
assert(sentCont, 'DONE seen before continuation sent');
sock.write('IDLE ok\r\n');
} else if (l === 'IDLE IDLE') {
setTimeout(function() {
sentCont = true;
sock.write('+ idling\r\n');
}, 100);
} else {
assert(RESPONSES[++res], 'No response for client request: ' + l);
sock.write(RESPONSES[res]);
}
});
}
});
});
srv.listen(0, '127.0.0.1', function() {
var port = srv.address().port;
var imap = new Imap({
user: 'foo',
password: 'bar',
host: '127.0.0.1',
port: port,
keepalive: true
});
imap.on('ready', function() {
srv.close();
imap.openBox('INBOX', true, function() {
var f = imap.seq.fetch(1, { bodies: ['TEXT'] });
f.on('message', function(m) {
m.on('body', function(stream, info) {
bodyInfo = info;
stream.on('data', function(chunk) { body += chunk.toString('utf8'); });
});
m.on('attributes', function(attrs) {
result = attrs;
});
});
f.on('end', function() {
imap.status('test', function(err, status) {
imap.end();
});
});
});
});
imap.connect();
});
process.once('exit', function() {
assert.deepEqual(result, {
uid: 1,
date: new Date('05-Sep-2004 00:38:03 +0000'),
flags: [ '\\Seen' ]
});
assert.equal(body, 'IMAP is terrible');
assert.deepEqual(bodyInfo, {
seqno: 1,
which: 'TEXT',
size: 16
});
});

View File

@@ -0,0 +1,83 @@
var parseBodyStructure = require('../lib/Parser').parseBodyStructure;
var assert = require('assert'),
inspect = require('util').inspect;
[
{ source: '("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 1152 23)'
+ '("TEXT" "PLAIN" ("CHARSET" "US-ASCII" "NAME" "cc.diff")'
+ ' "<960723163407.20117h@cac.washington.edu>" "Compiler diff"'
+ ' "BASE64" 4554 73)'
+ '"MIXED"',
expected: [ { type: 'mixed' },
[ { partID: '1',
type: 'text',
subtype: 'plain',
params: { charset: 'US-ASCII' },
id: null,
description: null,
encoding: '7BIT',
size: 1152,
lines: 23
}
],
[ { partID: '2',
type: 'text',
subtype: 'plain',
params: { charset: 'US-ASCII', name: 'cc.diff' },
id: '<960723163407.20117h@cac.washington.edu>',
description: 'Compiler diff',
encoding: 'BASE64',
size: 4554,
lines: 73
}
]
],
what: 'RFC3501 example #1'
},
{ source: 'NIL NIL ("CHARSET" "GB2312") NIL NIL NIL 176 NIL NIL NIL',
expected: [ { type: null,
params: null,
disposition: null,
language: [ 'CHARSET', 'GB2312' ],
location: null,
extensions: null
}
],
what: 'Issue 477'
},
{ source: '"TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 3028 92',
expected: [ { partID: '1',
type: 'text',
subtype: 'plain',
params: { charset: 'US-ASCII' },
id: null,
description: null,
encoding: '7BIT',
size: 3028,
lines: 92
}
],
what: 'RFC3501 example #2'
},
].forEach(function(v) {
var result;
try {
result = parseBodyStructure(v.source);
} catch (e) {
console.log(makeMsg(v.what, 'JS Exception: ' + e.stack));
return;
}
assert.deepEqual(result,
v.expected,
makeMsg(v.what,
'Result mismatch:'
+ '\nParsed: ' + inspect(result, false, 10)
+ '\nExpected: ' + inspect(v.expected, false, 10)
)
);
});
function makeMsg(what, msg) {
return '[' + what + ']: ' + msg;
}

View File

@@ -0,0 +1,117 @@
var parseExpr = require('../lib/Parser').parseExpr,
parseEnvelopeAddresses = require('../lib/Parser').parseEnvelopeAddresses;
var assert = require('assert'),
inspect = require('util').inspect;
[
{ source: '("Terry Gray" NIL "gray" "cac.washington.edu")',
expected: [ { name: 'Terry Gray',
mailbox: 'gray',
host: 'cac.washington.edu'
}
],
what: 'RFC3501 example #1'
},
{ source: '(NIL NIL "imap" "cac.washington.edu")',
expected: [ { name: null,
mailbox: 'imap',
host: 'cac.washington.edu'
}
],
what: 'RFC3501 example #2'
},
{ source: '("=?utf-8?Q?=C2=A9=C2=AEAZ=C2=A5?=" NIL "crazy" "example.org")',
expected: [ { name: '©®AZ¥',
mailbox: 'crazy',
host: 'example.org'
}
],
what: 'Name with encoded word(s)'
},
{ source: '(NIL NIL "imap" NIL)'
+ '(NIL NIL NIL NIL)',
expected: [ { group: 'imap',
addresses: []
}
],
what: 'Zero-length group'
},
{ source: '(NIL NIL "imap" NIL)'
+ '("Terry Gray" NIL "gray" "cac.washington.edu")'
+ '(NIL NIL NIL NIL)',
expected: [ { group: 'imap',
addresses: [
{ name: 'Terry Gray',
mailbox: 'gray',
host: 'cac.washington.edu'
}
]
}
],
what: 'One-length group'
},
{ source: '(NIL NIL "imap" NIL)'
+ '("Terry Gray" NIL "gray" "cac.washington.edu")'
+ '(NIL NIL NIL NIL)'
+ '(NIL NIL "imap" "cac.washington.edu")',
expected: [ { group: 'imap',
addresses: [
{ name: 'Terry Gray',
mailbox: 'gray',
host: 'cac.washington.edu'
}
]
},
{ name: null,
mailbox: 'imap',
host: 'cac.washington.edu'
}
],
what: 'One-length group and address'
},
{ source: '(NIL NIL "imap" NIL)'
+ '("Terry Gray" NIL "gray" "cac.washington.edu")',
expected: [ { group: 'imap',
addresses: [
{ name: 'Terry Gray',
mailbox: 'gray',
host: 'cac.washington.edu'
}
]
}
],
what: 'Implicit group end'
},
{ source: '("Terry Gray" NIL "gray" "cac.washington.edu")'
+ '(NIL NIL NIL NIL)',
expected: [ { name: 'Terry Gray',
mailbox: 'gray',
host: 'cac.washington.edu'
}
],
what: 'Group end without start'
},
].forEach(function(v) {
var result;
try {
result = parseEnvelopeAddresses(parseExpr(v.source));
} catch (e) {
console.log(makeMsg(v.what, 'JS Exception: ' + e.stack));
return;
}
assert.deepEqual(result,
v.expected,
makeMsg(v.what,
'Result mismatch:'
+ '\nParsed: ' + inspect(result, false, 10)
+ '\nExpected: ' + inspect(v.expected, false, 10)
)
);
});
function makeMsg(what, msg) {
return '[' + what + ']: ' + msg;
}

View File

@@ -0,0 +1,92 @@
var parseExpr = require('../lib/Parser').parseExpr;
var assert = require('assert'),
inspect = require('util').inspect;
[
{ source: '',
expected: [],
what: 'Empty value'
},
{ source: '""',
expected: [''],
what: 'Empty quoted string'
},
{ source: 'FLAGS NIL RFC822.SIZE 44827',
expected: ['FLAGS', null, 'RFC822.SIZE', 44827],
what: 'Simple, two key-value pairs with nil'
},
{ source: 'FLAGS (\\Seen) RFC822.SIZE 44827',
expected: ['FLAGS', ['\\Seen'], 'RFC822.SIZE', 44827],
what: 'Simple, two key-value pairs with list'
},
{ source: 'RFC822.SIZE 9007199254740993',
expected: ['RFC822.SIZE', '9007199254740993'],
what: 'Integer exceeding JavaScript max int size'
},
{ source: 'FLAGS (\\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700"',
expected: ['FLAGS', ['\\Seen'], 'INTERNALDATE', '17-Jul-1996 02:44:25 -0700'],
what: 'Quoted string'
},
{ source: '("Foo")("Bar") ("Baz")',
expected: [['Foo'], ['Bar'], ['Baz']],
what: 'Lists with varying spacing'
},
{ source: '"\\"IMAP\\" is terrible :\\\\"',
expected: ['"IMAP" is terrible :\\'],
what: 'Quoted string with escaped chars'
},
{ source: '"\\\\"IMAP\\" is terrible :\\\\"',
expected: ['\\"IMAP" is terrible :\\'],
what: 'Quoted string with escaped chars #2'
},
{ source: '"Who does not think \\"IMAP\\" is terrible\\\\bad?"',
expected: ['Who does not think "IMAP" is terrible\\bad?'],
what: 'Quoted string with escaped chars #3'
},
{ source: '"Who does not think \\\\"IMAP\\" is terrible\\\\bad?"',
expected: ['Who does not think \\"IMAP" is terrible\\bad?'],
what: 'Quoted string with escaped chars #4'
},
{ source: 'ENVELOPE ("Wed, 30 Mar 2014 02:38:23 +0100" "=?ISO-8859-1?Q?##ALLCAPS##123456## - ?= =?ISO-8859-1?Q?[ALERT][P3][ONE.TWO.FR] ?= =?ISO-8859-1?Q?Some Subject Line \\"D:\\\\\\"?=" (("Test Account (Rltvty L)" NIL "account" "test.com")) (("Test Account (Rltvty L)" NIL "account" "test.com")) ((NIL NIL "account" "test.com")) ((NIL NIL "one.two" "test.fr") (NIL NIL "two.three" "test.fr")) NIL NIL NIL "<message@test.eu>")',
expected: [
'ENVELOPE',
[ 'Wed, 30 Mar 2014 02:38:23 +0100',
'=?ISO-8859-1?Q?##ALLCAPS##123456## - ?= =?ISO-8859-1?Q?[ALERT][P3][ONE.TWO.FR] ?= =?ISO-8859-1?Q?Some Subject Line "D:\\"?=',
[ [ 'Test Account (Rltvty L)', null, 'account', 'test.com' ] ],
[ [ 'Test Account (Rltvty L)', null, 'account', 'test.com' ] ],
[ [ null, null, 'account', 'test.com' ] ],
[ [ null, null, 'one.two', 'test.fr' ],
[ null, null, 'two.three', 'test.fr' ]
],
null,
null,
null,
'<message@test.eu>'
]
],
what: 'Triple backslash in quoted string (GH Issue #345)'
},
].forEach(function(v) {
var result;
try {
result = parseExpr(v.source);
} catch (e) {
console.log(makeMsg(v.what, 'JS Exception: ' + e.stack));
return;
}
assert.deepEqual(result,
v.expected,
makeMsg(v.what,
'Result mismatch:'
+ '\nParsed: ' + inspect(result, false, 10)
+ '\nExpected: ' + inspect(v.expected, false, 10)
)
);
});
function makeMsg(what, msg) {
return '[' + what + ']: ' + msg;
}

View File

@@ -0,0 +1,109 @@
var parseHeader = require('../lib/Parser').parseHeader;
var assert = require('assert'),
inspect = require('util').inspect;
var CRLF = '\r\n';
[
{ source: ['To: Foo', CRLF,
' Bar Baz', CRLF],
expected: { to: [ 'Foo Bar Baz' ] },
what: 'Folded header value (plain -- space)'
},
{ source: ['To: Foo', CRLF,
'\tBar\tBaz', CRLF],
expected: { to: [ 'Foo\tBar\tBaz' ] },
what: 'Folded header value (plain -- tab)'
},
{ source: ['Subject: =?iso-8859-1?Q?=A1Hola,_se=F1or!?=', CRLF],
expected: { subject: [ '¡Hola, señor!' ] },
what: 'MIME encoded-word in value'
},
{ source: ['Subject: =?iso-8859-1*es?Q?=A1Hola,_se=F1or!?=', CRLF],
expected: { subject: [ '¡Hola, señor!' ] },
what: 'MIME encoded-word in value with language set (RFC2231)'
},
{ source: ['Subject: =?iso-8859-1*?Q?=A1Hola,_se=F1or!?=', CRLF],
expected: { subject: [ '¡Hola, señor!' ] },
what: 'MIME encoded-word in value with empty language set'
},
{ source: ['Subject: =?GB2312?Q?=B2=E2=CA=D4=CC=E2=C4=BF=D3=EB=D6=D0=B9=FA=D0=C5_long_subjects_are_not_OK_12?=', CRLF,
' =?GB2312?Q?345678901234567890123456789012345678901234567890123456789012?=', CRLF,
' =?GB2312?Q?345678901234567890?=', CRLF],
expected: { subject: [ '测试题目与中国信 long subjects are not OK 12345678901234567890123456789012345678901234567890123456789012345678901234567890' ] },
what: 'Folded header value (adjacent MIME encoded-words)'
},
{ source: ['Subject: =?GB2312?Q?=B2=E2=CA=D4=CC=E2=C4=BF=D3=EB=D6=D0=B9=FA=D0=C5_long_subjects_are_not_OK_12?=', CRLF,
' 3=?GB2312?Q?45678901234567890123456789012345678901234567890123456789012?=', CRLF,
' 3=?GB2312?Q?45678901234567890?=', CRLF],
expected: { subject: [ '测试题目与中国信 long subjects are not OK 12 345678901234567890123456789012345678901234567890123456789012 345678901234567890' ] },
what: 'Folded header value (non-adjacent MIME encoded-words)'
},
{ source: ['Subject: =?GB2312?Q?=B2=E2=CA=D4=CC=E2=C4=BF=D3=EB=D6=D0=B9=FA=D0=C5_long_subjects_are_not_OK_12?=', CRLF,
' 3=?GB2312?Q?45678901234567890123456789012345678901234567890123456789012?=', CRLF,
' =?GB2312?Q?345678901234567890?=', CRLF],
expected: { subject: [ '测试题目与中国信 long subjects are not OK 12 345678901234567890123456789012345678901234567890123456789012345678901234567890' ] },
what: 'Folded header value (one adjacent, one non-adjacent MIME encoded-words)'
},
{ source: ['Subject: =?UTF-8?Q?=E0=B9=84=E0=B8=97=E0=B8=A2_=E0=B9=84?=', CRLF,
' ', CRLF,
' =?UTF-8?Q?=E0=B8=97=E0=B8=A2_=E0=B9=84=E0=B8=97?= =?UTF-8?Q?=E0=B8=A2?=', CRLF],
expected: { subject: [ 'ไทย ไทย ไทย' ] },
what: 'Folded header value (adjacent MIME encoded-words seperated by linear whitespace)'
},
{ source: ['Subject: =?utf-8?Q?abcdefghij_=E0=B9=83=E0=B8=99_klmnopqr_=E0=B9=84=E0=B8=A1=E0=B9?=', CRLF,
' =?utf-8?Q?=88=E0=B8=82=E0=B8=B6=E0=B9=89=E0=B8=99?=', CRLF],
expected: { subject: [ 'abcdefghij ใน klmnopqr ไม่ขึ้น' ] },
what: 'Folded header value (incomplete multi-byte character split)'
},
{ source: ['Subject: =?utf-8?B?Rlc6IOC4quC4tOC5iOC4h+C4oeC4tQ==?=', CRLF,
' =?utf-8?B?4LiK4Li14Lin4Li04LiV4Lir4LiZ4LmJ4Liy4LiV?=', CRLF,
' =?utf-8?B?4Liy4LmB4Lib4Lil4LiBIOC5hiDguKPguK3=?=', CRLF,
' =?utf-8?Q?=E0=B8=9A=E0=B9=82=E0=B8=A5=E0=B8=81?=', CRLF],
expected: { subject: [ 'FW: สิ่งมีชีวิตหน้าตาแปลก ๆ รอบโลก' ] },
what: 'Folded header value (consecutive complete base64-encoded words)'
},
{ source: ['Subject: =?utf-8?B?4Lij4Li54Lib4Lig4Liy4Lie4LiX4Li14LmIIGVtYmVkIOC5g+C4meC5gOC4?=', CRLF,
' =?utf-8?B?meC4t+C5ieC4reC5gOC4oeC4peC4peC5jOC5hOC4oeC5iOC5geC4quC4lOC4?=', CRLF,
' =?utf-8?B?hw==?=', CRLF],
expected: { subject: [ 'รูปภาพที่ embed ในเนื้อเมลล์ไม่แสดง' ] },
what: 'Folded header value (consecutive partial base64-encoded words)'
},
{ source: [' ', CRLF,
'To: Foo', CRLF],
expected: { to: [ 'Foo' ] },
what: 'Invalid first line'
},
// header with body
{ source: ['Subject: test subject', CRLF,
'X-Another-Header: test', CRLF,
CRLF,
'This is body: Not a header', CRLF],
expected: { subject: [ 'test subject' ], 'x-another-header': [ 'test' ] },
what: 'Header with the body'
},
].forEach(function(v) {
var result;
try {
result = parseHeader(v.source.join(''));
} catch (e) {
console.log(makeMsg(v.what, 'JS Exception: ' + e.stack));
return;
}
assert.deepEqual(result,
v.expected,
makeMsg(v.what,
'Result mismatch:'
+ '\nParsed: ' + inspect(result, false, 10)
+ '\nExpected: ' + inspect(v.expected, false, 10)
)
);
});
function makeMsg(what, msg) {
return '[' + what + ']: ' + msg;
}

View File

@@ -0,0 +1,543 @@
var Parser = require('../lib/Parser').Parser;
var assert = require('assert'),
crypto = require('crypto'),
inspect = require('util').inspect;
var CR = '\r', LF = '\n', CRLF = CR + LF;
[
{ source: ['A1 OK LOGIN completed', CRLF],
expected: [ { type: 'ok',
tagnum: 1,
textCode: undefined,
text: 'LOGIN completed'
}
],
what: 'Tagged OK'
},
{ source: ['IDLE OK IDLE terminated', CRLF],
expected: [ 'IDLE OK IDLE terminated' ],
what: 'Unknown line'
},
{ source: ['IDLE OK Idle completed (0.002 + 1.783 + 1.783 secs).', CRLF],
expected: [ 'IDLE OK Idle completed (0.002 + 1.783 + 1.783 secs).' ],
what: 'Unknown line with + char'
},
{ source: ['+ idling', CRLF],
expected: [ { textCode: undefined,
text: 'idling'
}
],
what: 'Continuation'
},
{ source: ['+ [ALERT] idling', CRLF],
expected: [ { textCode: 'ALERT',
text: 'idling'
}
],
what: 'Continuation with text code'
},
{ source: ['+', CRLF],
expected: [ { textCode: undefined,
text: undefined
}
],
what: 'Continuation (broken -- RFC violation) sent by AOL IMAP'
},
{ source: ['* NAMESPACE ',
'(("" "/")) ',
'(("~" "/")) ',
'(("#shared/" "/")("#public/" "/")("#ftp/" "/")("#news." "."))',
CRLF],
expected: [ { type: 'namespace',
num: undefined,
textCode: undefined,
text: {
personal: [
{ prefix: '',
delimiter: '/',
extensions: undefined
}
],
other: [
{ prefix: '~',
delimiter: '/',
extensions: undefined
}
],
shared: [
{ prefix: '#shared/',
delimiter: '/',
extensions: undefined
},
{ prefix: '#public/',
delimiter: '/',
extensions: undefined
},
{ prefix: '#ftp/',
delimiter: '/',
extensions: undefined
},
{ prefix: '#news.',
delimiter: '.',
extensions: undefined
}
]
}
}
],
what: 'Multiple namespaces'
},
{ source: ['* NAMESPACE ',
'(("" "/" "X-PARAM" ("FLAG1" "FLAG2"))) ',
'NIL ',
'NIL',
CRLF],
expected: [ { type: 'namespace',
num: undefined,
textCode: undefined,
text: {
personal: [
{ prefix: '',
delimiter: '/',
extensions: {
'X-PARAM': [ 'FLAG1', 'FLAG2' ]
}
}
],
other: null,
shared: null
}
}
],
what: 'Multiple namespaces'
},
{ source: ['* FLAGS (\\Answered \\Flagged \\Deleted \\Seen \\Draft)', CRLF],
expected: [ { type: 'flags',
num: undefined,
textCode: undefined,
text: [
'\\Answered',
'\\Flagged',
'\\Deleted',
'\\Seen',
'\\Draft'
]
}
],
what: 'Flags'
},
{ source: ['* SEARCH 2 3 6', CRLF],
expected: [ { type: 'search',
num: undefined,
textCode: undefined,
text: [ 2, 3, 6 ]
}
],
what: 'Search'
},
{ source: ['* XLIST (\\Noselect) "/" ~/Mail/foo', CRLF],
expected: [ { type: 'xlist',
num: undefined,
textCode: undefined,
text: {
flags: [ '\\Noselect' ],
delimiter: '/',
name: '~/Mail/foo'
}
}
],
what: 'XList'
},
{ source: ['* LIST (\\Noselect) "/" ~/Mail/foo', CRLF],
expected: [ { type: 'list',
num: undefined,
textCode: undefined,
text: {
flags: [ '\\Noselect' ],
delimiter: '/',
name: '~/Mail/foo'
}
}
],
what: 'List'
},
{ source: ['* STATUS blurdybloop (MESSAGES 231 UIDNEXT 44292)', CRLF],
expected: [ { type: 'status',
num: undefined,
textCode: undefined,
text: {
name: 'blurdybloop',
attrs: { messages: 231, uidnext: 44292 }
}
}
],
what: 'Status'
},
{ source: ['* OK [UNSEEN 17] Message 17 is the first unseen message', CRLF],
expected: [ { type: 'ok',
num: undefined,
textCode: {
key: 'UNSEEN',
val: 17
},
text: 'Message 17 is the first unseen message'
}
],
what: 'Untagged OK (with text code, with text)'
},
{ source: ['* OK [PERMANENTFLAGS (\\Deleted \\Seen \\*)] Limited', CRLF],
expected: [ { type: 'ok',
num: undefined,
textCode: {
key: 'PERMANENTFLAGS',
val: [ '\\Deleted', '\\Seen', '\\*' ]
},
text: 'Limited'
}
],
what: 'Untagged OK (with text code, with text)'
},
{ source: ['* OK [UNSEEN 17]', CRLF],
expected: [ { type: 'ok',
num: undefined,
textCode: {
key: 'UNSEEN',
val: 17
},
text: undefined
}
],
what: 'Untagged OK (no text code, with text) (RFC violation)'
},
{ source: ['* OK IMAP4rev1 Service Ready', CRLF],
expected: [ { type: 'ok',
num: undefined,
textCode: undefined,
text: 'IMAP4rev1 Service Ready'
}
],
what: 'Untagged OK (no text code, with text)'
},
{ source: ['* OK', CRLF], // I have seen servers that send stuff like this ..
expected: [ { type: 'ok',
num: undefined,
textCode: undefined,
text: undefined
}
],
what: 'Untagged OK (no text code, no text) (RFC violation)'
},
{ source: ['* 18 EXISTS', CRLF],
expected: [ { type: 'exists',
num: 18,
textCode: undefined,
text: undefined
}
],
what: 'Untagged EXISTS'
},
{ source: ['* 2 RECENT', CRLF],
expected: [ { type: 'recent',
num: 2,
textCode: undefined,
text: undefined
}
],
what: 'Untagged RECENT'
},
{ source: ['* 12 FETCH (BODY[HEADER] {342}', CRLF,
'Date: Wed, 17 Jul 1996 02:23:25 -0700 (PDT)', CRLF,
'From: Terry Gray <gray@cac.washington.edu>', CRLF,
'Subject: IMAP4rev1 WG mtg summary and minutes', CRLF,
'To: imap@cac.washington.edu', CRLF,
'cc: minutes@CNRI.Reston.VA.US, John Klensin <KLENSIN@MIT.EDU>', CRLF,
'Message-Id: <B27397-0100000@cac.washington.edu>', CRLF,
'MIME-Version: 1.0', CRLF,
'Content-Type: TEXT/PLAIN; CHARSET=US-ASCII', CRLF, CRLF,
')', CRLF],
expected: [ { seqno: 12,
which: 'HEADER',
size: 342
},
{ type: 'fetch',
num: 12,
textCode: undefined,
text: {}
}
],
bodySHA1s: ['1f96faf50f6410f99237791f9e3b89454bf93fa7'],
what: 'Untagged FETCH (body)'
},
{ source: ['* 12 FETCH (BODY[TEXT] "IMAP is terrible")', CRLF],
expected: [ { seqno: 12,
which: 'TEXT',
size: 16
},
{ type: 'fetch',
num: 12,
textCode: undefined,
text: {}
}
],
bodySHA1s: ['bac8a1528c133787a6969a10a1ff453ebb9adfc8'],
what: 'Untagged FETCH (quoted body)'
},
{ source: ['* 12 FETCH (BODY[TEXT] "\\"IMAP\\" is terrible :\\\\")', CRLF],
expected: [ { seqno: 12,
which: 'TEXT',
size: 21
},
{ type: 'fetch',
num: 12,
textCode: undefined,
text: {}
}
],
bodySHA1s: ['7570c08150050a404603f63f60b65b42378d7d42'],
what: 'Untagged FETCH (quoted body with escaped chars)'
},
{ source: ['* 12 FETCH (INTERNALDATE {26}', CRLF,
'17-Jul-1996 02:44:25 -0700)' + CRLF],
expected: [ { type: 'fetch',
num: 12,
textCode: undefined,
text: {
internaldate: new Date('17-Jul-1996 02:44:25 -0700')
}
}
],
what: 'Untagged FETCH with non-body literal'
},
{ source: ['* 12 FETCH (INTERNALDATE {2',
'6}' + CRLF + '17-Jul-1996 02:44:25 -0700)' + CRLF],
expected: [ { type: 'fetch',
num: 12,
textCode: undefined,
text: {
internaldate: new Date('17-Jul-1996 02:44:25 -0700')
}
}
],
what: 'Untagged FETCH with non-body literal (length split)'
},
{ source: ['* 12 FETCH (INTERNALDATE {26}', CRLF,
'17-Jul-1996 02:44:25 -0700)' + CR,
LF],
expected: [ { type: 'fetch',
num: 12,
textCode: undefined,
text: {
internaldate: new Date('17-Jul-1996 02:44:25 -0700')
}
}
],
what: 'Untagged FETCH with non-body literal (split CRLF)'
},
{ source: ['* 12 FETCH (FLAGS (\\Seen)',
' INTERNALDATE "17-Jul-1996 02:44:25 -0700"',
' RFC822.SIZE 4286',
' ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)"',
' "IMAP4rev1 WG mtg summary and minutes"',
' (("Terry Gray" NIL "gray" "cac.washington.edu"))',
' (("Terry Gray" NIL "gray" "cac.washington.edu"))',
' (("Terry Gray" NIL "gray" "cac.washington.edu"))',
' ((NIL NIL "imap" "cac.washington.edu"))',
' ((NIL NIL "minutes" "CNRI.Reston.VA.US")',
'("John Klensin" NIL "KLENSIN" "MIT.EDU")) NIL NIL',
' "<B27397-0100000@cac.washington.edu>")',
' BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 3028',
' 92))',
CRLF],
expected: [ { type: 'fetch',
num: 12,
textCode: undefined,
text: {
flags: [ '\\Seen' ],
internaldate: new Date('17-Jul-1996 02:44:25 -0700'),
'rfc822.size': 4286,
envelope: {
date: new Date('Wed, 17 Jul 1996 02:23:25 -0700 (PDT)'),
subject: 'IMAP4rev1 WG mtg summary and minutes',
from: [
{ name: 'Terry Gray',
mailbox: 'gray',
host: 'cac.washington.edu'
}
],
sender: [
{ name: 'Terry Gray',
mailbox: 'gray',
host: 'cac.washington.edu'
}
],
replyTo: [
{ name: 'Terry Gray',
mailbox: 'gray',
host: 'cac.washington.edu'
}
],
to: [
{ name: null,
mailbox: 'imap',
host: 'cac.washington.edu'
}
],
cc: [
{ name: null,
mailbox: 'minutes',
host: 'CNRI.Reston.VA.US'
},
{ name: 'John Klensin',
mailbox: 'KLENSIN',
host: 'MIT.EDU'
}
],
bcc: null,
inReplyTo: null,
messageId: '<B27397-0100000@cac.washington.edu>'
},
body: [
{ partID: '1',
type: 'text',
subtype: 'plain',
params: { charset: 'US-ASCII' },
id: null,
description: null,
encoding: '7BIT',
size: 3028,
lines: 92
}
]
}
}
],
what: 'Untagged FETCH (flags, date, size, envelope, body[structure])'
},
// EXTENSIONS ================================================================
{ source: ['* ESEARCH (TAG "A285") UID MIN 7 MAX 3800', CRLF],
expected: [ { type: 'esearch',
num: undefined,
textCode: undefined,
text: { min: 7, max: 3800 }
}
],
what: 'ESearch UID, 2 items'
},
{ source: ['* ESEARCH (TAG "A284") MIN 4', CRLF],
expected: [ { type: 'esearch',
num: undefined,
textCode: undefined,
text: { min: 4 }
}
],
what: 'ESearch 1 item'
},
{ source: ['* ESEARCH (TAG "A283") ALL 2,10:11', CRLF],
expected: [ { type: 'esearch',
num: undefined,
textCode: undefined,
text: { all: [ '2', '10:11' ] }
}
],
what: 'ESearch ALL list'
},
{ source: ['* QUOTA "" (STORAGE 10 512)', CRLF],
expected: [ { type: 'quota',
num: undefined,
textCode: undefined,
text: {
root: '',
resources: {
storage: { usage: 10, limit: 512 }
}
}
}
],
what: 'Quota'
},
{ source: ['* QUOTAROOT INBOX ""', CRLF],
expected: [ { type: 'quotaroot',
num: undefined,
textCode: undefined,
text: {
roots: [ '' ],
mailbox: 'INBOX'
}
}
],
what: 'QuotaRoot'
},
{ source: ['A1 OK', CRLF], // some servers like ppops.net sends such response
expected: [ { type: 'ok',
tagnum: 1,
textCode: undefined,
text: ''
}
],
what: 'Tagged OK (no text code, no text)'
},
].forEach(function(v) {
var ss = new require('stream').Readable(), p, result = [];
ss._read = function(){};
p = new Parser(ss);
p.on('tagged', function(info) {
result.push(info);
});
p.on('untagged', function(info) {
result.push(info);
});
p.on('continue', function(info) {
result.push(info);
});
p.on('other', function(line) {
result.push(line);
});
p.on('body', function(stream, info) {
result.push(info);
if (Array.isArray(v.bodySHA1s)) {
var hash = crypto.createHash('sha1');
stream.on('data', function(d) {
hash.update(d);
});
stream.on('end', function() {
var calculated = hash.digest('hex'),
expected = v.bodySHA1s.shift();
assert.equal(calculated,
expected,
makeMsg(v.what,
'Body SHA1 mismatch:'
+ '\nCalculated: ' + calculated
+ '\nExpected: ' + expected
)
);
});
} else
stream.resume();
});
try {
v.source.forEach(function(chunk) {
ss.push(chunk);
});
} catch (e) {
console.log(makeMsg(v.what, 'JS Exception: ' + e.stack));
return;
}
setImmediate(function() {
assert.deepEqual(result,
v.expected,
makeMsg(v.what,
'Result mismatch:'
+ '\nParsed: ' + inspect(result, false, 10)
+ '\nExpected: ' + inspect(v.expected, false, 10)
)
);
});
});
function makeMsg(what, msg) {
return '[' + what + ']: ' + msg;
}

4
nodered/rootfs/data/node_modules/imap/test/test.js generated vendored Normal file
View File

@@ -0,0 +1,4 @@
require('fs').readdirSync(__dirname).forEach(function(f) {
if (f.substr(0, 5).toLowerCase() === 'test-')
require('./' + f);
});