-
Notifications
You must be signed in to change notification settings - Fork 25
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
111 changed files
with
5,702 additions
and
13,895 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
"use strict"; | ||
|
||
module.exports = function(grunt) { | ||
|
||
// Project configuration. | ||
grunt.initConfig({ | ||
pkg: grunt.file.readJSON("package.json"), | ||
jshint: { | ||
all: ["Gruntfile.js", "lib/**/*.js", "index.js"], | ||
options: { | ||
"node": true, | ||
"globalstrict": true, | ||
"evil": true, | ||
"unused": true, | ||
"undef": true, | ||
"newcap": true | ||
} | ||
}, | ||
nodeunit: { | ||
all: ['tests/*.js'] | ||
} | ||
}); | ||
|
||
// Load the plugin that provides the "uglify" task. | ||
grunt.loadNpmTasks("grunt-contrib-jshint"); | ||
grunt.loadNpmTasks("grunt-contrib-nodeunit"); | ||
|
||
// Default task(s). | ||
grunt.registerTask("default", ["jshint", "nodeunit"]); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
Copyright (c) 2013 Andris Reinman | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,227 +1,101 @@ | ||
# toybird | ||
# hoodiecrow | ||
|
||
Toybird is supposed to be a scriptable IMAP server for client testing. | ||
Hoodiecrow is supposed to be a scriptable IMAP server for client testing. | ||
|
||
**NB** I won't update this brach anymore, I started rewriting toybird in the v2 branch (to be more modular, uses better IMAP parser etc). | ||
[](http://travis-ci.org/andris9/hoodiecrow) | ||
[](http://badge.fury.io/js/hoodiecrow) | ||
|
||
## Scope | ||
|
||
Toybird is a single user / multiple connections IMAP server that uses a JSON object as its directory and messages structure. Nothing is read from or written to disk and the entire directory structure is instantiated every time the server is started, eg. changes made through the IMAP protocol (adding/removing messages/flags etc) are not saved permanently. This should ensure that you can write unit tests for clients in a way where a new fresh server with unmodified data is started for every test. | ||
Hoodiecrow is a single user / multiple connections IMAP server that uses a JSON object as its directory and messages structure. Nothing is read from or written to disk and the entire directory structure is instantiated every time the server is started, eg. changes made through the IMAP protocol (adding/removing messages/flags etc) are not saved permanently. This should ensure that you can write unit tests for clients in a way where a new fresh server with unmodified data is started for every test. | ||
|
||
Several clients can connect to the server simultanously but all the clients share the same user account, even if login credentials are different. | ||
|
||
## Available commands | ||
|
||
* `CAPABILITY` | ||
* `LOGOUT` | ||
* `LOGIN` | ||
* `NOOP` | ||
* `CHECK` | ||
* `LIST` | ||
* `CREATE` | ||
* `DELETE` | ||
* `RENAME` | ||
* `LSUB` | ||
* `SUBSCRIBE` | ||
* `UNSUBSCRIBE` | ||
* `SELECT` | ||
* `EXAMINE` | ||
* `CLOSE` | ||
* `STATUS` | ||
* `FETCH` | ||
* `SEARCH` | ||
* `STORE` | ||
* `COPY` | ||
* `APPEND` | ||
* `EXPUNGE` | ||
* `UID FETCH` | ||
* `UID STORE` | ||
* `UID COPY` | ||
* `UID SEARCH` | ||
|
||
Supported extensions | ||
|
||
* `ID` | ||
* `IDLE` | ||
* `STARTTLS` | ||
* `LOGINDISABLED` | ||
|
||
## Usage | ||
|
||
Install toybird and run sample application. | ||
|
||
git clone [email protected]:andris9/toybird.git | ||
cd toybird | ||
npm install | ||
node example.js | ||
|
||
The sample application defines IMAP directory structure, enables ID and IDLE extensions and starts the server. When the server is running, the application creates a client that connects to it. The client lists available mailboxes, selects INBOX and fetches some message data from it. | ||
|
||
If you have the sample application running, you can try connecting to it with a Desktp IMAP client like Thunderbird (use host: `"localhost"`, port: `1234`, username: `"testuser"`, password: `"testpass"`). IMAP client should be able to list all existing messages, mark messages as read/unread, add-remove flags etc. | ||
|
||
## Example | ||
|
||
```javascript | ||
var toybird = require("toybird"); | ||
|
||
var server = toybird({ | ||
|
||
// enable non default extensions | ||
enabled: ["ID", "IDLE", "STARTTLS", "LOGINDISABLED"], | ||
|
||
// describe initial IMAP directory structure for this server instace | ||
directories: { | ||
"INBOX": { | ||
uidnext: 100, | ||
messages: [{ | ||
uid:1, | ||
internaldate: new Date(), | ||
body: "From: sender\nTo:Receiver\nSubject: Test\n\nHello world!" | ||
}] | ||
}, | ||
"Other":{ | ||
flags: ["\\Noselect"], | ||
directories: { | ||
"Sent mail":{}, | ||
"Not Subscribed":{ | ||
unsubscribed: true | ||
} | ||
} | ||
} | ||
} | ||
); | ||
|
||
// add authentication info for clients | ||
server.addUser("testuser", "testpass"); | ||
|
||
// start the server | ||
server.listen(143); | ||
``` | ||
## API | ||
Hoodiecrow is extendable, any command can be overwritten, plugins can be added etc (see command folder for built in command examples and plugin folder for plugin examples). | ||
|
||
Server API allows to modify server state at runtime. If needed, specific notices are sent to connected clients. | ||
## Authentication | ||
|
||
### Create new mailbox | ||
An user can always login with username `"testuser"` and password `"testpass"`. Any other credentials can be added as needed. | ||
|
||
```javascript | ||
server.createMailbox(path) | ||
``` | ||
## Status | ||
|
||
Where | ||
### IMAP4rev1 | ||
|
||
* `path` is the mailbox name (eg. *"Other/Sent Mail"*) | ||
* **FETCH** and **UID FETCH** support is partial | ||
* No **SEARCH** or **UID SEARCH** | ||
* Other commands should be more or less ready | ||
|
||
### Delete a mailbox | ||
I'm trying to get these done one by one. Most of it was already implemented in the previous incarnation of **hoodiecrow**, so I can copy and paste a lot. | ||
|
||
```javascript | ||
server.deleteMailbox(path) | ||
``` | ||
### Supported Plugins | ||
|
||
Where | ||
Plugins can be enabled when starting the server but can not be unloaded or loaded when the server is already running | ||
|
||
* `path` is the mailbox name (eg. *"Other/Sent Mail"*) | ||
* **AUTH=PLAIN** (supports **SASL-IR**, ignores **LOGINDISABLED**) | ||
* **CONDSTORE** partial, see below for CONDSTORE support | ||
* **ENABLE** | ||
* **ID** | ||
* **IDLE** | ||
* **LITERALPLUS** | ||
* **LOGINDISABLED** is effective with LOGIN when connection is unencrypted but does not affect AUTH=PLAIN | ||
* **NAMESPACE** no anonymous namespaces though | ||
* **SALS-IR** | ||
* **STARTTLS** | ||
* **UNSELECT** | ||
* **XTOYBIRD** to programmatically control Hoodiecrow through the IMAP protocol. Does not require login. | ||
|
||
### Add new message | ||
Planned but not yet implemented | ||
|
||
```javascript | ||
server.addMessage(path, message) | ||
``` | ||
* **SPECIAL-USE** (maybe **XLIST** as well but probably not) | ||
* **MOVE** | ||
* **UIDPLUS** | ||
* **QUOTA** | ||
* **X-GM-EXT-1** except for **SEARCH X-GM-RAW** | ||
* **AUTH=XOAUTH2** (maybe **AUTH=XOAUTH2** also) | ||
|
||
Where | ||
## Existing XTOYBIRD commands | ||
|
||
* `path` is the mailbox name (eg. *"Other/Sent Mail"*) | ||
* `message` is the message structure (eg. `{uid: 1, flags: ["\Seen"], body:"From:..."}`) | ||
* **XTOYBIRD SERVER** dumps server object as a LITERAL string. Useful for debugging current state. | ||
* **XTOYBIRD SERVER** dumps session object as a LITERAL string. Useful for debugging current state. | ||
* **XTOYBIRD STORAGE** outputs storage as a LITERAL strint (JSON). Useful for storing the storage for later usage. | ||
|
||
### Define custom SEARCH handlers | ||
## Useful features for Hoodiecrow I'd like to see | ||
|
||
You can define a search handler for any keyword. | ||
* An ability to change UIDVALIDITY at runtime (eg. `A1 XTOYBIRD UIDVALIDITY INBOX 123` where 123 is the new UIDVALIDITY for INBOX) | ||
* An ability to change available disk space (eg. `A1 XTOYBIRD DISKSPACE 100 50` where 100 is total disk space in bytes and 50 is available space) | ||
* An ability to restart the server to return initial state (`A1 XTOYBIRD RESET`) | ||
* An ability to change storage runtime by sending a JSON string describing the entire storage (`A1 XTOYBIRD UPDATE {123}\r\n{"":{"INBOX":{...}}})`) | ||
* Maybe even enabling/disabling plugins but this would require restarting the server | ||
|
||
```javascript | ||
server.setSearchHandler(keyword, handler) | ||
``` | ||
Where | ||
* `keyword` is the the search keyword (eg. *"SENTBEFORE"*) | ||
* `handler` (mailbox, message, index [, param1[, param2[,...paramN]]]) is the handler function for a message. If it returns true, the message is included in the search results | ||
Example | ||
```javascript | ||
// return every 5th message | ||
server.setSearchHandler("XFIFTH", function(mailbox, message, index){ | ||
// 'index' is a 1 based message sequence number | ||
return index % 5 == 0; | ||
}); | ||
// tag SEARCH XFIFTH | ||
// * SEARCH 5 10 15 20 | ||
C: A1 XTOYBIRD ENABLE ID UIDPLUS | ||
S: * XTOYBIRD ENABLED ID | ||
S: * XTOYBIRD ENABLED UIDPLUS | ||
S: A1 XTOYBIRD completed. Restart required | ||
C: A2 RESTART | ||
* BYE Server is Restarting | ||
``` | ||
|
||
```javascript | ||
// return messages with exact subject, overrides default SUBJECT | ||
// default behavior is to search messages with partial, case insensitive matches | ||
server.setSearchHandler("SUBJECT", function(mailbox, message, index, queryParam){ | ||
return message.structured.parsedHeader.subject == queryParam; | ||
}); | ||
// tag SEARCH SUBJECT "exact Match" | ||
// * SEARCH ...(messages with 'Subject: exact Match') | ||
``` | ||
## CONDSTORE support | ||
|
||
## Plugins | ||
There is some support for creating custom plugins in toybird. Plugins can be enabled with the `enabled` property by providing a function as the plugin. | ||
```javascript | ||
var server = toybird({enabled: [xDatePlugin]}); | ||
|
||
function xDatePlugin(server){ | ||
|
||
// Add XDATE to capability listing | ||
server.addCapability("XDATE", function(connection){ | ||
// allow only for logged in users, hide for others | ||
return connection.state != "Not Authenticated"; | ||
}); | ||
|
||
// Add XDATE command | ||
// Runnign 'tag XDATE' should return server time | ||
// C: A1 XDATE | ||
// S: * XDATE "Sun Sep 01 2013 14:36:51 GMT+0300 (EEST)" | ||
// S: A1 OK XDATE Completed (Success) | ||
server.addCommandHandler("XDATE", function(connection, tag, data, callback){ | ||
if(!connection.checkSupport("XDATE")){ // is the capability enabled? | ||
connection.send(tag, "BAD Unknown command: XDATE"); | ||
return callback(); | ||
} | ||
connection.send("*", "XDATE " + connection.escapeString(Date())); | ||
connection.processNotices(); // show untagged responses like EXPUNGED etc. | ||
connection.send(tag, "OK XDATE Completed (Success)"); | ||
callback(); | ||
}); | ||
|
||
// Add XRANDOM search keyword | ||
// Return random messages as search matches for 'tag SEARCH XRANDOM' | ||
// C: A1 SEARCH XRANDOM | ||
// S: * SEARCH ...(random list of messages) | ||
// S: A1 OK SEARCH Completed (Success) | ||
server.setSearchHandler("XRANDOM", function(mailbox, message, index){ | ||
return Math.random() >= 0.5; | ||
}); | ||
} | ||
``` | ||
* All messages have MODSEQ value | ||
* CONDSTORE can be ENABLEd | ||
* SELECT/EXAMINE show HIGHESTMODSEQ | ||
* SELECT/EXAMINE support (CONDSTORE) option | ||
* Updating flags increments MODSEQ value | ||
|
||
# Known issues | ||
|
||
See *lib/plugins* folder for samples | ||
These issues will be fixed | ||
|
||
## Issues | ||
* **CONDSTORE** support is partial | ||
|
||
These issues are low priority and might not get fixed any time soon | ||
These issues are probably not going to get fixed | ||
|
||
* Session flags (including `\Recent`) are not supported, all flags are permanent | ||
* The parser is way too forgiving, should be more strict | ||
* Optional charset parameter for `SEARCH` is ignored | ||
* Special case for `LSUB %` - if `"foo/bar"` is subscribed but `"foo"` is not, then listing `LSUB "" "%"` should return `* LSUB (\Noselect) foo` but toybox ignores the unsubscribed `"foo"` and skips it from the listing. | ||
* BODYSTRUCTURE is generated correctly even for complex messages but line number count is a bit off, not sure how this is exactly calculated (trim empty lines at end etc.?) | ||
* **addr-adl** (at-domain-list) values are not supported, NIL is always used | ||
* **anonymous namespaces** are not supported | ||
* **STORE** returns NO and nothing is updated if there are pending EXPUNGE messages | ||
|
||
# License | ||
|
||
**MIT** | ||
**MIT** |
Oops, something went wrong.