mirror of
https://github.com/mgerb/mywebsite
synced 2026-01-12 02:42:48 +00:00
updated bunch of file paths and changed the way posts are loaded
This commit is contained in:
20
node_modules/buildmail/.jshintrc
generated
vendored
Normal file
20
node_modules/buildmail/.jshintrc
generated
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"indent": 4,
|
||||
"node": true,
|
||||
"globalstrict": true,
|
||||
"evil": true,
|
||||
"unused": true,
|
||||
"undef": true,
|
||||
"newcap": true,
|
||||
"esnext": true,
|
||||
"curly": true,
|
||||
"eqeqeq": true,
|
||||
"expr": true,
|
||||
|
||||
"predef": [
|
||||
"describe",
|
||||
"it",
|
||||
"beforeEach",
|
||||
"afterEach"
|
||||
]
|
||||
}
|
||||
4
node_modules/buildmail/.npmignore
generated
vendored
Normal file
4
node_modules/buildmail/.npmignore
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
.DS_Store
|
||||
examples
|
||||
16
node_modules/buildmail/.travis.yml
generated
vendored
Normal file
16
node_modules/buildmail/.travis.yml
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
- 0.12
|
||||
- iojs
|
||||
before_install:
|
||||
- npm install -g grunt-cli
|
||||
notifications:
|
||||
email:
|
||||
- andris@kreata.ee
|
||||
webhooks:
|
||||
urls:
|
||||
- https://webhooks.gitter.im/e/0ed18fd9b3e529b3c2cc
|
||||
on_success: change # options: [always|never|change] default: always
|
||||
on_failure: always # options: [always|never|change] default: always
|
||||
on_start: false # default: false
|
||||
44
node_modules/buildmail/CHANGELOG.md
generated
vendored
Normal file
44
node_modules/buildmail/CHANGELOG.md
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
# Changelog
|
||||
|
||||
## v2.0.0 2015-10-06
|
||||
|
||||
* Replaced hyperquest with needle. Bumped major version as needle might behave a little bit different than hyperquest
|
||||
|
||||
## v1.3.0 2015-10-05
|
||||
|
||||
* Add non-standard name param to content-type header, otherwise non compliant clients like QQ do not understand file names
|
||||
* Bumped libmime version to get emoji support in filenames
|
||||
|
||||
## v1.2.5 2015-09-24
|
||||
|
||||
* Fixed a bug where ascii html content was not properly handled
|
||||
* Updated buildmail for stricter quoted printable encoding
|
||||
|
||||
## v1.2.4 2015-04-15
|
||||
|
||||
* Only use format=flowed with text/plain and not with other text/* stuff
|
||||
|
||||
## v1.2.3 2015-04-15
|
||||
|
||||
* Maintenace release, bumped dependency versions
|
||||
|
||||
## v1.2.2 2015-04-03
|
||||
|
||||
* Maintenace release, bumped libqp which resolves an endless loop in case of a trailing <CR>
|
||||
|
||||
## v1.2.1 2014-09-12
|
||||
|
||||
* Maintenace release, fixed a test and bumped dependency versions
|
||||
|
||||
## v1.2.0 2014-09-12
|
||||
|
||||
* Allow functions as transform plugins (the function should create a stream object)
|
||||
|
||||
## v1.1.1 2014-08-21
|
||||
|
||||
* Bumped libmime version to handle filenames with spaces properly. Short ascii only names with spaces were left unquoted.
|
||||
|
||||
## v1.1.0 2014-07-24
|
||||
|
||||
* Added new method `getAddresses` that returns all used addresses as a structured object
|
||||
* Changed version number scheme. Major is now 1 but it is not backwards incopatible with 0.x, as only the scheme changed but not the content
|
||||
29
node_modules/buildmail/Gruntfile.js
generated
vendored
Normal file
29
node_modules/buildmail/Gruntfile.js
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
module.exports = function(grunt) {
|
||||
'use strict';
|
||||
|
||||
// Project configuration.
|
||||
grunt.initConfig({
|
||||
jshint: {
|
||||
all: ['src/*.js', 'test/*.js'],
|
||||
options: {
|
||||
jshintrc: '.jshintrc'
|
||||
}
|
||||
},
|
||||
|
||||
mochaTest: {
|
||||
all: {
|
||||
options: {
|
||||
reporter: 'spec'
|
||||
},
|
||||
src: ['test/*-unit.js']
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Load the plugin(s)
|
||||
grunt.loadNpmTasks('grunt-contrib-jshint');
|
||||
grunt.loadNpmTasks('grunt-mocha-test');
|
||||
|
||||
// Tasks
|
||||
grunt.registerTask('default', ['jshint', 'mochaTest']);
|
||||
};
|
||||
19
node_modules/buildmail/LICENSE
generated
vendored
Normal file
19
node_modules/buildmail/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
Copyright (c) 2014-2015 Andris Reinman
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
554
node_modules/buildmail/README.md
generated
vendored
Normal file
554
node_modules/buildmail/README.md
generated
vendored
Normal file
@@ -0,0 +1,554 @@
|
||||
# buildmail
|
||||
|
||||
Low level rfc2822 message composer that streams output. Define your own mime tree, no magic included.
|
||||
|
||||
Ported from [MailBuild](https://github.com/whiteout-io/mailbuild) of the [emailjs.org](http://emailjs.org/) project. This port uses similar API but is for Node only and streams the output.
|
||||
|
||||
## Usage
|
||||
|
||||
Install with npm
|
||||
|
||||
npm install buildmail
|
||||
|
||||
Require in your scripts
|
||||
|
||||
```javascript
|
||||
var BuildMail = require('buildmail');
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
Create a new `BuildMail` object with
|
||||
|
||||
```javascript
|
||||
var builder = new BuildMail(contentType [, options]);
|
||||
```
|
||||
|
||||
Where
|
||||
|
||||
* **contentType** - define the content type for created node. Can be left blank for attachments (content type derived from `filename` option if available)
|
||||
* **options** - an optional options object
|
||||
* **filename** - *String* filename for an attachment node
|
||||
* **baseBoundary** - *String* shared part of the unique multipart boundary (generated randomly if not set)
|
||||
* **keepBcc** - *Boolean* If true keep the Bcc value in generated headers (default is to remove it)
|
||||
|
||||
## Methods
|
||||
|
||||
The same methods apply to the root node created with `new BuildMail()` and to any child nodes.
|
||||
|
||||
### createChild
|
||||
|
||||
Creates and appends a child node to the node object
|
||||
|
||||
```javascript
|
||||
node.createChild(contentType, options)
|
||||
```
|
||||
|
||||
The same arguments apply as with `new BuildMail()`. Created node object is returned.
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
new BuildMail('multipart/mixed').
|
||||
createChild('multipart/related').
|
||||
createChild('text/plain');
|
||||
```
|
||||
|
||||
Generates the following mime tree:
|
||||
|
||||
```
|
||||
multipart/mixed
|
||||
↳ multipart/related
|
||||
↳ text/plain
|
||||
```
|
||||
|
||||
### appendChild
|
||||
|
||||
Appends an existing child node to the node object. Removes the node from an existing tree if needed.
|
||||
|
||||
```javascript
|
||||
node.appendChild(childNode)
|
||||
```
|
||||
|
||||
Where
|
||||
|
||||
* **childNode** - child node to be appended
|
||||
|
||||
Method returns appended child node.
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
var childNode = new BuildMail('text/plain'),
|
||||
rootNode = new BuildMail('multipart/mixed');
|
||||
rootnode.appendChild(childNode);
|
||||
```
|
||||
|
||||
Generates the following mime tree:
|
||||
|
||||
```
|
||||
multipart/mixed
|
||||
↳ text/plain
|
||||
```
|
||||
|
||||
## replace
|
||||
|
||||
Replaces current node with another node
|
||||
|
||||
```javascript
|
||||
node.replace(replacementNode)
|
||||
```
|
||||
|
||||
Where
|
||||
|
||||
* **replacementNode** - node to replace the current node with
|
||||
|
||||
Method returns replacement node.
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
var rootNode = new BuildMail('multipart/mixed'),
|
||||
childNode = rootNode.createChild('text/plain');
|
||||
childNode.replace(new BuildMail('text/html'));
|
||||
```
|
||||
|
||||
Generates the following mime tree:
|
||||
|
||||
```
|
||||
multipart/mixed
|
||||
↳ text/html
|
||||
```
|
||||
|
||||
## remove
|
||||
|
||||
Removes current node from the mime tree. Does not make a lot of sense for a root node.
|
||||
|
||||
```javascript
|
||||
node.remove();
|
||||
```
|
||||
|
||||
Method returns removed node.
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
|
||||
var rootNode = new BuildMail('multipart/mixed'),
|
||||
childNode = rootNode.createChild('text/plain');
|
||||
childNode.remove();
|
||||
```
|
||||
|
||||
Generates the following mime tree:
|
||||
|
||||
```
|
||||
multipart/mixed
|
||||
```
|
||||
|
||||
## setHeader
|
||||
|
||||
Sets a header value. If the value for selected key exists, it is overwritten.
|
||||
|
||||
You can set multiple values as well by using `[{key:'', value:''}]` or
|
||||
`{key: 'value'}` structures as the first argument.
|
||||
|
||||
```javascript
|
||||
node.setHeader(key, value);
|
||||
```
|
||||
|
||||
Where
|
||||
|
||||
* **key** - *String|Array|Object* Header key or a list of key value pairs
|
||||
* **value** - *String* Header value
|
||||
|
||||
Method returns current node.
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
new BuildMail('text/plain').
|
||||
setHeader('content-disposition', 'inline').
|
||||
setHeader({
|
||||
'content-transfer-encoding': '7bit'
|
||||
}).
|
||||
setHeader([
|
||||
{key: 'message-id', value: 'abcde'}
|
||||
```
|
||||
|
||||
Generates the following header:
|
||||
|
||||
```
|
||||
Content-type: text/plain
|
||||
Content-Disposition: inline
|
||||
Content-Transfer-Encoding: 7bit
|
||||
Message-Id: <abcde>
|
||||
```
|
||||
|
||||
## addHeader
|
||||
|
||||
Adds a header value. If the value for selected key exists, the value is appended
|
||||
as a new field and old one is not touched.
|
||||
|
||||
You can set multiple values as well by using `[{key:'', value:''}]` or
|
||||
`{key: 'value'}` structures as the first argument.
|
||||
|
||||
```javascript
|
||||
node.addHeader(key, value);
|
||||
```
|
||||
|
||||
Where
|
||||
|
||||
* **key** - *String|Array|Object* Header key or a list of key value pairs
|
||||
* **value** - *String* Header value
|
||||
|
||||
Method returns current node.
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
new BuildMail('text/plain').
|
||||
addHeader('X-Spam', '1').
|
||||
setHeader({
|
||||
'x-spam': '2'
|
||||
}).
|
||||
setHeader([
|
||||
{key: 'x-spam', value: '3'}
|
||||
]);
|
||||
```
|
||||
|
||||
Generates the following header:
|
||||
|
||||
```
|
||||
Content-type: text/plain
|
||||
X-Spam: 1
|
||||
X-Spam: 2
|
||||
X-Spam: 3
|
||||
```
|
||||
|
||||
## getHeader
|
||||
|
||||
Retrieves the first mathcing value of a selected key
|
||||
|
||||
```javascript
|
||||
node.getHeader(key)
|
||||
```
|
||||
|
||||
Where
|
||||
|
||||
* **key** - *String* Key to search for
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
new BuildMail('text/plain').getHeader('content-type'); // text/plain
|
||||
```
|
||||
|
||||
## buildHeaders
|
||||
|
||||
Builds the current header info into a header block that can be used in an e-mail
|
||||
|
||||
```javascript
|
||||
var headers = node.buildHeaders()
|
||||
```
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
new BuildMail('text/plain').
|
||||
addHeader('X-Spam', '1').
|
||||
setHeader({
|
||||
'x-spam': '2'
|
||||
}).
|
||||
setHeader([
|
||||
{key: 'x-spam', value: '3'}
|
||||
]).buildHeaders();
|
||||
```
|
||||
|
||||
returns the following String
|
||||
|
||||
```
|
||||
Content-Type: text/plain
|
||||
X-Spam: 3
|
||||
Date: Sat, 21 Jun 2014 10:52:44 +0000
|
||||
Message-Id: <1403347964894-790a5296-0eb7c7c7-6440334f@localhost>
|
||||
MIME-Version: 1.0
|
||||
```
|
||||
|
||||
If the node is the root node, then `Date` and `Message-Id` values are generated automatically if missing
|
||||
|
||||
## setContent
|
||||
|
||||
Sets body content for current node. If the value is a string and Content-Type is text/* then charset is set automatically.
|
||||
If the value is a Buffer or a Stream you need to specify the charset yourself.
|
||||
|
||||
```javascript
|
||||
node.setContent(body)
|
||||
```
|
||||
|
||||
Where
|
||||
|
||||
* **body** - *String|Buffer|Stream|Object* body content
|
||||
|
||||
If the value is an object, it should include one of the following properties
|
||||
|
||||
* **path** - path to a file that will be used as the content
|
||||
* **href** - URL that will be used as the content
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
new BuildMail('text/plain').setContent('Hello world!');
|
||||
|
||||
new BuildMail('text/plain; charset=utf-8').setContent(fs.createReadStream('message.txt'));
|
||||
```
|
||||
|
||||
## build
|
||||
|
||||
Builds the rfc2822 message from the current node. If this is a root node, mandatory header fields are set if missing (Date, Message-Id, MIME-Version)
|
||||
|
||||
```javascript
|
||||
node.build(callback)
|
||||
```
|
||||
|
||||
Callback returns the rfc2822 message as a Buffer
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
new BuildMail('text/plain').setContent('Hello world!').build(function(err, mail){
|
||||
console.log(mail.toString('ascii'));
|
||||
});
|
||||
```
|
||||
|
||||
Returns the following string:
|
||||
|
||||
```
|
||||
Content-type: text/plain
|
||||
Date: <current datetime>
|
||||
Message-Id: <generated value>
|
||||
MIME-Version: 1.0
|
||||
|
||||
Hello world!
|
||||
```
|
||||
|
||||
## createReadStream
|
||||
|
||||
If you manage large attachments you probably do not want to generate but stream the message.
|
||||
|
||||
```javascript
|
||||
var stream = node.createReadStream(options)
|
||||
```
|
||||
|
||||
Where
|
||||
|
||||
* **options** - *Object* optional Stream options (ie. `highWaterMark`)
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
var message = new BuildMail();
|
||||
message.addHeader({
|
||||
from: 'From <from@example.com>',
|
||||
to: 'receiver1@example.com',
|
||||
cc: 'receiver2@example.com'
|
||||
});
|
||||
message.setContent(fs.createReadStream('message.txt'));
|
||||
message.createReadStream().pipe(fs.createWriteStream('message.eml'));
|
||||
```
|
||||
|
||||
## transform
|
||||
|
||||
If you want to modify the created stream, you can add transform streams that the output will be piped through.
|
||||
|
||||
```javascript
|
||||
node.transform(transformStream)
|
||||
```
|
||||
|
||||
Where
|
||||
|
||||
* **transformStream** - *Stream* or *Function* Transform stream that the output will go through before returing with `createReadStream`. If the value is a function the function should return a transform stream object when called.
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
var PassThrough = require('stream').PassThrough;
|
||||
var message = new BuildMail();
|
||||
message.addHeader({
|
||||
from: 'From <from@example.com>',
|
||||
to: 'receiver1@example.com',
|
||||
cc: 'receiver2@example.com'
|
||||
});
|
||||
message.setContent(fs.createReadStream('message.txt'));
|
||||
message.transform(new PassThrough()); // add a stream that the output will be piped through
|
||||
message.createReadStream().pipe(fs.createWriteStream('message.eml'));
|
||||
```
|
||||
|
||||
## setEnvelope
|
||||
|
||||
Set envelope object to use. If one is not set, it is generated based ong the headers.
|
||||
|
||||
```javascript
|
||||
node.setEnvelope(envelope)
|
||||
```
|
||||
|
||||
Where
|
||||
|
||||
* **envelope** is an envelope object in the form of `{from:'address', to: ['addresses']}`
|
||||
|
||||
## getEnvelope
|
||||
|
||||
Generates a SMTP envelope object. Makes sense only for root node.
|
||||
|
||||
```javascript
|
||||
var envelope = node.generateEnvelope()
|
||||
```
|
||||
|
||||
Method returns the envelope in the form of `{from:'address', to: ['addresses']}`
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
new BuildMail().
|
||||
addHeader({
|
||||
from: 'From <from@example.com>',
|
||||
to: 'receiver1@example.com',
|
||||
cc: 'receiver2@example.com'
|
||||
}).
|
||||
getEnvelope();
|
||||
```
|
||||
|
||||
Returns the following object:
|
||||
|
||||
```json
|
||||
{
|
||||
'from': 'from@example.com',
|
||||
'to': ['receiver1@example.com', 'receiver2@example.com']
|
||||
}
|
||||
```
|
||||
|
||||
## getAddresses
|
||||
|
||||
Returns an address container object. Includes all parsed addresses from From, Sender, To, Cc, Bcc and Reply-To fields.
|
||||
|
||||
While `getEnvelope()` returns 'from' value as a single address (the first one encountered) then `getAddresses` return all values as arrays, including `from`. Additionally while `getEnvelope` returns only `from` and a combined `to` value then `getAddresses` returns all fields separately.
|
||||
|
||||
Possbile return values (all arrays in the form of `[{name:'', address:''}]`):
|
||||
|
||||
* **from**
|
||||
* **sender**
|
||||
* **'reply-to'**
|
||||
* **to**
|
||||
* **cc**
|
||||
* **bcc**
|
||||
|
||||
If no addresses were found for a particular field, the field is not set in the response object.
|
||||
|
||||
**Example**
|
||||
|
||||
```javascript
|
||||
new BuildMail().
|
||||
addHeader({
|
||||
from: 'From <from@example.com>',
|
||||
to: '"Receiver" receiver1@example.com',
|
||||
cc: 'receiver2@example.com'
|
||||
}).
|
||||
getAddresses();
|
||||
```
|
||||
|
||||
Returns the following object:
|
||||
|
||||
```javascript
|
||||
{
|
||||
from: [{
|
||||
name: 'From',
|
||||
address: 'from@example.com'
|
||||
}],
|
||||
to: [{
|
||||
name: 'Receiver',
|
||||
address: 'receiver1@example.com'
|
||||
}],
|
||||
cc: [{
|
||||
name: '',
|
||||
address: 'receiver2@example.com'
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
### Addresses
|
||||
|
||||
When setting address headers (`From`, `To`, `Cc`, `Bcc`) use of unicode is allowed. If needed
|
||||
the addresses are converted to punycode automatically.
|
||||
|
||||
### Attachments
|
||||
|
||||
For attachments you should minimally set `filename` option and `Content-Disposition` header. If filename is specified, you can leave content type blank - if content type is not set, it is detected from the filename.
|
||||
|
||||
```javascript
|
||||
new BuildMail('multipart/mixed').
|
||||
createChild(false, {filename: 'image.png'}).
|
||||
setHeader('Content-Disposition', 'attachment');
|
||||
```
|
||||
|
||||
Obviously you might want to add `Content-Id` header as well if you want to reference this attachment from the HTML content.
|
||||
|
||||
### MIME structure
|
||||
|
||||
Most probably you only need to deal with the following multipart types when generating messages:
|
||||
|
||||
* **multipart/alternative** - includes the same content in different forms (usually text/plain + text/html)
|
||||
* **multipart/related** - includes main node and related nodes (eg. text/html + referenced attachments). Also requires a `type` parameter that indicates the Content-Type of the *root* element in the node
|
||||
* **multipart/mixed** - includes other multipart nodes and attachments, or single content node and attachments
|
||||
|
||||
**Examples**
|
||||
|
||||
One content node and an attachment
|
||||
|
||||
```
|
||||
multipart/mixed
|
||||
↳ text/plain
|
||||
↳ image/png
|
||||
```
|
||||
|
||||
Content node with referenced attachment (eg. image with `Content-Type` referenced by `cid:` url in the HTML)
|
||||
|
||||
```
|
||||
multipart/related
|
||||
↳ text/html
|
||||
↳ image/png
|
||||
```
|
||||
|
||||
Plaintext and HTML alternatives
|
||||
|
||||
```
|
||||
multipart/alternative
|
||||
↳ text/html
|
||||
↳ text/plain
|
||||
```
|
||||
|
||||
One content node with referenced attachment and a regular attachment
|
||||
|
||||
```
|
||||
multipart/mixed
|
||||
↳ multipart/related
|
||||
↳ text/plain
|
||||
↳ image/png
|
||||
↳ application/x-zip
|
||||
```
|
||||
|
||||
Alternative content with referenced attachment for HTML and a regular attachment
|
||||
|
||||
```
|
||||
multipart/mixed
|
||||
↳ multipart/alternative
|
||||
↳ text/plain
|
||||
↳ multipart/related
|
||||
↳ text/html
|
||||
↳ image/png
|
||||
↳ application/x-zip
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
**MIT**
|
||||
1
node_modules/buildmail/node_modules/.bin/needle
generated
vendored
Symbolic link
1
node_modules/buildmail/node_modules/.bin/needle
generated
vendored
Symbolic link
@@ -0,0 +1 @@
|
||||
../needle/bin/needle
|
||||
4
node_modules/buildmail/node_modules/needle/.npmignore
generated
vendored
Normal file
4
node_modules/buildmail/node_modules/needle/.npmignore
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
*~
|
||||
node_modules
|
||||
temp
|
||||
sandbox
|
||||
459
node_modules/buildmail/node_modules/needle/README.md
generated
vendored
Normal file
459
node_modules/buildmail/node_modules/needle/README.md
generated
vendored
Normal file
@@ -0,0 +1,459 @@
|
||||
Needle
|
||||
======
|
||||
|
||||
[](https://nodei.co/npm/needle/)
|
||||
|
||||
The leanest and most handsome HTTP client in the Nodelands.
|
||||
|
||||
```js
|
||||
var needle = require('needle');
|
||||
|
||||
needle.get('http://www.google.com', function(error, response) {
|
||||
if (!error && response.statusCode == 200)
|
||||
console.log(response.body);
|
||||
});
|
||||
```
|
||||
|
||||
Callbacks not floating your boat? Needle got your back.
|
||||
|
||||
```js
|
||||
var data = {
|
||||
file: '/home/johnlennon/walrus.png',
|
||||
content_type: 'image/png'
|
||||
};
|
||||
|
||||
needle
|
||||
.post('https://my.server.com/foo', data, { multipart: true })
|
||||
.on('readable', function() { /* eat your chunks */ })
|
||||
.on('end', function() {
|
||||
console.log('Ready-o, friend-o.');
|
||||
})
|
||||
```
|
||||
|
||||
With only one single dependency, Needle supports:
|
||||
|
||||
- HTTP/HTTPS requests, with the usual verbs you would expect
|
||||
- All of Node's native TLS options, such as 'rejectUnauthorized' (see below)
|
||||
- Basic & Digest authentication
|
||||
- Multipart form-data (e.g. file uploads)
|
||||
- HTTP Proxy forwarding, optionally with authentication
|
||||
- Streaming gzip or deflate decompression
|
||||
- Automatic XML & JSON parsing
|
||||
- 301/302/303 redirect following, with fine-grained tuning, and
|
||||
- Streaming non-UTF-8 charset decoding, via `iconv-lite`
|
||||
|
||||
And yes, Mr. Wayne, it does come with the latest streams2 support.
|
||||
|
||||
This makes Needle an ideal alternative for performing quick HTTP requests in Node, either for API interaction, downloading or uploading streams of data, and so on. If you need OAuth, AWS support or anything fancier, you should check out mikeal's request module.
|
||||
|
||||
Install
|
||||
-------
|
||||
|
||||
```
|
||||
$ npm install needle
|
||||
```
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
```js
|
||||
// using callback
|
||||
needle.get('ifconfig.me/all.json', function(error, response) {
|
||||
if (!error)
|
||||
console.log(response.body.ip_addr); // JSON decoding magic. :)
|
||||
});
|
||||
|
||||
// using streams
|
||||
var out = fs.createWriteStream('logo.png');
|
||||
needle.get('https://google.com/images/logo.png').pipe(out);
|
||||
```
|
||||
|
||||
As you can see, you can call Needle with a callback or without it. When passed, the response body will be buffered and written to `response.body`, and the callback will be fired when all of the data has been collected and processed (e.g. decompressed, decoded and/or parsed).
|
||||
|
||||
When no callback is passed, the buffering logic will be skipped but the response stream will still go through Needle's processing pipeline, so you get all the benefits of post-processing while keeping the streamishness we all love from Node.
|
||||
|
||||
Response pipeline
|
||||
-----------------
|
||||
|
||||
Depending on the response's Content-Type, Needle will either attempt to parse JSON or XML streams, or, if a text response was received, will ensure that the final encoding you get is UTF-8. For XML decoding to work, though, you'll need to install the `xml2js` package as we don't enforce unneeded dependencies unless strictly needed.
|
||||
|
||||
You can also request a gzip/deflated response, which, if sent by the server, will be processed before parsing or decoding is performed.
|
||||
|
||||
```js
|
||||
needle.get('http://stackoverflow.com/feeds', { compressed: true }, function(err, resp) {
|
||||
console.log(resp.body); // this little guy won't be a Gzipped binary blob
|
||||
// but a nice object containing all the latest entries
|
||||
});
|
||||
```
|
||||
|
||||
Or in anti-callback mode, using a few other options:
|
||||
|
||||
```js
|
||||
var options = {
|
||||
compressed : true, // sets 'Accept-Encoding' to 'gzip,deflate'
|
||||
follow_max : 5, // follow up to five redirects
|
||||
rejectUnauthorized : true // verify SSL certificate
|
||||
}
|
||||
|
||||
var stream = needle.get('https://backend.server.com/everything.html', options);
|
||||
|
||||
stream.on('readable', function() {
|
||||
while (data = this.read()) {
|
||||
console.log(data.toString());
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
All of Needle's request methods return a Readable stream, and both `options` and `callback` are optional. If passed, the callback will return three arguments: `error`, `response` and `body`, which is basically an alias for `response.body`.
|
||||
|
||||
### needle.head(url, [options,] callback)
|
||||
|
||||
```js
|
||||
var options = {
|
||||
open_timeout: 5000 // if we don't get our response headers in 5 seconds, boom.
|
||||
}
|
||||
|
||||
needle.head('https://my.backend.server.com', function(err, resp) {
|
||||
if (err)
|
||||
console.log('Shoot! Something is wrong: ' + err.message)
|
||||
else
|
||||
console.log('Yup, still alive.')
|
||||
})
|
||||
```
|
||||
|
||||
### needle.get(url, [options,] callback)
|
||||
|
||||
```js
|
||||
needle.get('google.com/search?q=syd+barrett', function(err, resp) {
|
||||
// if no http:// is found, Needle will automagically prepend it.
|
||||
});
|
||||
```
|
||||
|
||||
### needle.post(url, data, [options,] callback)
|
||||
|
||||
```js
|
||||
var options = {
|
||||
headers: { 'X-Custom-Header': 'Bumbaway atuna' }
|
||||
}
|
||||
|
||||
needle.post('https://my.app.com/endpoint', 'foo=bar', options, function(err, resp) {
|
||||
// you can pass params as a string or as an object.
|
||||
});
|
||||
```
|
||||
|
||||
### needle.put(url, data, [options,] callback)
|
||||
|
||||
```js
|
||||
var nested = {
|
||||
params: {
|
||||
are: {
|
||||
also: 'supported'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
needle.put('https://api.app.com/v2', nested, function(err, resp) {
|
||||
console.log('Got ' + resp.bytes + ' bytes.') // another nice treat from this handsome fella.
|
||||
});
|
||||
```
|
||||
|
||||
### needle.delete(url, data, [options,] callback)
|
||||
|
||||
```js
|
||||
var options = {
|
||||
username: 'fidelio',
|
||||
password: 'x'
|
||||
}
|
||||
|
||||
needle.delete('https://api.app.com/messages/123', null, options, function(err, resp) {
|
||||
// in this case, data may be null, but you need to explicity pass it.
|
||||
});
|
||||
```
|
||||
|
||||
### needle.request(method, url, data, [options,] callback)
|
||||
|
||||
Generic request. This not only allows for flexibility, but also lets you perform a GET request with data, in which case will be appended to the request as a query string.
|
||||
|
||||
```js
|
||||
var data = {
|
||||
q : 'a very smart query',
|
||||
page : 2,
|
||||
format : 'json'
|
||||
}
|
||||
|
||||
needle.request('get', 'forum.com/search', data, function(err, resp) {
|
||||
if (!err && resp.statusCode == 200)
|
||||
console.log(resp.body); // here you go, mister.
|
||||
});
|
||||
```
|
||||
|
||||
More examples after this short break.
|
||||
|
||||
Request options
|
||||
---------------
|
||||
|
||||
For information about options that've changed, there's always [the changelog](https://github.com/tomas/needle/releases).
|
||||
|
||||
- `open_timeout`: (or `timeout`) Returns error if connection takes longer than X milisecs to establish. Defaults to `10000` (10 secs). `0` means no timeout.
|
||||
- `read_timeout`: Returns error if data transfer takes longer than X milisecs, after connection is established. Defaults to `0` (no timeout).
|
||||
- `follow_max` : (or `follow`) Number of redirects to follow. Defaults to `0`. See below for more redirect options.
|
||||
- `multipart` : Enables multipart/form-data encoding. Defaults to `false`. Use it when uploading files.
|
||||
- `proxy` : Forwards request through HTTP(s) proxy. Eg. `proxy: 'http://user:pass@proxy.server.com:3128'`.
|
||||
- `agent` : Uses an http.Agent of your choice, instead of the global, default one.
|
||||
- `headers` : Object containing custom HTTP headers for request. Overrides defaults described below.
|
||||
- `auth` : Determines what to do with provided username/password. Options are `auto`, `digest` or `basic` (default). `auto` will detect the type of authentication depending on the response headers.
|
||||
- `json` : When `true`, sets content type to `application/json` and sends request body as JSON string, instead of a query string.
|
||||
|
||||
Response options
|
||||
----------------
|
||||
|
||||
- `decode_response` : (or `decode`) Whether to decode the text responses to UTF-8, if Content-Type header shows a different charset. Defaults to `true`.
|
||||
- `parse_response` : (or `parse`) Whether to parse XML or JSON response bodies automagically. Defaults to `true`. You can also set this to 'xml' or 'json' in which case Needle will *only* parse the response if the content type matches.
|
||||
- `output` : Dump response output to file. This occurs after parsing and charset decoding is done.
|
||||
|
||||
Note: To stay light on dependencies, Needle doesn't include the `xml2js` module used for XML parsing. To enable it, simply do `npm install xml2js`.
|
||||
|
||||
HTTP Header options
|
||||
-------------------
|
||||
|
||||
These are basically shortcuts to the `headers` option described above.
|
||||
|
||||
- `cookies` : Sets a {key: 'val'} object as a 'Cookie' header.
|
||||
- `compressed`: If `true`, sets 'Accept-Encoding' header to 'gzip,deflate', and inflates content if zipped. Defaults to `false`.
|
||||
- `username` : For HTTP basic auth.
|
||||
- `password` : For HTTP basic auth. Requires username to be passed, but is optional.
|
||||
- `accept` : Sets 'Accept' HTTP header. Defaults to `*/*`.
|
||||
- `connection`: Sets 'Connection' HTTP header. Defaults to `close`.
|
||||
- `user_agent`: Sets the 'User-Agent' HTTP header. Defaults to `Needle/{version} (Node.js {node_version})`.
|
||||
|
||||
Node.js TLS Options
|
||||
-------------------
|
||||
|
||||
These options are passed directly to `https.request` if present. Taken from the [original documentation](http://nodejs.org/docs/latest/api/https.html):
|
||||
|
||||
- `pfx` : Certificate, Private key and CA certificates to use for SSL.
|
||||
- `key` : Private key to use for SSL.
|
||||
- `passphrase` : A string of passphrase for the private key or pfx.
|
||||
- `cert` : Public x509 certificate to use.
|
||||
- `ca` : An authority certificate or array of authority certificates to check the remote host against.
|
||||
- `ciphers` : A string describing the ciphers to use or exclude.
|
||||
- `rejectUnauthorized` : If true, the server certificate is verified against the list of supplied CAs. An 'error' event is emitted if verification fails. Verification happens at the connection level, before the HTTP request is sent.
|
||||
- `secureProtocol` : The SSL method to use, e.g. SSLv3_method to force SSL version 3.
|
||||
|
||||
Redirect options
|
||||
----------------
|
||||
|
||||
These options only apply if the `follow_max` (or `follow`) option is higher than 0.
|
||||
|
||||
- `follow_set_cookies` : Sends the cookies received in the `set-cookie` header as part of the following request. `false` by default.
|
||||
- `follow_set_referer` : Sets the 'Referer' header to the requested URI when following a redirect. `false` by default.
|
||||
- `follow_keep_method` : If enabled, resends the request using the original verb instead of being rewritten to `get` with no data. `false` by default.
|
||||
- `follow_if_same_host` : When true, Needle will only follow redirects that point to the same host as the original request. `false` by default.
|
||||
- `follow_if_same_protocol` : When true, Needle will only follow redirects that point to the same protocol as the original request. `false` by default.
|
||||
|
||||
Overriding Defaults
|
||||
-------------------
|
||||
|
||||
Yes sir, we have it. Needle includes a `defaults()` method, that lets you override some of the defaults for all future requests. Like this:
|
||||
|
||||
```js
|
||||
needle.defaults({
|
||||
open_timeout: 60000,
|
||||
user_agent: 'MyApp/1.2.3',
|
||||
parse_response: false });
|
||||
```
|
||||
|
||||
This will override Needle's default user agent and 10-second timeout, and disable response parsing, so you don't need to pass those options in every other request.
|
||||
|
||||
Examples Galore
|
||||
---------------
|
||||
|
||||
### HTTPS GET with Basic Auth
|
||||
|
||||
```js
|
||||
needle.get('https://api.server.com', { username: 'you', password: 'secret' },
|
||||
function(err, resp) {
|
||||
// used HTTP auth
|
||||
});
|
||||
```
|
||||
|
||||
Or use [RFC-1738](http://tools.ietf.org/html/rfc1738#section-3.1) basic auth URL syntax:
|
||||
|
||||
```js
|
||||
needle.get('https://username:password@api.server.com', function(err, resp) {
|
||||
// used HTTP auth from URL
|
||||
});
|
||||
```
|
||||
|
||||
### Digest Auth
|
||||
|
||||
```js
|
||||
needle.get('other.server.com', { username: 'you', password: 'secret', auth: 'digest' },
|
||||
function(err, resp, body) {
|
||||
// needle prepends 'http://' to your URL, if missing
|
||||
});
|
||||
```
|
||||
|
||||
### Custom Accept header, deflate
|
||||
|
||||
```js
|
||||
var options = {
|
||||
compressed : true,
|
||||
follow : 10,
|
||||
accept : 'application/vnd.github.full+json'
|
||||
}
|
||||
|
||||
needle.get('api.github.com/users/tomas', options, function(err, resp, body) {
|
||||
// body will contain a JSON.parse(d) object
|
||||
// if parsing fails, you'll simply get the original body
|
||||
});
|
||||
```
|
||||
|
||||
### GET XML object
|
||||
|
||||
```js
|
||||
needle.get('https://news.ycombinator.com/rss', function(err, resp, body) {
|
||||
// if xml2js is installed, you'll get a nice object containing the nodes in the RSS
|
||||
});
|
||||
```
|
||||
|
||||
### GET binary, output to file
|
||||
|
||||
```js
|
||||
needle.get('http://upload.server.com/tux.png', { output: '/tmp/tux.png' }, function(err, resp, body) {
|
||||
// you can dump any response to a file, not only binaries.
|
||||
});
|
||||
```
|
||||
|
||||
### GET through proxy
|
||||
|
||||
```js
|
||||
needle.get('http://search.npmjs.org', { proxy: 'http://localhost:1234' }, function(err, resp, body) {
|
||||
// request passed through proxy
|
||||
});
|
||||
```
|
||||
|
||||
### GET a very large document in a stream (from 0.7+)
|
||||
|
||||
```js
|
||||
var stream = needle.get('http://www.as35662.net/100.log');
|
||||
|
||||
stream.on('readable', function() {
|
||||
var chunk;
|
||||
while (chunk = this.read()) {
|
||||
console.log('got data: ', chunk);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### GET JSON object in a stream (from 0.7+)
|
||||
|
||||
```js
|
||||
var stream = needle.get('http://jsonplaceholder.typicode.com/db', { parse: true });
|
||||
|
||||
stream.on('readable', function() {
|
||||
var node;
|
||||
|
||||
// our stream will only emit a single JSON root node.
|
||||
while (node = this.read()) {
|
||||
console.log('got data: ', node);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### GET JSONStream flexible parser with search query (from 0.7+)
|
||||
|
||||
```js
|
||||
|
||||
// The 'data' element of this stream will be the string representation
|
||||
// of the titles of all posts.
|
||||
|
||||
needle.get('http://jsonplaceholder.typicode.com/db', { parse: true })
|
||||
.pipe(new JSONStream.parse('posts.*.title'));
|
||||
.on('data', function (obj) {
|
||||
console.log('got post title: %s', obj);
|
||||
});
|
||||
```
|
||||
|
||||
### File upload using multipart, passing file path
|
||||
|
||||
```js
|
||||
var data = {
|
||||
foo: 'bar',
|
||||
image: { file: '/home/tomas/linux.png', content_type: 'image/png' }
|
||||
}
|
||||
|
||||
needle.post('http://my.other.app.com', data, { multipart: true }, function(err, resp, body) {
|
||||
// needle will read the file and include it in the form-data as binary
|
||||
});
|
||||
```
|
||||
|
||||
### Stream upload, PUT or POST
|
||||
|
||||
``` js
|
||||
needle.put('https://api.app.com/v2', fs.createReadStream('myfile.txt'), function(err, resp, body) {
|
||||
// stream content is uploaded verbatim
|
||||
});
|
||||
```
|
||||
|
||||
### Multipart POST, passing data buffer
|
||||
|
||||
```js
|
||||
var buffer = fs.readFileSync('/path/to/package.zip');
|
||||
|
||||
var data = {
|
||||
zip_file: {
|
||||
buffer : buffer,
|
||||
filename : 'mypackage.zip',
|
||||
content_type : 'application/octet-stream'
|
||||
}
|
||||
}
|
||||
|
||||
needle.post('http://somewhere.com/over/the/rainbow', data, { multipart: true }, function(err, resp, body) {
|
||||
// if you see, when using buffers we need to pass the filename for the multipart body.
|
||||
// you can also pass a filename when using the file path method, in case you want to override
|
||||
// the default filename to be received on the other end.
|
||||
});
|
||||
```
|
||||
|
||||
### Multipart with custom Content-Type
|
||||
|
||||
```js
|
||||
var data = {
|
||||
token: 'verysecret',
|
||||
payload: {
|
||||
value: JSON.stringify({ title: 'test', version: 1 }),
|
||||
content_type: 'application/json'
|
||||
}
|
||||
}
|
||||
|
||||
needle.post('http://test.com/', data, { timeout: 5000, multipart: true }, function(err, resp, body) {
|
||||
// in this case, if the request takes more than 5 seconds
|
||||
// the callback will return a [Socket closed] error
|
||||
});
|
||||
```
|
||||
|
||||
For even more examples, check out the examples directory in the repo.
|
||||
|
||||
### Testing
|
||||
|
||||
To run tests, you need to generate a self-signed SSL certificate in the `test` directory. After cloning the repository, run the following commands:
|
||||
|
||||
$ mkdir -p test/keys
|
||||
$ openssl genrsa -out test/keys/ssl.key 2048
|
||||
$ openssl req -new -key test/keys/ssl.key -x509 -days 999 -out test/keys/ssl.cert
|
||||
|
||||
Then you should be able to run `npm test` once you have the dependencies in place.
|
||||
|
||||
Credits
|
||||
-------
|
||||
|
||||
Written by Tomás Pollak, with the help of contributors.
|
||||
|
||||
Copyright
|
||||
---------
|
||||
|
||||
(c) Fork Ltd. Licensed under the MIT license.
|
||||
29
node_modules/buildmail/node_modules/needle/bin/needle
generated
vendored
Executable file
29
node_modules/buildmail/node_modules/needle/bin/needle
generated
vendored
Executable file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env node
|
||||
var needle = require('./../lib/needle');
|
||||
|
||||
var exit = function(code, str) {
|
||||
console.log(str) || process.exit(code);
|
||||
}
|
||||
|
||||
if (process.argv[2] == '-v' || process.argv[2] == '--version')
|
||||
exit(0, needle.version);
|
||||
else if (process.argv[3] == null)
|
||||
exit(1, 'Usage: needle [get|head|post|put|delete] url [query]');
|
||||
|
||||
var method = process.argv[2].toLowerCase(),
|
||||
url = process.argv[3],
|
||||
options = { compressed: true, parse: true, follow: true, timeout: 5000 };
|
||||
|
||||
var callback = function(err, resp) {
|
||||
if (err) return exit(1, "Error: " + err.message);
|
||||
|
||||
if (process.argv.indexOf('-i') != -1)
|
||||
console.log(resp.headers) || console.log('');
|
||||
|
||||
console.log(resp.body);
|
||||
};
|
||||
|
||||
if (method == 'post' || method == 'put')
|
||||
needle[method](url, process.argv[4], options, callback);
|
||||
else
|
||||
needle[method](url, options, callback);
|
||||
22
node_modules/buildmail/node_modules/needle/examples/deflated-stream.js
generated
vendored
Normal file
22
node_modules/buildmail/node_modules/needle/examples/deflated-stream.js
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
var fs = require('fs'),
|
||||
stream = require('stream'),
|
||||
needle = require('./../');
|
||||
|
||||
var url = 'http://ibl.gamechaser.net/f/tagqfxtteucbuldhezkz/bt_level1.gz';
|
||||
|
||||
var resp = needle.get(url, { compressed: true, follow: true });
|
||||
console.log('Downloading...');
|
||||
|
||||
resp.on('readable', function() {
|
||||
|
||||
while (data = this.read()) {
|
||||
var lines = data.toString().split('\n');
|
||||
console.log('Got ' + lines.length + ' items.');
|
||||
// console.log(lines);
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
resp.on('end', function(data) {
|
||||
console.log('Done');
|
||||
})
|
||||
16
node_modules/buildmail/node_modules/needle/examples/digest-auth.js
generated
vendored
Normal file
16
node_modules/buildmail/node_modules/needle/examples/digest-auth.js
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
var needle = require('./..');
|
||||
|
||||
var opts = {
|
||||
username: 'user3',
|
||||
password: 'user3',
|
||||
auth: 'digest'
|
||||
}
|
||||
|
||||
needle.get('http://test.webdav.org/auth-digest/', opts, function(err, resp, body){
|
||||
console.log(resp.headers);
|
||||
|
||||
if (resp.statusCode == 401)
|
||||
console.log('\nIt failed.')
|
||||
else
|
||||
console.log('\nIt worked!')
|
||||
});
|
||||
13
node_modules/buildmail/node_modules/needle/examples/download-to-file.js
generated
vendored
Normal file
13
node_modules/buildmail/node_modules/needle/examples/download-to-file.js
generated
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
var fs = require('fs'),
|
||||
needle = require('./..'),
|
||||
path = require('path');
|
||||
|
||||
var url = process.argv[2] || 'https://upload.wikimedia.org/wikipedia/commons/a/af/Tux.png';
|
||||
var file = path.basename(url);
|
||||
|
||||
console.log('Downloading ' + file);
|
||||
|
||||
needle.get(url, { output: file, follow: 3 }, function(err, resp, data){
|
||||
console.log('File saved: ' + process.cwd() + '/' + file);
|
||||
console.log(resp.bytes + ' bytes transferred.');
|
||||
});
|
||||
25
node_modules/buildmail/node_modules/needle/examples/multipart-stream.js
generated
vendored
Normal file
25
node_modules/buildmail/node_modules/needle/examples/multipart-stream.js
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
var needle = require('./../');
|
||||
|
||||
var url = 'http://posttestserver.com/post.php?dir=needle';
|
||||
|
||||
var black_pixel = new Buffer("R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=", 'base64');
|
||||
|
||||
var data = {
|
||||
foo: 'bar',
|
||||
nested: {
|
||||
test: 123
|
||||
},
|
||||
image: { buffer: black_pixel, content_type: 'image/gif' }
|
||||
}
|
||||
|
||||
var resp = needle.post(url, data, { multipart: true });
|
||||
|
||||
resp.on('readable', function() {
|
||||
while (data = this.read()) {
|
||||
console.log(data.toString());
|
||||
}
|
||||
})
|
||||
|
||||
resp.on('end', function(data) {
|
||||
console.log('Done.');
|
||||
})
|
||||
23
node_modules/buildmail/node_modules/needle/examples/parsed-stream.js
generated
vendored
Normal file
23
node_modules/buildmail/node_modules/needle/examples/parsed-stream.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
//////////////////////////////////////////
|
||||
// This example demonstrates what happends
|
||||
// when you use the built-in JSON parser.
|
||||
//////////////////////////////////////////
|
||||
|
||||
var fs = require('fs'),
|
||||
stream = require('stream'),
|
||||
needle = require('./../');
|
||||
|
||||
var url = 'http://ip.jsontest.com/',
|
||||
resp = needle.get(url, { parse: true });
|
||||
|
||||
resp.on('readable', function(obj) {
|
||||
var chunk;
|
||||
|
||||
while (chunk = this.read()) {
|
||||
console.log('root = ', chunk);
|
||||
}
|
||||
});
|
||||
|
||||
resp.on('end', function() {
|
||||
console.log('Done.');
|
||||
});
|
||||
21
node_modules/buildmail/node_modules/needle/examples/parsed-stream2.js
generated
vendored
Normal file
21
node_modules/buildmail/node_modules/needle/examples/parsed-stream2.js
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
//////////////////////////////////////////
|
||||
// This example illustrates a more complex
|
||||
// example of parsing a JSON stream.
|
||||
//////////////////////////////////////////
|
||||
|
||||
var needle = require('./../'),
|
||||
JSONStream = require('JSONStream');
|
||||
|
||||
var url = 'http://jsonplaceholder.typicode.com/db';
|
||||
|
||||
// Initialize our GET request with our default (JSON)
|
||||
// parsers disabled.
|
||||
|
||||
var json = new needle.get(url, {parse: false})
|
||||
// And now interpret the stream as JSON, returning only the
|
||||
// title of all the posts.
|
||||
.pipe(new JSONStream.parse('posts.*.title'));
|
||||
|
||||
json.on('data', function (obj) {
|
||||
console.log('got title: \'' + obj + '\'');
|
||||
})
|
||||
23
node_modules/buildmail/node_modules/needle/examples/stream-events.js
generated
vendored
Normal file
23
node_modules/buildmail/node_modules/needle/examples/stream-events.js
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
var needle = require('./..');
|
||||
|
||||
var resp = needle.get('google.com', { follow: true, timeout: 5000 });
|
||||
|
||||
resp.on('readable', function() {
|
||||
var chunk;
|
||||
while (chunk = this.read()) {
|
||||
console.log('Got ' + chunk.length + ' bytes');
|
||||
}
|
||||
})
|
||||
|
||||
resp.on('headers', function(headers) {
|
||||
console.log('Got headers', headers);
|
||||
})
|
||||
|
||||
resp.on('redirect', function(url) {
|
||||
console.log('Redirected to url ' + url);
|
||||
})
|
||||
|
||||
resp.on('end', function(err) {
|
||||
console.log('Finished. No more data to receive.');
|
||||
if (err) console.log('With error', err)
|
||||
})
|
||||
9
node_modules/buildmail/node_modules/needle/examples/stream-to-file.js
generated
vendored
Normal file
9
node_modules/buildmail/node_modules/needle/examples/stream-to-file.js
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
var fs = require('fs'),
|
||||
needle = require('./..'),
|
||||
path = require('path');
|
||||
|
||||
var url = process.argv[2] || 'http://www.google.com/images/errors/robot.png';
|
||||
var file = path.basename(url);
|
||||
|
||||
console.log('Downloading ' + file);
|
||||
needle.get(url).pipe(fs.createWriteStream(file));
|
||||
51
node_modules/buildmail/node_modules/needle/examples/upload-image.js
generated
vendored
Normal file
51
node_modules/buildmail/node_modules/needle/examples/upload-image.js
generated
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
var needle = require('../'),
|
||||
path = require('path');
|
||||
|
||||
var image = 'https://upload.wikimedia.org/wikipedia/commons/a/af/Tux.png';
|
||||
|
||||
function upload(obj, cb) {
|
||||
console.log('Uploading image...');
|
||||
|
||||
var url = 'http://deviantsart.com';
|
||||
|
||||
var opts = {
|
||||
timeout: 10000,
|
||||
follow: 3,
|
||||
multipart: true
|
||||
};
|
||||
|
||||
var params = {
|
||||
file: obj
|
||||
}
|
||||
|
||||
needle.post(url, params, opts, function(err, resp) {
|
||||
if (err || !resp.body.match('url'))
|
||||
return cb(err || new Error('No image URL found.'))
|
||||
|
||||
cb(null, JSON.parse(resp.body).url)
|
||||
})
|
||||
}
|
||||
|
||||
function download(url, cb) {
|
||||
console.log('Getting ' + url);
|
||||
needle.get(url, function(err, resp) {
|
||||
if (err) throw err;
|
||||
|
||||
cb(null, resp.body);
|
||||
})
|
||||
}
|
||||
|
||||
////////////////////////////////////////
|
||||
// ok, now go.
|
||||
|
||||
download(image, function(err, buffer) {
|
||||
if (err) throw err;
|
||||
|
||||
var obj = { buffer: buffer, content_type: 'image/png' };
|
||||
|
||||
upload(obj, function(err, url) {
|
||||
if (err) throw err;
|
||||
|
||||
console.log('Image uploaded to ' + url);
|
||||
})
|
||||
})
|
||||
106
node_modules/buildmail/node_modules/needle/lib/auth.js
generated
vendored
Normal file
106
node_modules/buildmail/node_modules/needle/lib/auth.js
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
var createHash = require('crypto').createHash;
|
||||
|
||||
function get_header(header, credentials, opts) {
|
||||
var type = header.split(' ')[0],
|
||||
user = credentials[0],
|
||||
pass = credentials[1];
|
||||
|
||||
if (type == 'Digest') {
|
||||
return digest.generate(header, user, pass, opts.method, opts.path);
|
||||
} else if (type == 'Basic') {
|
||||
return basic(user, pass);
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////
|
||||
// basic
|
||||
|
||||
function md5(string) {
|
||||
return createHash('md5').update(string).digest('hex');
|
||||
}
|
||||
|
||||
function basic(user, pass) {
|
||||
var str = typeof pass == 'undefined' ? user : [user, pass].join(':');
|
||||
return 'Basic ' + new Buffer(str).toString('base64');
|
||||
}
|
||||
|
||||
////////////////////
|
||||
// digest
|
||||
// logic inspired from https://github.com/simme/node-http-digest-client
|
||||
|
||||
var digest = {};
|
||||
|
||||
digest.parse_header = function(header) {
|
||||
var challenge = {},
|
||||
matches = header.match(/([a-z0-9_-]+)="([^"]+)"/gi);
|
||||
|
||||
for (var i = 0, l = matches.length; i < l; i++) {
|
||||
var pos = matches[i].indexOf('='),
|
||||
key = matches[i].substring(0, pos),
|
||||
val = matches[i].substring(pos + 1);
|
||||
challenge[key] = val.substring(1, val.length - 1);
|
||||
}
|
||||
|
||||
return challenge;
|
||||
}
|
||||
|
||||
digest.update_nc = function(nc) {
|
||||
var max = 99999999;
|
||||
nc++;
|
||||
|
||||
if (nc > max)
|
||||
nc = 1;
|
||||
|
||||
var padding = new Array(8).join('0') + '';
|
||||
nc = nc + '';
|
||||
return padding.substr(0, 8 - nc.length) + nc;
|
||||
}
|
||||
|
||||
digest.generate = function(header, user, pass, method, path) {
|
||||
|
||||
var nc = 1,
|
||||
cnonce = null,
|
||||
challenge = digest.parse_header(header);
|
||||
|
||||
var ha1 = md5(user + ':' + challenge.realm + ':' + pass),
|
||||
ha2 = md5(method + ':' + path),
|
||||
resp = [ha1, challenge.nonce];
|
||||
|
||||
if (typeof challenge.qop === 'string') {
|
||||
cnonce = md5(Math.random().toString(36)).substr(0, 8);
|
||||
nc = digest.update_nc(nc);
|
||||
resp = resp.concat(nc, cnonce);
|
||||
}
|
||||
|
||||
resp = resp.concat(challenge.qop, ha2);
|
||||
|
||||
var params = {
|
||||
username: user,
|
||||
realm: challenge.realm,
|
||||
nonce: challenge.nonce,
|
||||
uri: path,
|
||||
qop: challenge.qop,
|
||||
response: md5(resp.join(':'))
|
||||
}
|
||||
|
||||
// if (challenge.opaque) {
|
||||
// params.opaque = challenge.opaque;
|
||||
// }
|
||||
|
||||
if (cnonce) {
|
||||
params.nc = nc;
|
||||
params.cnonce = cnonce;
|
||||
}
|
||||
|
||||
header = []
|
||||
for (var k in params)
|
||||
header.push(k + '="' + params[k] + '"')
|
||||
|
||||
return 'Digest ' + header.join(', ');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
header : get_header,
|
||||
basic : basic,
|
||||
digest : digest.generate
|
||||
}
|
||||
81
node_modules/buildmail/node_modules/needle/lib/cookies.js
generated
vendored
Normal file
81
node_modules/buildmail/node_modules/needle/lib/cookies.js
generated
vendored
Normal file
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// Simple cookie handling implementation based on the standard RFC 6265.
|
||||
// This module just has two functionalities:
|
||||
// - Parse a set-cookie-header as a key value object
|
||||
// - Write a cookie-string from a key value object
|
||||
// All cookie attributes are ignored.
|
||||
//
|
||||
|
||||
// RegExps
|
||||
|
||||
var COOKIE_PAIR = /^([^=\s]+)\s*=\s*("?)\s*(.*)\s*\2\s*$/;
|
||||
var EXCLUDED_CHARS = /[\x00-\x1F\x7F\x3B\x3B\s\"\,\\"%]/g;
|
||||
var TRAILING_SEMICOLON = /\x3B+$/;
|
||||
var SEP_SEMICOLON = /\s*\x3B\s*/;
|
||||
|
||||
// Constants
|
||||
|
||||
var KEY_INDEX = 1; // index of key from COOKIE_PAIR match
|
||||
var VALUE_INDEX = 3; // index of value from COOKIE_PAIR match
|
||||
|
||||
// Convenience functions
|
||||
|
||||
// Returns a copy str trimmed and without trainling semicolon.
|
||||
function cleanCookieString(str) {
|
||||
return str.trim().replace(/\x3B+$/, '');
|
||||
}
|
||||
|
||||
function getFirstPair(str) {
|
||||
var index = str.indexOf('\x3B');
|
||||
return index === -1 ? str : str.substr(0, index);
|
||||
}
|
||||
|
||||
// Private functions
|
||||
|
||||
// Returns a encoded copy of str based on RFC6265 S4.1.1.
|
||||
function encodeCookieComponent(str) {
|
||||
return str.toString().replace(EXCLUDED_CHARS, encodeURIComponent);
|
||||
}
|
||||
|
||||
// Parses a set-cookie-string based on the standard definded in RFC6265 S4.1.1.
|
||||
function parseSetCookieString(str) {
|
||||
str = cleanCookieString(str);
|
||||
str = getFirstPair(str);
|
||||
|
||||
var res = COOKIE_PAIR.exec(str);
|
||||
|
||||
return {
|
||||
name: decodeURIComponent(res[KEY_INDEX]),
|
||||
value: decodeURIComponent(res[VALUE_INDEX])
|
||||
};
|
||||
}
|
||||
|
||||
// Parses a set-cookie-header and returns a key/value object. Each key
|
||||
// represent a name of a cookie.
|
||||
function parseSetCookieHeader(header) {
|
||||
header = (header instanceof Array) ? header : [header];
|
||||
|
||||
return header.reduce(function(res, str) {
|
||||
var cookie = parseSetCookieString(str);
|
||||
res[cookie.name] = cookie.value;
|
||||
return res;
|
||||
}, {});
|
||||
}
|
||||
|
||||
// Writes a set-cookie-string based on the standard definded in RFC6265 S4.1.1.
|
||||
function writeCookieString(obj) {
|
||||
return Object.keys(obj).reduce(function(str, name) {
|
||||
var encodedName = encodeCookieComponent(name);
|
||||
var encodedValue = encodeCookieComponent(obj[name]);
|
||||
str += (str ? "; " : "") + encodedName + '=' + encodedValue;
|
||||
return str;
|
||||
}, "");
|
||||
}
|
||||
|
||||
// Module interface
|
||||
|
||||
// returns a key/val object from an array of cookie strings
|
||||
exports.read = parseSetCookieHeader;
|
||||
|
||||
// writes a cookie string header
|
||||
exports.write = writeCookieString;
|
||||
53
node_modules/buildmail/node_modules/needle/lib/decoder.js
generated
vendored
Normal file
53
node_modules/buildmail/node_modules/needle/lib/decoder.js
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
var iconv,
|
||||
inherits = require('util').inherits,
|
||||
stream = require('stream');
|
||||
|
||||
var regex = /(?:charset|encoding)\s*=\s*['"]? *([\w\-]+)/i;
|
||||
|
||||
inherits(StreamDecoder, stream.Transform);
|
||||
|
||||
function StreamDecoder(charset) {
|
||||
if (!(this instanceof StreamDecoder))
|
||||
return new StreamDecoder(charset);
|
||||
|
||||
stream.Transform.call(this, charset);
|
||||
this.charset = charset;
|
||||
this.parsed_chunk = false;
|
||||
}
|
||||
|
||||
StreamDecoder.prototype._transform = function(chunk, encoding, done) {
|
||||
var res, found;
|
||||
|
||||
// try get charset from chunk, just once
|
||||
if (this.charset == 'iso-8859-1' && !this.parsed_chunk) {
|
||||
this.parsed_chunk = true;
|
||||
|
||||
var matches = regex.exec(chunk.toString());
|
||||
if (matches) {
|
||||
found = matches[1].toLowerCase();
|
||||
this.charset = found == 'utf-8' ? 'utf8' : found;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
res = iconv.decode(chunk, this.charset);
|
||||
} catch(e) { // something went wrong, just return original chunk
|
||||
res = chunk;
|
||||
}
|
||||
|
||||
this.push(res);
|
||||
done();
|
||||
}
|
||||
|
||||
module.exports = function(charset) {
|
||||
try {
|
||||
if (!iconv) iconv = require('iconv-lite');
|
||||
} catch(e) {
|
||||
/* iconv not found */
|
||||
}
|
||||
|
||||
if (iconv)
|
||||
return new StreamDecoder(charset);
|
||||
else
|
||||
return new stream.PassThrough;
|
||||
}
|
||||
96
node_modules/buildmail/node_modules/needle/lib/multipart.js
generated
vendored
Normal file
96
node_modules/buildmail/node_modules/needle/lib/multipart.js
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
var readFile = require('fs').readFile,
|
||||
basename = require('path').basename;
|
||||
|
||||
exports.build = function(data, boundary, callback) {
|
||||
|
||||
if (typeof data != 'object')
|
||||
return callback(new Error('Multipart builder expects data as key/val object.'));
|
||||
|
||||
var body = '',
|
||||
object = flatten(data),
|
||||
count = Object.keys(object).length;
|
||||
|
||||
if (count === 0)
|
||||
return callback(new Error('Empty multipart body. Invalid data.'))
|
||||
|
||||
function done(err, section) {
|
||||
if (err) return callback(err);
|
||||
if (section) body += section;
|
||||
--count || callback(null, body + '--' + boundary + '--');
|
||||
};
|
||||
|
||||
for (var key in object) {
|
||||
|
||||
var value = object[key];
|
||||
if (value === null || typeof value == 'undefined') {
|
||||
done();
|
||||
} else {
|
||||
var part = (value.buffer || value.file || value.content_type) ? value : {value: value};
|
||||
generate_part(key, part, boundary, done);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function generate_part(name, part, boundary, callback) {
|
||||
|
||||
var return_part = '--' + boundary + '\r\n';
|
||||
return_part += 'Content-Disposition: form-data; name="' + name + '"';
|
||||
|
||||
function append(data, filename) {
|
||||
|
||||
if (data) {
|
||||
var binary = part.content_type.indexOf('text') == -1;
|
||||
return_part += '; filename="' + encodeURIComponent(filename) + '"\r\n';
|
||||
if (binary) return_part += 'Content-Transfer-Encoding: binary\r\n';
|
||||
return_part += 'Content-Type: ' + part.content_type + '\r\n\r\n';
|
||||
return_part += binary ? data.toString('binary') : data.toString('utf8');
|
||||
}
|
||||
|
||||
callback(null, return_part + '\r\n');
|
||||
};
|
||||
|
||||
if ((part.file || part.buffer) && part.content_type) {
|
||||
|
||||
var filename = part.filename ? part.filename : part.file ? basename(part.file) : name;
|
||||
if (part.buffer) return append(part.buffer, filename);
|
||||
|
||||
readFile(part.file, function(err, data) {
|
||||
if (err) return callback(err);
|
||||
append(data, filename);
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
if (typeof part.value == 'object')
|
||||
return callback(new Error('Object received for ' + name + ', expected string.'))
|
||||
|
||||
if (part.content_type) {
|
||||
return_part += '\r\n';
|
||||
return_part += 'Content-Type: ' + part.content_type;
|
||||
}
|
||||
|
||||
return_part += '\r\n\r\n';
|
||||
return_part += part.value;
|
||||
append();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// flattens nested objects for multipart body
|
||||
function flatten(object, into, prefix) {
|
||||
into = into || {};
|
||||
|
||||
for(var key in object) {
|
||||
var prefix_key = prefix ? prefix + '[' + key + ']' : key;
|
||||
var prop = object[key];
|
||||
|
||||
if (prop && typeof prop === 'object' && !(prop.buffer || prop.file || prop.content_type))
|
||||
flatten(prop, into, prefix_key)
|
||||
else
|
||||
into[prefix_key] = prop;
|
||||
}
|
||||
|
||||
return into;
|
||||
}
|
||||
593
node_modules/buildmail/node_modules/needle/lib/needle.js
generated
vendored
Normal file
593
node_modules/buildmail/node_modules/needle/lib/needle.js
generated
vendored
Normal file
@@ -0,0 +1,593 @@
|
||||
//////////////////////////////////////////
|
||||
// Needle -- Node.js HTTP Client
|
||||
// Written by Tomás Pollak <tomas@forkhq.com>
|
||||
// (c) 2012-2015 - Fork Ltd.
|
||||
// MIT Licensed
|
||||
//////////////////////////////////////////
|
||||
|
||||
var fs = require('fs'),
|
||||
http = require('http'),
|
||||
https = require('https'),
|
||||
url = require('url'),
|
||||
stream = require('stream'),
|
||||
debug = require('debug')('needle'),
|
||||
stringify = require('./querystring').build,
|
||||
multipart = require('./multipart'),
|
||||
auth = require('./auth'),
|
||||
cookies = require('./cookies'),
|
||||
parsers = require('./parsers'),
|
||||
decoder = require('./decoder');
|
||||
|
||||
//////////////////////////////////////////
|
||||
// variabilia
|
||||
|
||||
var version = JSON.parse(fs.readFileSync(__dirname + '/../package.json').toString()).version;
|
||||
|
||||
var user_agent = 'Needle/' + version;
|
||||
user_agent += ' (Node.js ' + process.version + '; ' + process.platform + ' ' + process.arch + ')';
|
||||
|
||||
var tls_options = 'agent pfx key passphrase cert ca ciphers rejectUnauthorized secureProtocol';
|
||||
|
||||
//////////////////////////////////////////
|
||||
// decompressors for gzip/deflate bodies
|
||||
|
||||
var decompressors = {};
|
||||
|
||||
try {
|
||||
|
||||
var zlib = require('zlib')
|
||||
|
||||
decompressors['x-deflate'] = zlib.Inflate;
|
||||
decompressors['deflate'] = zlib.Inflate;
|
||||
decompressors['x-gzip'] = zlib.Gunzip;
|
||||
decompressors['gzip'] = zlib.Gunzip;
|
||||
|
||||
} catch(e) { /* zlib not available */ }
|
||||
|
||||
//////////////////////////////////////////
|
||||
// options and aliases
|
||||
|
||||
var defaults = {
|
||||
// data
|
||||
boundary : '--------------------NODENEEDLEHTTPCLIENT',
|
||||
encoding : 'utf8',
|
||||
parse : 'all', // same as true. valid options: 'json', 'xml' or false/null
|
||||
|
||||
// headers
|
||||
accept : '*/*',
|
||||
connection : 'close',
|
||||
user_agent : user_agent,
|
||||
|
||||
// numbers
|
||||
open_timeout : 10000,
|
||||
read_timeout : 0,
|
||||
follow_max : 0,
|
||||
|
||||
// booleans
|
||||
decode_response : true,
|
||||
follow_set_cookies : false,
|
||||
follow_set_referer : false,
|
||||
follow_keep_method : false,
|
||||
follow_if_same_host : false,
|
||||
follow_if_same_protocol : false
|
||||
}
|
||||
|
||||
var aliased = {
|
||||
options: {
|
||||
decode : 'decode_response',
|
||||
parse : 'parse_response',
|
||||
timeout : 'open_timeout',
|
||||
follow : 'follow_max'
|
||||
},
|
||||
inverted: {}
|
||||
}
|
||||
|
||||
// only once, invert aliased keys so we can get passed options.
|
||||
Object.keys(aliased.options).map(function(k) {
|
||||
var value = aliased.options[k];
|
||||
aliased.inverted[value] = k;
|
||||
});
|
||||
|
||||
//////////////////////////////////////////
|
||||
// helpers
|
||||
|
||||
function keys_by_type(type) {
|
||||
return Object.keys(defaults).map(function(el) {
|
||||
if (defaults[el].constructor == type)
|
||||
return el;
|
||||
}).filter(function(el) { return el })
|
||||
}
|
||||
|
||||
function parse_content_type(header) {
|
||||
if (!header || header == '') return {};
|
||||
|
||||
var charset = 'iso-8859-1', arr = header.split(';');
|
||||
try { charset = arr[1].match(/charset=(.+)/)[1] } catch (e) { /* not found */ }
|
||||
|
||||
return { type: arr[0], charset: charset };
|
||||
}
|
||||
|
||||
function is_stream(obj) {
|
||||
return typeof obj.pipe === 'function';
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// the main act
|
||||
|
||||
function Needle(method, uri, data, options, callback) {
|
||||
if (typeof uri !== 'string')
|
||||
throw new TypeError('URL must be a string, not ' + uri);
|
||||
|
||||
this.method = method;
|
||||
this.uri = uri;
|
||||
this.data = data;
|
||||
this.options = options;
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
Needle.prototype.setup = function(uri, options) {
|
||||
|
||||
function get_option(key, fallback) {
|
||||
// if original is in options, return that value
|
||||
if (typeof options[key] != 'undefined') return options[key];
|
||||
|
||||
// otherwise, return value from alias or fallback/undefined
|
||||
return typeof options[aliased.inverted[key]] != 'undefined'
|
||||
? options[aliased.inverted[key]] : fallback;
|
||||
}
|
||||
|
||||
function check_value(expected, key) {
|
||||
var value = get_option(key),
|
||||
type = typeof value;
|
||||
|
||||
if (type != 'undefined' && type != expected)
|
||||
throw new TypeError(type + ' received for ' + key + ', but expected a ' + expected);
|
||||
|
||||
return (type == expected) ? value : defaults[key];
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// the basics
|
||||
|
||||
var config = {
|
||||
http_opts : {}, // passed later to http.request() directly
|
||||
proxy : options.proxy,
|
||||
output : options.output,
|
||||
parser : get_option('parse_response', true),
|
||||
encoding : options.encoding || (options.multipart ? 'binary' : defaults.encoding)
|
||||
}
|
||||
|
||||
keys_by_type(Boolean).forEach(function(key) {
|
||||
config[key] = check_value('boolean', key);
|
||||
})
|
||||
|
||||
keys_by_type(Number).forEach(function(key) {
|
||||
config[key] = check_value('number', key);
|
||||
})
|
||||
|
||||
// populate http_opts with given TLS options
|
||||
tls_options.split(' ').forEach(function(key) {
|
||||
if (typeof options[key] != 'undefined') {
|
||||
config.http_opts[key] = options[key];
|
||||
if (typeof options.agent == 'undefined')
|
||||
config.http_opts.agent = false; // otherwise tls options are skipped
|
||||
}
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// headers, cookies
|
||||
|
||||
config.headers = {
|
||||
'Accept' : options.accept || defaults.accept,
|
||||
'Connection' : options.connection || defaults.connection,
|
||||
'User-Agent' : options.user_agent || defaults.user_agent
|
||||
}
|
||||
|
||||
if ((options.compressed || defaults.compressed) && typeof zlib != 'undefined')
|
||||
config.headers['Accept-Encoding'] = 'gzip,deflate';
|
||||
|
||||
if (options.cookies)
|
||||
config.headers['Cookie'] = cookies.write(options.cookies);
|
||||
|
||||
// now that all our headers are set, overwrite them if instructed.
|
||||
for (var h in options.headers)
|
||||
config.headers[h] = options.headers[h];
|
||||
|
||||
//////////////////////////////////////////////////
|
||||
// basic/digest auth
|
||||
|
||||
if (uri.indexOf('@') !== -1) { // url contains user:pass@host, so parse it.
|
||||
var parts = (url.parse(uri).auth || '').split(':');
|
||||
options.username = parts[0];
|
||||
options.password = parts[1];
|
||||
}
|
||||
|
||||
if (options.username) {
|
||||
if (options.auth && (options.auth == 'auto' || options.auth == 'digest')) {
|
||||
config.credentials = [options.username, options.password];
|
||||
} else {
|
||||
config.headers['Authorization'] = auth.basic(options.username, options.password);
|
||||
}
|
||||
}
|
||||
|
||||
// if proxy is present, set auth header from either url or proxy_user option.
|
||||
if (config.proxy) {
|
||||
if (config.proxy.indexOf('http') === -1)
|
||||
config.proxy = 'http://' + config.proxy;
|
||||
|
||||
if (config.proxy.indexOf('@') !== -1) {
|
||||
var parts = (url.parse(config.proxy).auth || '').split(':');
|
||||
options.proxy_user = parts[0];
|
||||
options.proxy_pass = parts[1];
|
||||
}
|
||||
|
||||
if (options.proxy_user)
|
||||
config.headers['Proxy-Authorization'] = auth.basic(options.proxy_user, options.proxy_pass);
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
Needle.prototype.start = function() {
|
||||
|
||||
var self = this,
|
||||
out = new stream.PassThrough({ objectMode: false }),
|
||||
uri = this.uri,
|
||||
data = this.data,
|
||||
method = this.method,
|
||||
callback = (typeof this.options == 'function') ? this.options : this.callback,
|
||||
options = this.options || {};
|
||||
|
||||
// if no 'http' is found on URL, prepend it.
|
||||
if (uri.indexOf('http') === -1)
|
||||
uri = uri.replace(/^(\/\/)?/, 'http://');
|
||||
|
||||
var config = this.setup(uri, options);
|
||||
|
||||
if (data) {
|
||||
if (method.toUpperCase() == 'GET') { // build query string and append to URI
|
||||
|
||||
uri = uri.replace(/\?.*|$/, '?' + stringify(data));
|
||||
post_data = null;
|
||||
|
||||
} else if (options.multipart) { // build multipart body for request
|
||||
|
||||
var boundary = options.boundary || defaults.boundary;
|
||||
|
||||
multipart.build(data, boundary, function(err, body) {
|
||||
if (err) throw(err);
|
||||
|
||||
config.headers['Content-Type'] = 'multipart/form-data; boundary=' + boundary;
|
||||
config.headers['Content-Length'] = body.length;
|
||||
self.send_request(1, method, uri, config, body, out, callback);
|
||||
});
|
||||
|
||||
return out; // stream
|
||||
|
||||
} else if (is_stream(data) || Buffer.isBuffer(data)) {
|
||||
|
||||
post_data = data;
|
||||
|
||||
} else { // string or object data, no multipart.
|
||||
|
||||
// if no content-type was passed, determine if json or not.
|
||||
if (!config.headers['Content-Type']) {
|
||||
config.headers['Content-Type'] = options.json
|
||||
? 'application/json; charset=utf-8'
|
||||
: 'application/x-www-form-urlencoded'; // no charset says W3 spec.
|
||||
}
|
||||
|
||||
// format post_data and build a buffer out of it.
|
||||
var post_data = options.json ? JSON.stringify(data) : stringify(data);
|
||||
post_data = new Buffer(post_data, config.encoding);
|
||||
config.headers['Content-Length'] = post_data.length;
|
||||
|
||||
// unless a specific accept header was passed, assume json wants json back.
|
||||
if (options.json && config.headers['Accept'] === defaults.accept)
|
||||
config.headers['Accept'] = 'application/json';
|
||||
}
|
||||
}
|
||||
|
||||
return this.send_request(1, method, uri, config, post_data, out, callback);
|
||||
}
|
||||
|
||||
Needle.prototype.get_request_opts = function(method, uri, config) {
|
||||
var opts = config.http_opts,
|
||||
proxy = config.proxy,
|
||||
remote = proxy ? url.parse(proxy) : url.parse(uri);
|
||||
|
||||
opts.protocol = remote.protocol;
|
||||
opts.host = remote.hostname;
|
||||
opts.port = remote.port || (remote.protocol == 'https:' ? 443 : 80);
|
||||
opts.path = proxy ? uri : remote.pathname + (remote.search || '');
|
||||
opts.method = method;
|
||||
opts.headers = config.headers;
|
||||
|
||||
if (!opts.headers['Host']) {
|
||||
// if using proxy, make sure the host header shows the final destination
|
||||
var target = proxy ? url.parse(uri) : remote;
|
||||
opts.headers['Host'] = target.hostname;
|
||||
|
||||
// and if a non standard port was passed, append it to the port header
|
||||
if (target.port && [80, 443].indexOf(target.port) === -1) {
|
||||
opts.headers['Host'] += ':' + target.port;
|
||||
}
|
||||
}
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
Needle.prototype.should_follow = function(location, config, original) {
|
||||
if (!location) return false;
|
||||
|
||||
// returns true if location contains matching property (host or protocol)
|
||||
function matches(property) {
|
||||
var property = original[property];
|
||||
return location.indexOf(property) !== -1;
|
||||
}
|
||||
|
||||
// first, check whether the requested location is actually different from the original
|
||||
if (location === original)
|
||||
return false;
|
||||
|
||||
if (config.follow_if_same_host && !matches('host'))
|
||||
return false; // host does not match, so not following
|
||||
|
||||
if (config.follow_if_same_protocol && !matches('protocol'))
|
||||
return false; // procotol does not match, so not following
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Needle.prototype.send_request = function(count, method, uri, config, post_data, out, callback) {
|
||||
|
||||
var timer,
|
||||
returned = 0,
|
||||
self = this,
|
||||
request_opts = this.get_request_opts(method, uri, config),
|
||||
protocol = request_opts.protocol == 'https:' ? https : http;
|
||||
|
||||
function done(err, resp, body) {
|
||||
if (returned++ > 0) return;
|
||||
|
||||
if (timer) clearTimeout(timer);
|
||||
request.removeListener('error', had_error);
|
||||
|
||||
if (callback)
|
||||
callback(err, resp, body);
|
||||
else
|
||||
out.emit('end', err, resp, body);
|
||||
}
|
||||
|
||||
function had_error(err) {
|
||||
debug('Request error', err);
|
||||
done(err || new Error('Unknown error when making request.'));
|
||||
}
|
||||
|
||||
function set_timeout(milisecs) {
|
||||
if (milisecs <= 0) return;
|
||||
timer = setTimeout(function() { request.abort() }, milisecs);
|
||||
}
|
||||
|
||||
debug('Making request #' + count, request_opts);
|
||||
var request = protocol.request(request_opts, function(resp) {
|
||||
|
||||
var headers = resp.headers;
|
||||
debug('Got response', resp.statusCode, headers);
|
||||
|
||||
// clear open timeout, and set a read timeout.
|
||||
if (timer) clearTimeout(timer);
|
||||
set_timeout(config.read_timeout);
|
||||
|
||||
if (headers['set-cookie']) {
|
||||
resp.cookies = cookies.read(headers['set-cookie']);
|
||||
debug('Got cookies', resp.cookies);
|
||||
}
|
||||
|
||||
// if redirect code is found, send a GET request to that location if enabled via 'follow' option
|
||||
if ([301, 302, 303].indexOf(resp.statusCode) !== -1 && self.should_follow(headers.location, config, uri)) {
|
||||
|
||||
if (count <= config.follow_max) {
|
||||
out.emit('redirect', headers.location);
|
||||
|
||||
// unless follow_keep_method was set to true, rewrite the request to GET before continuing
|
||||
if (!config.follow_keep_method) {
|
||||
method = 'GET';
|
||||
post_data = null;
|
||||
delete config.headers['Content-Length']; // in case the original was a multipart POST request.
|
||||
}
|
||||
|
||||
if (config.follow_set_cookies && resp.cookies)
|
||||
config.headers['Cookie'] = cookies.write(resp.cookies);
|
||||
|
||||
if (config.follow_set_referer)
|
||||
config.headers['Referer'] = uri;
|
||||
|
||||
config.headers['Host'] = null; // clear previous Host header to avoid conflicts.
|
||||
|
||||
debug('Redirecting to ' + url.resolve(uri, headers.location));
|
||||
return self.send_request(++count, method, url.resolve(uri, headers.location), config, post_data, out, callback);
|
||||
} else if (config.follow_max > 0) {
|
||||
return done(new Error('Max redirects reached. Possible loop in: ' + headers.location));
|
||||
}
|
||||
}
|
||||
|
||||
// if authentication is requested and credentials were not passed, resend request if we have user/pass
|
||||
if (resp.statusCode == 401 && headers['www-authenticate'] && config.credentials) {
|
||||
if (!config.headers['Authorization']) { // only if authentication hasn't been sent
|
||||
var auth_header = auth.header(headers['www-authenticate'], config.credentials, request_opts);
|
||||
|
||||
if (auth_header) {
|
||||
config.headers['Authorization'] = auth_header;
|
||||
return self.send_request(count, method, uri, config, post_data, out, callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ok so we got a valid (non-redirect & authorized) response. notify the stream guys.
|
||||
out.emit('headers', headers);
|
||||
|
||||
var pipeline = [],
|
||||
mime = parse_content_type(headers['content-type']),
|
||||
text_response = mime.type && mime.type.indexOf('text/') != -1;
|
||||
|
||||
// To start, if our body is compressed and we're able to inflate it, do it.
|
||||
if (headers['content-encoding'] && decompressors[headers['content-encoding']]) {
|
||||
pipeline.push(decompressors[headers['content-encoding']]());
|
||||
}
|
||||
|
||||
// If parse is enabled and we have a parser for it, then go for it.
|
||||
if (config.parser && parsers[mime.type]) {
|
||||
|
||||
// If a specific parser was requested, make sure we don't parse other types.
|
||||
var parser_name = config.parser.toString().toLowerCase();
|
||||
if (['xml', 'json'].indexOf(parser_name) == -1 || parsers[mime.type].name == parser_name) {
|
||||
|
||||
// OK, so either we're parsing all content types or the one requested matches.
|
||||
out.parser = parsers[mime.type].name;
|
||||
pipeline.push(parsers[mime.type].fn());
|
||||
|
||||
// Set objectMode on out stream to improve performance.
|
||||
out._writableState.objectMode = true;
|
||||
out._readableState.objectMode = true;
|
||||
}
|
||||
|
||||
// If we're not parsing, and unless decoding was disabled, we'll try
|
||||
// decoding non UTF-8 bodies to UTF-8, using the iconv-lite library.
|
||||
} else if (text_response && config.decode_response
|
||||
&& mime.charset && !mime.charset.match(/utf-?8$/i)) {
|
||||
pipeline.push(decoder(mime.charset));
|
||||
}
|
||||
|
||||
// And `out` is the stream we finally push the decoded/parsed output to.
|
||||
pipeline.push(out);
|
||||
|
||||
// Now, release the kraken!
|
||||
var tmp = resp;
|
||||
while (pipeline.length) {
|
||||
tmp = tmp.pipe(pipeline.shift());
|
||||
}
|
||||
|
||||
// If the user has requested and output file, pipe the output stream to it.
|
||||
// In stream mode, we will still get the response stream to play with.
|
||||
if (config.output && resp.statusCode == 200) {
|
||||
resp.pipe(fs.createWriteStream(config.output))
|
||||
}
|
||||
|
||||
// Only aggregate the full body if a callback was requested.
|
||||
if (callback) {
|
||||
resp.raw = [];
|
||||
resp.body = [];
|
||||
resp.bytes = 0;
|
||||
|
||||
// Count the amount of (raw) bytes passed using a PassThrough stream.
|
||||
var clean_pipe = new stream.PassThrough();
|
||||
resp.pipe(clean_pipe);
|
||||
|
||||
clean_pipe.on('readable', function() {
|
||||
var chunk;
|
||||
while (chunk = this.read()) {
|
||||
resp.bytes += chunk.length;
|
||||
resp.raw.push(chunk);
|
||||
}
|
||||
})
|
||||
|
||||
// Listen on the 'readable' event to aggregate the chunks.
|
||||
out.on('readable', function() {
|
||||
var chunk;
|
||||
while ((chunk = this.read()) !== null) {
|
||||
// We're either pushing buffers or objects, never strings.
|
||||
if (typeof chunk == 'string') chunk = new Buffer(chunk);
|
||||
|
||||
// Push all chunks to resp.body. We'll bind them in resp.end().
|
||||
resp.body.push(chunk);
|
||||
}
|
||||
})
|
||||
|
||||
// And set the .body property once all data is in.
|
||||
out.on('end', function() {
|
||||
// we may want access to the raw data, so keep a reference.
|
||||
resp.raw = Buffer.concat(resp.raw);
|
||||
|
||||
// if parse was successful, we should have an array with one object
|
||||
if (resp.body[0] !== undefined && !Buffer.isBuffer(resp.body[0])) {
|
||||
|
||||
// that's our body right there.
|
||||
resp.body = resp.body[0];
|
||||
|
||||
// set the parser property on our response. we may want to check.
|
||||
if (out.parser) resp.parser = out.parser;
|
||||
|
||||
} else { // we got one or several buffers. string or binary.
|
||||
resp.body = Buffer.concat(resp.body);
|
||||
|
||||
// if we're here and parsed is true, it means we tried to but it didn't work.
|
||||
// so given that we got a text response, let's stringify it.
|
||||
if (text_response || out.parser) {
|
||||
resp.body = resp.body.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// time to call back, junior.
|
||||
done(null, resp, resp.body);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}); // end request call
|
||||
|
||||
// unless timeout was disabled, set a timeout to abort the request
|
||||
set_timeout(config.open_timeout);
|
||||
request.on('error', had_error);
|
||||
|
||||
if (post_data) {
|
||||
if (is_stream(post_data)) {
|
||||
post_data.pipe(request);
|
||||
} else {
|
||||
request.write(post_data, config.encoding);
|
||||
request.end();
|
||||
}
|
||||
} else {
|
||||
request.end();
|
||||
}
|
||||
|
||||
out.request = request;
|
||||
return out;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////
|
||||
// exports
|
||||
|
||||
exports.version = version;
|
||||
|
||||
exports.defaults = function(obj) {
|
||||
for (var key in obj) {
|
||||
var target_key = aliased.options[key] || key;
|
||||
|
||||
if (defaults.hasOwnProperty(target_key) && typeof obj[key] != 'undefined') {
|
||||
|
||||
// ensure type matches the original
|
||||
if (obj[key].constructor.toString() != defaults[target_key].constructor.toString())
|
||||
throw new TypeError('Invalid type for ' + key);
|
||||
|
||||
defaults[target_key] = obj[key];
|
||||
}
|
||||
}
|
||||
return defaults;
|
||||
}
|
||||
|
||||
'head get'.split(' ').forEach(function(method) {
|
||||
exports[method] = function(uri, options, callback) {
|
||||
return new Needle(method, uri, null, options, callback).start();
|
||||
}
|
||||
})
|
||||
|
||||
'post put delete'.split(' ').forEach(function(method) {
|
||||
exports[method] = function(uri, data, options, callback) {
|
||||
return new Needle(method, uri, data, options, callback).start();
|
||||
}
|
||||
})
|
||||
|
||||
exports.request = function(method, uri, data, opts, callback) {
|
||||
return new Needle(method, uri, data, opts, callback).start();
|
||||
};
|
||||
68
node_modules/buildmail/node_modules/needle/lib/parsers.js
generated
vendored
Normal file
68
node_modules/buildmail/node_modules/needle/lib/parsers.js
generated
vendored
Normal file
@@ -0,0 +1,68 @@
|
||||
//////////////////////////////////////////
|
||||
// Defines mappings between content-type
|
||||
// and the appropriate parsers.
|
||||
//////////////////////////////////////////
|
||||
|
||||
var Transform = require('stream').Transform;
|
||||
|
||||
function parserFactory(name, fn) {
|
||||
|
||||
function parser() {
|
||||
var chunks = [],
|
||||
stream = new Transform({ objectMode: true });
|
||||
|
||||
// Buffer all our data
|
||||
stream._transform = function(chunk, encoding, done) {
|
||||
chunks.push(chunk);
|
||||
done();
|
||||
}
|
||||
|
||||
// And call the parser when all is there.
|
||||
stream._flush = function(done) {
|
||||
var self = this,
|
||||
data = Buffer.concat(chunks);
|
||||
|
||||
try {
|
||||
fn(data, function(err, result) {
|
||||
if (err) throw err;
|
||||
self.push(result);
|
||||
});
|
||||
} catch (err) {
|
||||
self.push(data); // just pass the original data
|
||||
} finally {
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
return { fn: parser, name: name };
|
||||
}
|
||||
|
||||
var json = parserFactory('json', function(buffer, cb) {
|
||||
var err, data;
|
||||
try { data = JSON.parse(buffer); } catch (e) { err = e; }
|
||||
cb(err, data);
|
||||
});
|
||||
|
||||
module.exports['application/json'] = json;
|
||||
module.exports['text/javascript'] = json;
|
||||
|
||||
try {
|
||||
|
||||
var xml2js = require('xml2js');
|
||||
|
||||
// xml2js.Parser.parseString() has the exact same function signature
|
||||
// as our ParseStream expects, so we can reuse this.
|
||||
var xml = parserFactory('xml', new xml2js.Parser({
|
||||
explicitRoot : true,
|
||||
explicitArray: false
|
||||
}).parseString, true);
|
||||
|
||||
module.exports['text/xml'] = xml;
|
||||
module.exports['application/xml'] = xml;
|
||||
module.exports['application/rss+xml'] = xml;
|
||||
module.exports['application/atom+xml'] = xml;
|
||||
|
||||
} catch(e) { /* xml2js not found */ }
|
||||
45
node_modules/buildmail/node_modules/needle/lib/querystring.js
generated
vendored
Normal file
45
node_modules/buildmail/node_modules/needle/lib/querystring.js
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
// based on the qs module, but handles null objects as expected
|
||||
// fixes by Tomas Pollak.
|
||||
|
||||
function stringify(obj, prefix) {
|
||||
if (prefix && (obj === null || typeof obj == 'undefined')) {
|
||||
return prefix + '=';
|
||||
} else if (obj.constructor == Array) {
|
||||
return stringifyArray(obj, prefix);
|
||||
} else if (obj !== null && typeof obj == 'object') {
|
||||
return stringifyObject(obj, prefix);
|
||||
} else if (prefix) { // string inside array or hash
|
||||
return prefix + '=' + encodeURIComponent(String(obj));
|
||||
} else if (String(obj).indexOf('=') !== -1) { // string with equal sign
|
||||
return String(obj);
|
||||
} else {
|
||||
throw new TypeError('Cannot build a querystring out of: ' + obj);
|
||||
}
|
||||
};
|
||||
|
||||
function stringifyArray(arr, prefix) {
|
||||
var ret = [];
|
||||
|
||||
for (var i = 0, len = arr.length; i < len; i++) {
|
||||
if (prefix)
|
||||
ret.push(stringify(arr[i], prefix + '[' + i + ']'));
|
||||
else
|
||||
ret.push(stringify(arr[i]));
|
||||
}
|
||||
|
||||
return ret.join('&');
|
||||
}
|
||||
|
||||
function stringifyObject(obj, prefix) {
|
||||
var ret = [];
|
||||
|
||||
Object.keys(obj).forEach(function(key) {
|
||||
ret.push(stringify(obj[key], prefix
|
||||
? prefix + '[' + encodeURIComponent(key) + ']'
|
||||
: encodeURIComponent(key)));
|
||||
})
|
||||
|
||||
return ret.join('&');
|
||||
}
|
||||
|
||||
exports.build = stringify;
|
||||
123
node_modules/buildmail/node_modules/needle/package.json
generated
vendored
Normal file
123
node_modules/buildmail/node_modules/needle/package.json
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
{
|
||||
"_args": [
|
||||
[
|
||||
"needle@^0.10.0",
|
||||
"/home/mitchell/Desktop/test-mywebsite/mywebsite/node_modules/buildmail"
|
||||
]
|
||||
],
|
||||
"_from": "needle@>=0.10.0 <0.11.0",
|
||||
"_id": "needle@0.10.0",
|
||||
"_inCache": true,
|
||||
"_installable": true,
|
||||
"_location": "/buildmail/needle",
|
||||
"_nodeVersion": "0.10.25",
|
||||
"_npmUser": {
|
||||
"email": "tomas@forkhq.com",
|
||||
"name": "tomas"
|
||||
},
|
||||
"_npmVersion": "2.7.5",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"name": "needle",
|
||||
"raw": "needle@^0.10.0",
|
||||
"rawSpec": "^0.10.0",
|
||||
"scope": null,
|
||||
"spec": ">=0.10.0 <0.11.0",
|
||||
"type": "range"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/buildmail"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/needle/-/needle-0.10.0.tgz",
|
||||
"_shasum": "16a24d63f2a61152eb74cce1d12af85c507577d4",
|
||||
"_shrinkwrap": null,
|
||||
"_spec": "needle@^0.10.0",
|
||||
"_where": "/home/mitchell/Desktop/test-mywebsite/mywebsite/node_modules/buildmail",
|
||||
"author": {
|
||||
"email": "tomas@forkhq.com",
|
||||
"name": "Tomás Pollak"
|
||||
},
|
||||
"bin": {
|
||||
"needle": "./bin/needle"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/tomas/needle/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"debug": "^2.1.2",
|
||||
"iconv-lite": "^0.4.4"
|
||||
},
|
||||
"description": "The leanest and most handsome HTTP client in the Nodelands.",
|
||||
"devDependencies": {
|
||||
"JSONStream": "",
|
||||
"jschardet": "",
|
||||
"mocha": "",
|
||||
"q": "",
|
||||
"should": "",
|
||||
"sinon": "",
|
||||
"xml2js": ""
|
||||
},
|
||||
"directories": {
|
||||
"lib": "./lib"
|
||||
},
|
||||
"dist": {
|
||||
"shasum": "16a24d63f2a61152eb74cce1d12af85c507577d4",
|
||||
"tarball": "http://registry.npmjs.org/needle/-/needle-0.10.0.tgz"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10.x"
|
||||
},
|
||||
"gitHead": "6aebe366a642636638ba26af0349de07a7669331",
|
||||
"homepage": "https://github.com/tomas/needle",
|
||||
"keywords": [
|
||||
"charset",
|
||||
"client",
|
||||
"cookie",
|
||||
"deflate",
|
||||
"http",
|
||||
"https",
|
||||
"iconv",
|
||||
"multipart",
|
||||
"proxy",
|
||||
"redirect",
|
||||
"request",
|
||||
"simple",
|
||||
"timeout",
|
||||
"upload"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "./lib/needle",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "tomas",
|
||||
"email": "tomas@forkhq.com"
|
||||
}
|
||||
],
|
||||
"name": "needle",
|
||||
"optionalDependencies": {},
|
||||
"readme": "ERROR: No README data found!",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/tomas/needle.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "mocha test"
|
||||
},
|
||||
"tags": [
|
||||
"charset",
|
||||
"client",
|
||||
"cookie",
|
||||
"deflate",
|
||||
"http",
|
||||
"https",
|
||||
"iconv",
|
||||
"multipart",
|
||||
"proxy",
|
||||
"redirect",
|
||||
"request",
|
||||
"simple",
|
||||
"timeout",
|
||||
"upload"
|
||||
],
|
||||
"version": "0.10.0"
|
||||
}
|
||||
173
node_modules/buildmail/node_modules/needle/test/basic_auth_spec.js
generated
vendored
Normal file
173
node_modules/buildmail/node_modules/needle/test/basic_auth_spec.js
generated
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
var helpers = require('./helpers'),
|
||||
should = require('should'),
|
||||
needle = require('./../'),
|
||||
server;
|
||||
|
||||
var port = 7707;
|
||||
|
||||
describe('Basic Auth', function() {
|
||||
|
||||
before(function(done) {
|
||||
server = helpers.server({ port: port }, done);
|
||||
})
|
||||
|
||||
after(function(done) {
|
||||
server.close(done);
|
||||
})
|
||||
|
||||
///////////////// helpers
|
||||
|
||||
var get_auth = function(header) {
|
||||
var token = header.split(/\s+/).pop();
|
||||
return token && new Buffer(token, 'base64').toString().split(':');
|
||||
}
|
||||
|
||||
describe('when neither username or password are passed', function() {
|
||||
|
||||
it('doesnt send any Authorization headers', function(done) {
|
||||
needle.get('localhost:' + port, { parse: true }, function(err, resp) {
|
||||
var sent_headers = resp.body.headers;
|
||||
Object.keys(sent_headers).should.not.containEql('authorization');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('when username is an empty string, and password is a valid string', function() {
|
||||
|
||||
var opts = { username: '', password: 'foobar', parse: true };
|
||||
|
||||
it('doesnt send any Authorization headers', function(done) {
|
||||
needle.get('localhost:' + port, { parse: true }, function(err, resp) {
|
||||
var sent_headers = resp.body.headers;
|
||||
Object.keys(sent_headers).should.not.containEql('authorization');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
describe('when username is a valid string, but no username is passed', function() {
|
||||
|
||||
var opts = { username: 'foobar', parse: true };
|
||||
|
||||
it('sends Authorization header', function(done) {
|
||||
needle.get('localhost:' + port, opts, function(err, resp) {
|
||||
var sent_headers = resp.body.headers;
|
||||
Object.keys(sent_headers).should.containEql('authorization');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
it('Basic Auth only includes username, without colon', function(done) {
|
||||
needle.get('localhost:' + port, opts, function(err, resp) {
|
||||
var sent_headers = resp.body.headers;
|
||||
var auth = get_auth(sent_headers['authorization']);
|
||||
auth[0].should.equal('foobar');
|
||||
auth.should.have.lengthOf(1);
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('when username is a valid string, and password is null', function() {
|
||||
|
||||
var opts = { username: 'foobar', password: null, parse: true };
|
||||
|
||||
it('sends Authorization header', function(done) {
|
||||
needle.get('localhost:' + port, opts, function(err, resp) {
|
||||
var sent_headers = resp.body.headers;
|
||||
Object.keys(sent_headers).should.containEql('authorization');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
it('Basic Auth only includes both username and password', function(done) {
|
||||
needle.get('localhost:' + port, opts, function(err, resp) {
|
||||
var sent_headers = resp.body.headers;
|
||||
var auth = get_auth(sent_headers['authorization']);
|
||||
auth[0].should.equal('foobar');
|
||||
auth[1].should.equal('');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('when username is a valid string, and password is an empty string', function() {
|
||||
|
||||
var opts = { username: 'foobar', password: '', parse: true };
|
||||
|
||||
it('sends Authorization header', function(done) {
|
||||
needle.get('localhost:' + port, opts, function(err, resp) {
|
||||
var sent_headers = resp.body.headers;
|
||||
Object.keys(sent_headers).should.containEql('authorization');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
it('Basic Auth only includes both username and password', function(done) {
|
||||
needle.get('localhost:' + port, opts, function(err, resp) {
|
||||
var sent_headers = resp.body.headers;
|
||||
var auth = get_auth(sent_headers['authorization']);
|
||||
auth[0].should.equal('foobar');
|
||||
auth[1].should.equal('');
|
||||
auth.should.have.lengthOf(2);
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('when username AND password are non empty strings', function() {
|
||||
|
||||
var opts = { username: 'foobar', password: 'jakub', parse: true };
|
||||
|
||||
it('sends Authorization header', function(done) {
|
||||
needle.get('localhost:' + port, opts, function(err, resp) {
|
||||
var sent_headers = resp.body.headers;
|
||||
Object.keys(sent_headers).should.containEql('authorization');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
it('Basic Auth only includes both user and password', function(done) {
|
||||
needle.get('localhost:' + port, opts, function(err, resp) {
|
||||
var sent_headers = resp.body.headers;
|
||||
var auth = get_auth(sent_headers['authorization']);
|
||||
auth[0].should.equal('foobar');
|
||||
auth[1].should.equal('jakub');
|
||||
auth.should.have.lengthOf(2);
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('when username/password are included in URL', function() {
|
||||
var opts = { parse: true };
|
||||
|
||||
it('sends Authorization header', function(done) {
|
||||
needle.get('foobar:jakub@localhost:' + port, opts, function(err, resp) {
|
||||
var sent_headers = resp.body.headers;
|
||||
Object.keys(sent_headers).should.containEql('authorization');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
it('Basic Auth only includes both user and password', function(done) {
|
||||
needle.get('foobar:jakub@localhost:' + port, opts, function(err, resp) {
|
||||
var sent_headers = resp.body.headers;
|
||||
var auth = get_auth(sent_headers['authorization']);
|
||||
auth[0].should.equal('foobar');
|
||||
auth[1].should.equal('jakub');
|
||||
auth.should.have.lengthOf(2);
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
78
node_modules/buildmail/node_modules/needle/test/compression_spec.js
generated
vendored
Normal file
78
node_modules/buildmail/node_modules/needle/test/compression_spec.js
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
var should = require('should'),
|
||||
needle = require('./../'),
|
||||
http = require('http'),
|
||||
zlib = require('zlib'),
|
||||
stream = require('stream'),
|
||||
port = 11111,
|
||||
server;
|
||||
|
||||
describe('compression', function(){
|
||||
|
||||
require.bind(null, 'zlib').should.not.throw()
|
||||
|
||||
var jsonData = '{"foo":"bar"}';
|
||||
|
||||
describe('when server supports compression', function(){
|
||||
|
||||
before(function(){
|
||||
server = http.createServer(function(req, res) {
|
||||
var raw = new stream.PassThrough();
|
||||
|
||||
var acceptEncoding = req.headers['accept-encoding'];
|
||||
if (!acceptEncoding) {
|
||||
acceptEncoding = '';
|
||||
}
|
||||
|
||||
if (acceptEncoding.match(/\bdeflate\b/)) {
|
||||
res.setHeader('Content-Encoding', 'deflate');
|
||||
raw.pipe(zlib.createDeflate()).pipe(res);
|
||||
} else if (acceptEncoding.match(/\bgzip\b/)) {
|
||||
res.setHeader('Content-Encoding', 'gzip');
|
||||
raw.pipe(zlib.createGzip()).pipe(res);
|
||||
} else {
|
||||
raw.pipe(res);
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
raw.end(jsonData)
|
||||
}).listen(port);
|
||||
});
|
||||
|
||||
after(function(){
|
||||
server.close();
|
||||
})
|
||||
|
||||
describe('and client requests no compression', function() {
|
||||
it('should have the body decompressed', function(done){
|
||||
needle.get('localhost:' + port, function(err, response, body){
|
||||
should.ifError(err);
|
||||
body.should.have.property('foo', 'bar');
|
||||
response.bytes.should.equal(jsonData.length);
|
||||
done();
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('and client requests gzip compression', function() {
|
||||
it('should have the body decompressed', function(done){
|
||||
needle.get('localhost:' + port, {headers: {'Accept-Encoding': 'gzip'}}, function(err, response, body){
|
||||
should.ifError(err);
|
||||
body.should.have.property('foo', 'bar');
|
||||
response.bytes.should.not.equal(jsonData.length);
|
||||
done();
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('and client requests deflate compression', function() {
|
||||
it('should have the body decompressed', function(done){
|
||||
needle.get('localhost:' + port, {headers: {'Accept-Encoding': 'deflate'}}, function(err, response, body){
|
||||
should.ifError(err);
|
||||
body.should.have.property('foo', 'bar');
|
||||
response.bytes.should.not.equal(jsonData.length);
|
||||
done();
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
204
node_modules/buildmail/node_modules/needle/test/cookies_spec.js
generated
vendored
Normal file
204
node_modules/buildmail/node_modules/needle/test/cookies_spec.js
generated
vendored
Normal file
@@ -0,0 +1,204 @@
|
||||
var needle = require('../'),
|
||||
sinon = require('sinon'),
|
||||
http = require('http'),
|
||||
should = require('should'),
|
||||
assert = require('assert');
|
||||
|
||||
var WEIRD_COOKIE_NAME = 'wc',
|
||||
BASE64_COOKIE_NAME = 'bc',
|
||||
FORBIDDEN_COOKIE_NAME = 'fc',
|
||||
NUMBER_COOKIE_NAME = 'nc',
|
||||
WEIRD_COOKIE_VALUE = '!\'*+#()&-./0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[' +
|
||||
']^_`abcdefghijklmnopqrstuvwxyz{|}~',
|
||||
BASE64_COOKIE_VALUE = 'Y29va2llCg==',
|
||||
FORBIDDEN_COOKIE_VALUE = ' ;"\\,',
|
||||
NUMBER_COOKIE_VALUE = 12354342,
|
||||
WEIRD_COOKIE = 'wc=!\'*+#()&-./0123456789:<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~',
|
||||
BASE64_COOKIE = 'bc=Y29va2llCg==',
|
||||
FORBIDDEN_COOKIE = 'fc=%20%3B%22%5C%2C',
|
||||
NUMBER_COOKIE = 'nc=12354342',
|
||||
COOKIE_HEADER = WEIRD_COOKIE + '; ' + BASE64_COOKIE + '; ' +
|
||||
FORBIDDEN_COOKIE + '; ' + NUMBER_COOKIE,
|
||||
TEST_HOST = 'localhost';
|
||||
NO_COOKIES_TEST_PORT = 11112, ALL_COOKIES_TEST_PORT = 11113;
|
||||
|
||||
function decode(str) {
|
||||
return decodeURIComponent(str);
|
||||
}
|
||||
|
||||
function encode(str) {
|
||||
str = str.toString().replace(/[\x00-\x1F\x7F]/g, encodeURIComponent);
|
||||
return str.replace(/[\s\"\,;\\%]/g, encodeURIComponent);
|
||||
}
|
||||
|
||||
describe('cookies', function() {
|
||||
|
||||
var headers, server, opts;
|
||||
|
||||
before(function() {
|
||||
setCookieHeader = [
|
||||
WEIRD_COOKIE_NAME + '=' + encode(WEIRD_COOKIE_VALUE) + ';',
|
||||
BASE64_COOKIE_NAME + '=' + encode(BASE64_COOKIE_VALUE) + ';',
|
||||
FORBIDDEN_COOKIE_NAME + '=' + encode(FORBIDDEN_COOKIE_VALUE) + ';',
|
||||
NUMBER_COOKIE_NAME + '=' + encode(NUMBER_COOKIE_VALUE) + ';'
|
||||
];
|
||||
});
|
||||
|
||||
before(function(done) {
|
||||
serverAllCookies = http.createServer(function(req, res) {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.setHeader('Set-Cookie', setCookieHeader);
|
||||
res.end('200');
|
||||
}).listen(ALL_COOKIES_TEST_PORT, TEST_HOST, done);
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
serverAllCookies.close(done);
|
||||
});
|
||||
|
||||
describe('with default options', function() {
|
||||
it('no cookie header is set on request', function(done) {
|
||||
needle.get(
|
||||
TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, function(err, response) {
|
||||
assert(!response.req._headers.cookie);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('if response does not contain cookies', function() {
|
||||
before(function(done) {
|
||||
serverNoCookies = http.createServer(function(req, res) {
|
||||
res.setHeader('Content-Type', 'text/html');
|
||||
res.end('200');
|
||||
}).listen(NO_COOKIES_TEST_PORT, TEST_HOST, done);
|
||||
});
|
||||
|
||||
it('response.cookies is undefined', function(done) {
|
||||
needle.get(
|
||||
TEST_HOST + ':' + NO_COOKIES_TEST_PORT, function(error, response) {
|
||||
assert(!response.cookies);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
after(function(done) {
|
||||
serverNoCookies.close(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('if response contains cookies', function() {
|
||||
it('puts them on resp.cookies', function(done) {
|
||||
needle.get(
|
||||
TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, function(error, response) {
|
||||
response.should.have.property('cookies');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('parses them as a object', function(done) {
|
||||
needle.get(
|
||||
TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, function(error, response) {
|
||||
response.cookies.should.be.an.instanceOf(Object)
|
||||
.and.have.property(WEIRD_COOKIE_NAME);
|
||||
response.cookies.should.have.property(BASE64_COOKIE_NAME);
|
||||
response.cookies.should.have.property(FORBIDDEN_COOKIE_NAME);
|
||||
response.cookies.should.have.property(NUMBER_COOKIE_NAME);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('must decode it', function(done) {
|
||||
needle.get(
|
||||
TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, function(error, response) {
|
||||
response.cookies.wc.should.be.eql(WEIRD_COOKIE_VALUE);
|
||||
response.cookies.bc.should.be.eql(BASE64_COOKIE_VALUE);
|
||||
response.cookies.fc.should.be.eql(FORBIDDEN_COOKIE_VALUE);
|
||||
response.cookies.nc.should.be.eql(NUMBER_COOKIE_VALUE.toString());
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
describe('and response is a redirect', function() {
|
||||
describe('and follow_set_cookies is false', function() {
|
||||
it('no cookie header set on redirection request', function() {});
|
||||
});
|
||||
describe('and follow_set_cookies is true', function() {});
|
||||
});
|
||||
});
|
||||
|
||||
describe('if resquest contains cookie header', function() {
|
||||
var opts = {
|
||||
cookies: {}
|
||||
};
|
||||
|
||||
before(function() {
|
||||
opts.cookies[WEIRD_COOKIE_NAME] = WEIRD_COOKIE_VALUE;
|
||||
opts.cookies[BASE64_COOKIE_NAME] = BASE64_COOKIE_VALUE;
|
||||
opts.cookies[FORBIDDEN_COOKIE_NAME] = FORBIDDEN_COOKIE_VALUE;
|
||||
opts.cookies[NUMBER_COOKIE_NAME] = NUMBER_COOKIE_VALUE;
|
||||
});
|
||||
|
||||
it('must be a valid cookie string', function(done) {
|
||||
var COOKIE_PAIR = /^([^=\s]+)\s*=\s*("?)\s*(.*)\s*\2\s*$/;
|
||||
|
||||
needle.get(TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, opts, function(error, response) {
|
||||
var cookieString = response.req._headers.cookie;
|
||||
|
||||
cookieString.should.be.type('string');
|
||||
|
||||
cookieString.split(/\s*;\s*/).forEach(function(pair) {
|
||||
COOKIE_PAIR.test(pair).should.be.exactly(true);
|
||||
});
|
||||
|
||||
cookieString.should.be.exactly(COOKIE_HEADER);
|
||||
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('dont have to encode allowed characters', function(done) {
|
||||
var COOKIE_PAIR = /^([^=\s]+)\s*=\s*("?)\s*(.*)\s*\2\s*$/,
|
||||
KEY_INDEX = 1,
|
||||
VALUE_INEX = 3;
|
||||
|
||||
needle.get(TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, opts, function(error, response) {
|
||||
var cookieObj = {},
|
||||
cookieString = response.req._headers.cookie;
|
||||
|
||||
cookieString.split(/\s*;\s*/).forEach(function(str) {
|
||||
var pair = COOKIE_PAIR.exec(str);
|
||||
cookieObj[pair[KEY_INDEX]] = pair[VALUE_INEX];
|
||||
});
|
||||
|
||||
cookieObj[WEIRD_COOKIE_NAME].should.be.exactly(WEIRD_COOKIE_VALUE);
|
||||
cookieObj[BASE64_COOKIE_NAME].should.be.exactly(BASE64_COOKIE_VALUE);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('must encode forbidden characters', function(done) {
|
||||
var COOKIE_PAIR = /^([^=\s]+)\s*=\s*("?)\s*(.*)\s*\2\s*$/,
|
||||
KEY_INDEX = 1,
|
||||
VALUE_INEX = 3;
|
||||
|
||||
needle.get(TEST_HOST + ':' + ALL_COOKIES_TEST_PORT, opts, function(error, response) {
|
||||
var cookieObj = {},
|
||||
cookieString = response.req._headers.cookie;
|
||||
|
||||
cookieString.split(/\s*;\s*/).forEach(function(str) {
|
||||
var pair = COOKIE_PAIR.exec(str);
|
||||
cookieObj[pair[KEY_INDEX]] = pair[VALUE_INEX];
|
||||
});
|
||||
|
||||
cookieObj[FORBIDDEN_COOKIE_NAME].should.not.be.eql(
|
||||
FORBIDDEN_COOKIE_VALUE);
|
||||
cookieObj[FORBIDDEN_COOKIE_NAME].should.be.exactly(
|
||||
encode(FORBIDDEN_COOKIE_VALUE));
|
||||
cookieObj[FORBIDDEN_COOKIE_NAME].should.be.exactly(
|
||||
encodeURIComponent(FORBIDDEN_COOKIE_VALUE));
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
86
node_modules/buildmail/node_modules/needle/test/decoder_spec.js
generated
vendored
Normal file
86
node_modules/buildmail/node_modules/needle/test/decoder_spec.js
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
var should = require('should'),
|
||||
needle = require('./../'),
|
||||
Q = require('q'),
|
||||
chardet = require('jschardet');
|
||||
|
||||
describe('character encoding', function() {
|
||||
|
||||
var url;
|
||||
this.timeout(5000);
|
||||
|
||||
describe('test A', function() {
|
||||
|
||||
before(function() {
|
||||
url = 'http://www.huanqiukexue.com/html/newgc/2014/1215/25011.html';
|
||||
})
|
||||
|
||||
describe('with decode = false', function() {
|
||||
|
||||
it('does not decode', function(done) {
|
||||
|
||||
needle.get(url, { decode: false }, function(err, resp) {
|
||||
resp.body.should.be.a.string;
|
||||
chardet.detect(resp.body).encoding.should.eql('windows-1252');
|
||||
resp.body.indexOf('柳博米尔斯基').should.eql(-1);
|
||||
done();
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('with decode = true', function() {
|
||||
|
||||
it('decodes', function(done) {
|
||||
|
||||
needle.get(url, { decode: true }, function(err, resp) {
|
||||
resp.body.should.be.a.string;
|
||||
chardet.detect(resp.body).encoding.should.eql('ascii');
|
||||
resp.body.indexOf('柳博米尔斯基').should.not.eql(-1);
|
||||
done();
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('test B', function() {
|
||||
|
||||
it('encodes to UTF-8', function(done) {
|
||||
|
||||
// Our Needle wrapper that requests a chinese website.
|
||||
var task = Q.nbind(needle.get, needle, 'http://www.chinesetop100.com/');
|
||||
|
||||
// Different instantiations of this task
|
||||
var tasks = [Q.fcall(task, {decode: true}),
|
||||
Q.fcall(task, {decode: false})];
|
||||
|
||||
var results = tasks.map(function(task) {
|
||||
return task.then(function(obj) {
|
||||
return obj[0].body;
|
||||
});
|
||||
});
|
||||
|
||||
// Execute all requests concurrently
|
||||
Q.all(results).done(function(bodies) {
|
||||
|
||||
var charsets = [
|
||||
chardet.detect(bodies[0]).encoding,
|
||||
chardet.detect(bodies[1]).encoding,
|
||||
]
|
||||
|
||||
// We wanted to decode our first stream.
|
||||
charsets[0].should.equal('ascii');
|
||||
bodies[0].indexOf('全球中文网站前二十强').should.not.equal(-1);
|
||||
|
||||
// But not our second stream.
|
||||
charsets[1].should.equal('windows-1252');
|
||||
bodies[1].indexOf('全球中文网站前二十强').should.equal(-1);
|
||||
|
||||
done();
|
||||
});
|
||||
})
|
||||
})
|
||||
})
|
||||
223
node_modules/buildmail/node_modules/needle/test/errors_spec.js
generated
vendored
Normal file
223
node_modules/buildmail/node_modules/needle/test/errors_spec.js
generated
vendored
Normal file
@@ -0,0 +1,223 @@
|
||||
var needle = require('../'),
|
||||
sinon = require('sinon'),
|
||||
should = require('should'),
|
||||
http = require('http'),
|
||||
Emitter = require('events').EventEmitter,
|
||||
helpers = require('./helpers');
|
||||
|
||||
var get_catch = function(url, opts) {
|
||||
var err;
|
||||
try {
|
||||
needle.get(url, opts);
|
||||
} catch(e) {
|
||||
err = e;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
describe('errors', function(){
|
||||
|
||||
|
||||
describe('when host does not exist', function(){
|
||||
|
||||
var url = 'http://unexistinghost/foo';
|
||||
|
||||
describe('with callback', function() {
|
||||
|
||||
it('does not throw', function(){
|
||||
var ex = get_catch(url);
|
||||
should.not.exist(ex);
|
||||
})
|
||||
|
||||
it('callbacks an error', function(done) {
|
||||
needle.get(url, function(err){
|
||||
err.should.be.a.Error;
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
it('error should be ENOTFOUND', function(done){
|
||||
needle.get(url, function(err){
|
||||
err.code.should.match(/ENOTFOUND|EADDRINFO/)
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
it('does not callback a response', function(done){
|
||||
needle.get(url, function(err, resp){
|
||||
should.not.exist(resp);
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('without callback', function() {
|
||||
|
||||
it('does not throw', function(){
|
||||
var ex = get_catch(url);
|
||||
should.not.exist(ex);
|
||||
})
|
||||
|
||||
it('emits end event once, with error', function(done) {
|
||||
var callcount = 0,
|
||||
stream = needle.get(url);
|
||||
|
||||
stream.on('end', function(err) {
|
||||
callcount++;
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
callcount.should.equal(1);
|
||||
done();
|
||||
}, 200)
|
||||
})
|
||||
|
||||
it('error should be ENOTFOUND or EADDRINFO', function(done) {
|
||||
var errorific,
|
||||
stream = needle.get(url);
|
||||
|
||||
stream.on('end', function(err) {
|
||||
errorific = err;
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
errorific.code.should.match(/ENOTFOUND|EADDRINFO/)
|
||||
done();
|
||||
}, 200)
|
||||
})
|
||||
|
||||
it('does not emit a readable event', function(done){
|
||||
var called = false,
|
||||
stream = needle.get(url);
|
||||
|
||||
stream.on('readable', function() {
|
||||
called = true;
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
called.should.be.false;
|
||||
done();
|
||||
}, 50)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('when request timeouts', function(){
|
||||
|
||||
var server,
|
||||
url = 'http://localhost:3333/foo';
|
||||
|
||||
var send_request = function(cb) {
|
||||
return needle.get(url, { timeout: 200 }, cb);
|
||||
}
|
||||
|
||||
before(function(){
|
||||
server = helpers.server({ port: 3333, wait: 1000 });
|
||||
})
|
||||
|
||||
after(function(){
|
||||
server.close();
|
||||
})
|
||||
|
||||
describe('with callback', function() {
|
||||
|
||||
it('aborts the request', function(done){
|
||||
|
||||
var time = new Date();
|
||||
|
||||
send_request(function(err){
|
||||
var timediff = (new Date() - time);
|
||||
timediff.should.be.within(200, 300);
|
||||
done();
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
it('callbacks an error', function(done){
|
||||
send_request(function(err){
|
||||
err.should.be.a.Error;
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
it('error should be ECONNRESET', function(done){
|
||||
send_request(function(err){
|
||||
err.code.should.equal('ECONNRESET')
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
it('does not callback a response', function(done) {
|
||||
send_request(function(err, resp){
|
||||
should.not.exist(resp);
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('without callback', function() {
|
||||
|
||||
it('emits end event once, with error', function(done) {
|
||||
var called = 0,
|
||||
stream = send_request();
|
||||
|
||||
stream.on('end', function(err) {
|
||||
called++;
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
called.should.equal(1);
|
||||
done();
|
||||
}, 250)
|
||||
})
|
||||
|
||||
it('aborts the request', function(done){
|
||||
|
||||
var time = new Date();
|
||||
var stream = send_request();
|
||||
|
||||
stream.on('end', function(err) {
|
||||
var timediff = (new Date() - time);
|
||||
timediff.should.be.within(200, 300);
|
||||
done();
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
it('error should be ECONNRESET', function(done){
|
||||
var error,
|
||||
stream = send_request();
|
||||
|
||||
stream.on('end', function(err) {
|
||||
error = err;
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
error.code.should.equal('ECONNRESET')
|
||||
done();
|
||||
}, 250)
|
||||
})
|
||||
|
||||
it('does not emit a readable event', function(done){
|
||||
var called = false,
|
||||
stream = send_request();
|
||||
|
||||
stream.on('readable', function() {
|
||||
called = true;
|
||||
})
|
||||
|
||||
setTimeout(function() {
|
||||
called.should.be.false;
|
||||
done();
|
||||
}, 250)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
67
node_modules/buildmail/node_modules/needle/test/helpers.js
generated
vendored
Normal file
67
node_modules/buildmail/node_modules/needle/test/helpers.js
generated
vendored
Normal file
@@ -0,0 +1,67 @@
|
||||
var fs = require('fs');
|
||||
|
||||
var protocols = {
|
||||
http : require('http'),
|
||||
https : require('https')
|
||||
}
|
||||
|
||||
var keys = {
|
||||
cert : fs.readFileSync(__dirname + '/keys/ssl.cert'),
|
||||
key : fs.readFileSync(__dirname + '/keys/ssl.key')
|
||||
}
|
||||
|
||||
var helpers = {};
|
||||
|
||||
helpers.server = function(opts, cb) {
|
||||
|
||||
var defaults = {
|
||||
code : 200,
|
||||
headers : {'Content-Type': 'application/json'}
|
||||
}
|
||||
|
||||
var mirror_response = function(req) {
|
||||
return JSON.stringify({
|
||||
headers: req.headers,
|
||||
body: req.body
|
||||
})
|
||||
}
|
||||
|
||||
var get = function(what) {
|
||||
if (!opts[what])
|
||||
return defaults[what];
|
||||
|
||||
if (typeof opts[what] == 'function')
|
||||
return opts[what](); // set them at runtime
|
||||
else
|
||||
return opts[what];
|
||||
}
|
||||
|
||||
var finish = function(req, res) {
|
||||
res.writeHead(get('code'), get('headers'));
|
||||
res.end(opts.response || mirror_response(req));
|
||||
}
|
||||
|
||||
var handler = function(req, res){
|
||||
|
||||
req.setEncoding('utf8'); // get as string
|
||||
req.body = '';
|
||||
req.on('data', function(str) { req.body += str })
|
||||
|
||||
setTimeout(function(){
|
||||
finish(req, res);
|
||||
}, opts.wait || 0);
|
||||
|
||||
};
|
||||
|
||||
var protocol = opts.protocol || 'http';
|
||||
|
||||
if (protocol == 'https')
|
||||
server = protocols[protocol].createServer(keys, handler);
|
||||
else
|
||||
server = protocols[protocol].createServer(handler);
|
||||
|
||||
server.listen(opts.port, cb);
|
||||
return server;
|
||||
}
|
||||
|
||||
module.exports = helpers;
|
||||
21
node_modules/buildmail/node_modules/needle/test/keys/ssl.cert
generated
vendored
Normal file
21
node_modules/buildmail/node_modules/needle/test/keys/ssl.cert
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDXTCCAkWgAwIBAgIJANyV2Q1ZdQpBMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMTUwNDA5MDYyOTI0WhcNMTgwMTAyMDYyOTI0WjBF
|
||||
MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEAmtOqQHG6YUNKvwiS99LJx+80xE8/bNYU1LF7Egcuk1OQAU/as9Jrh67o
|
||||
6zVVqCvwAxYCTLNF72Vs2+yJpImnjNIgPdh2qhm66SxGeF+qpA6UVk0NNwU3dxOJ
|
||||
AvWrV/wr1F1I1JnEx44NA79AkkMXjQVYJvpQuwDjtcyU7NBgLM5z2mgosBOKvPwk
|
||||
R3xRdv+1Koi30+1iCLDo/gTjaLg4dldcChD/eSCZL01XjMXN0HuEJ7/bd42738oM
|
||||
vAs0z9ekgt6L4tbn3AhM0X4udgm6vYk0h584i8MKqPTKE6BML+N6x+a6MuRNTBvJ
|
||||
QC39NJTqoZ/O24A2/zx3ypiUMxHu2wIDAQABo1AwTjAdBgNVHQ4EFgQU1oOm6dAs
|
||||
UtFa3UMdQhpYhgG2MHwwHwYDVR0jBBgwFoAU1oOm6dAsUtFa3UMdQhpYhgG2MHww
|
||||
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAkiSa2Qz573igG2XhXsQp
|
||||
LtGoN9xsXJ3yNL/ycuFUgYpg5HvzLwttLP5TdD1ptQ78j+9xsuecdi1mQRWrF6FZ
|
||||
L7rB8OYYlGFaF8/7cxwIeN6DPzAuLAckndHl44HkvMjGspm8fwovMr1Jy0c3rRKs
|
||||
68znXssjoVe0MqOhYlZuj+s6FwU6jnMCr1QUeOUFATDzzZPKmrxT4mfr0szdfEWw
|
||||
Z/vWBuU4oVARwtETX+ttHMSshxkUI8aNVmwbGV4W1WCf4aOSSlTdeS6z45n+KZBg
|
||||
OzfDDlo9SO4IH3YSMLlX+ZqjEbZ8A9gAeHK3dGoJ0d7JdjlyyLeIkmiEi2Jiv/zC
|
||||
Yw==
|
||||
-----END CERTIFICATE-----
|
||||
27
node_modules/buildmail/node_modules/needle/test/keys/ssl.key
generated
vendored
Normal file
27
node_modules/buildmail/node_modules/needle/test/keys/ssl.key
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAmtOqQHG6YUNKvwiS99LJx+80xE8/bNYU1LF7Egcuk1OQAU/a
|
||||
s9Jrh67o6zVVqCvwAxYCTLNF72Vs2+yJpImnjNIgPdh2qhm66SxGeF+qpA6UVk0N
|
||||
NwU3dxOJAvWrV/wr1F1I1JnEx44NA79AkkMXjQVYJvpQuwDjtcyU7NBgLM5z2mgo
|
||||
sBOKvPwkR3xRdv+1Koi30+1iCLDo/gTjaLg4dldcChD/eSCZL01XjMXN0HuEJ7/b
|
||||
d42738oMvAs0z9ekgt6L4tbn3AhM0X4udgm6vYk0h584i8MKqPTKE6BML+N6x+a6
|
||||
MuRNTBvJQC39NJTqoZ/O24A2/zx3ypiUMxHu2wIDAQABAoIBAQCYmL/3jl/lVx6H
|
||||
lFrOGRo5jYETbwwTKBkV3OtwxFVabYTEVkE29NB4xaLZmBKZktyXHg0cLaFjJzMY
|
||||
u1SaL/ZMrBmM+xlR49Q91x6Jq7iSw6/eM0kwDlsxaLakjR/Yszk7sMmEiR3SlSYs
|
||||
qEZrIedHXZoZqKMC/Qhx+XZaxbHU1goSIOMVvfgXGzeRwlQILfYntrJ/04gAU9TK
|
||||
FneNLCBBzc69yplTidWotQK2pg7OTU+k2VApYbLSGFXUCW9kBQ+lJ10L1qayIVjp
|
||||
pP83etFb43Cxm5aMPt0vgUIwTzaGsUrWaPnYv0OjDNG1FbIbfmRU0qceYdgidurc
|
||||
UyjBVGIBAoGBAMjINxApVlaizqMbatrrfVeuRuNTmDx7l44JTcAPPyLwWgv6+qsn
|
||||
7n0RasSnHM1fuScHEgkafLqvNIdmEoMt+wn2jCKmjqFhNmMhM3VjpfSXZIfR6htC
|
||||
mAZvAqN6+6uzXXbpg/SLfR5kNmWjrF4Zw2klV6O3s2x+tJ+fGvcdSWfbAoGBAMVo
|
||||
CFcv7V6x/3DQ14ZVKCjexNrufumSTK3lUCVUH7PZRADo73P+vz3KYlILGyqFaE5y
|
||||
cQt5v8zdCqDJuM7gIIMHeQpUfL0zW6HzCrFZd48w/tnMwLjjgyVt4gV9HtfYiiWO
|
||||
x7lDCFkwVcTQqVY1GtXGjslTdWICT9qZMbJbp8UBAoGAHQvh82OXivcIn84SmlMh
|
||||
EfMzbCWmy3ExzqmH5vYNszdEP6FybBpdxRgk8wgeAZQMjBnYwRpk2vkHx2K74OBh
|
||||
BeL2CPlBfxU6MgMWl6/vzT+tmnyCuqjap2ud3RqomAFdzxsfLNyVIDVtLS0NfZuK
|
||||
ioaNdDDaMyLtbH4u/3rsKKsCgYBNjzs/rjfv7bV0CMm1IL4fmK71aaD5eh6gcClW
|
||||
EUBHdESQqvRxuZQ/3cVVgMGSqkm6EKKzpIkkH3CujbMLrOl5MbVvxpQf7g7BiXEZ
|
||||
DxK3csOepSDPQ6xiR1L3XxqAKbbVXMzB0EF2cVQxeN4fXcwGU/YDycOj106yj2AE
|
||||
OJipAQKBgQCGUUQJ74KV4/X+bZtsGqla26G7TL616+ZIYZaS4mut1eC0Qu84q238
|
||||
Iv2bGGg9iLa7bvP4qHk3lyR2u1UOyFLisAyt/dYam9CE4pLB2rVfF/def1DjYXjw
|
||||
L+9C9oYUW4NYzhsbiX7mwqklXA0DQINoAfE+qTibLAkfDDUGvYH13g==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
340
node_modules/buildmail/node_modules/needle/test/parsing_spec.js
generated
vendored
Normal file
340
node_modules/buildmail/node_modules/needle/test/parsing_spec.js
generated
vendored
Normal file
@@ -0,0 +1,340 @@
|
||||
var should = require('should'),
|
||||
needle = require('./../'),
|
||||
http = require('http'),
|
||||
port = 11111,
|
||||
server;
|
||||
|
||||
describe('parsing', function(){
|
||||
|
||||
describe('when response is an JSON string', function(){
|
||||
|
||||
var json_string = '{"foo":"bar"}';
|
||||
|
||||
before(function(done){
|
||||
server = http.createServer(function(req, res) {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(json_string);
|
||||
}).listen(port, done);
|
||||
});
|
||||
|
||||
after(function(done){
|
||||
server.close(done);
|
||||
})
|
||||
|
||||
describe('and parse option is not passed', function() {
|
||||
|
||||
it('should return object', function(done){
|
||||
needle.get('localhost:' + port, function(err, response, body){
|
||||
should.ifError(err);
|
||||
body.should.have.property('foo', 'bar');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('and parse option is true', function() {
|
||||
|
||||
describe('and JSON is valid', function() {
|
||||
|
||||
it('should return object', function(done) {
|
||||
needle.get('localhost:' + port, { parse: true }, function(err, response, body){
|
||||
should.not.exist(err);
|
||||
body.should.have.property('foo', 'bar')
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
it('should have a .parser = json property', function(done) {
|
||||
needle.get('localhost:' + port, { parse: true }, function(err, resp) {
|
||||
should.not.exist(err);
|
||||
resp.parser.should.eql('json');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
describe('and response is empty', function() {
|
||||
|
||||
var old_json_string;
|
||||
|
||||
before(function() {
|
||||
old_json_string = json_string;
|
||||
json_string = "";
|
||||
});
|
||||
|
||||
after(function() {
|
||||
json_string = old_json_string;
|
||||
});
|
||||
|
||||
it('should return an empty string', function(done) {
|
||||
needle.get('localhost:' + port, { parse: true }, function(err, resp) {
|
||||
should.not.exist(err);
|
||||
resp.body.should.equal('');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('and JSON is invalid', function() {
|
||||
|
||||
var old_json_string;
|
||||
|
||||
before(function() {
|
||||
old_json_string = json_string;
|
||||
json_string = "this is not going to work";
|
||||
});
|
||||
|
||||
after(function() {
|
||||
json_string = old_json_string;
|
||||
});
|
||||
|
||||
it('does not throw', function(done) {
|
||||
(function(){
|
||||
needle.get('localhost:' + port, { parse: true }, done);
|
||||
}).should.not.throw();
|
||||
});
|
||||
|
||||
it('does NOT return object', function(done) {
|
||||
needle.get('localhost:' + port, { parse: true }, function(err, response, body) {
|
||||
should.not.exist(err);
|
||||
body.should.be.a.String;
|
||||
body.toString().should.eql('this is not going to work');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
describe('and parse option is false', function() {
|
||||
|
||||
it('does NOT return object', function(done){
|
||||
needle.get('localhost:' + port, { parse: false }, function(err, response, body) {
|
||||
should.not.exist(err);
|
||||
body.should.be.an.instanceof(Buffer)
|
||||
body.toString().should.eql('{"foo":"bar"}');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
it('should NOT have a .parser = json property', function(done) {
|
||||
needle.get('localhost:' + port, { parse: false }, function(err, resp) {
|
||||
should.not.exist(err);
|
||||
should.not.exist(resp.parser);
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('and parse option is "xml"', function() {
|
||||
|
||||
it('does NOT return object', function(done){
|
||||
needle.get('localhost:' + port, { parse: 'xml' }, function(err, response, body) {
|
||||
should.not.exist(err);
|
||||
body.should.be.an.instanceof(Buffer)
|
||||
body.toString().should.eql('{"foo":"bar"}');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
it('should NOT have a .parser = json property', function(done) {
|
||||
needle.get('localhost:' + port, { parse: 'xml' }, function(err, resp) {
|
||||
should.not.exist(err);
|
||||
should.not.exist(resp.parser);
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
describe('when response is JSON \'false\'', function(){
|
||||
|
||||
var json_string = 'false';
|
||||
|
||||
before(function(done){
|
||||
server = http.createServer(function(req, res) {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(json_string);
|
||||
}).listen(port, done);
|
||||
});
|
||||
|
||||
after(function(done){
|
||||
server.close(done);
|
||||
})
|
||||
|
||||
describe('and parse option is not passed', function() {
|
||||
|
||||
it('should return object', function(done){
|
||||
needle.get('localhost:' + port, function(err, response, body){
|
||||
should.ifError(err);
|
||||
body.should.equal(false);
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('and parse option is true', function() {
|
||||
|
||||
describe('and JSON is valid', function() {
|
||||
|
||||
it('should return object', function(done){
|
||||
needle.get('localhost:' + port, { parse: true }, function(err, response, body){
|
||||
should.not.exist(err);
|
||||
body.should.equal(false)
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
describe('and response is empty', function() {
|
||||
|
||||
var old_json_string;
|
||||
|
||||
before(function() {
|
||||
old_json_string = json_string;
|
||||
json_string = "";
|
||||
});
|
||||
|
||||
after(function() {
|
||||
json_string = old_json_string;
|
||||
});
|
||||
|
||||
it('should return an empty string', function(done) {
|
||||
needle.get('localhost:' + port, { parse: true }, function(err, resp) {
|
||||
should.not.exist(err);
|
||||
resp.body.should.equal('');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('and JSON is invalid', function() {
|
||||
|
||||
var old_json_string;
|
||||
|
||||
before(function() {
|
||||
old_json_string = json_string;
|
||||
json_string = "this is not going to work";
|
||||
});
|
||||
|
||||
after(function() {
|
||||
json_string = old_json_string;
|
||||
});
|
||||
|
||||
it('does not throw', function(done) {
|
||||
(function(){
|
||||
needle.get('localhost:' + port, { parse: true }, done);
|
||||
}).should.not.throw();
|
||||
});
|
||||
|
||||
it('does NOT return object', function(done) {
|
||||
needle.get('localhost:' + port, { parse: true }, function(err, response, body) {
|
||||
should.not.exist(err);
|
||||
body.should.be.a.String;
|
||||
body.toString().should.eql('this is not going to work');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
})
|
||||
|
||||
describe('and parse option is false', function() {
|
||||
|
||||
it('does NOT return object', function(done){
|
||||
needle.get('localhost:' + port, { parse: false }, function(err, response, body) {
|
||||
should.not.exist(err);
|
||||
body.should.be.an.instanceof(Buffer)
|
||||
body.toString().should.eql('false');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('and parse option is "xml"', function() {
|
||||
|
||||
it('does NOT return object', function(done){
|
||||
needle.get('localhost:' + port, { parse: 'xml' }, function(err, response, body) {
|
||||
should.not.exist(err);
|
||||
body.should.be.an.instanceof(Buffer)
|
||||
body.toString().should.eql('false');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
});
|
||||
|
||||
describe('when response is an XML string', function(){
|
||||
|
||||
before(function(done){
|
||||
server = http.createServer(function(req, res) {
|
||||
res.writeHeader(200, {'Content-Type': 'application/xml'})
|
||||
res.end("<post><body>hello there</body></post>")
|
||||
}).listen(port, done);
|
||||
});
|
||||
|
||||
after(function(done){
|
||||
server.close(done);
|
||||
})
|
||||
|
||||
describe('and xml2js library is present', function(){
|
||||
|
||||
require.bind(null, 'xml2js').should.not.throw();
|
||||
|
||||
describe('and parse_response is true', function(){
|
||||
|
||||
it('should return valid object', function(done){
|
||||
needle.get('localhost:' + port, function(err, response, body){
|
||||
should.not.exist(err);
|
||||
body.post.should.have.property('body', 'hello there');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
it('should have a .parser = json property', function(done) {
|
||||
needle.get('localhost:' + port, function(err, resp) {
|
||||
should.not.exist(err);
|
||||
resp.parser.should.eql('xml');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('and parse response is not true', function(){
|
||||
|
||||
it('should return xml string', function(){
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('and xml2js is not found', function(){
|
||||
|
||||
it('should return xml string', function(){
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
186
node_modules/buildmail/node_modules/needle/test/proxy_spec.js
generated
vendored
Normal file
186
node_modules/buildmail/node_modules/needle/test/proxy_spec.js
generated
vendored
Normal file
@@ -0,0 +1,186 @@
|
||||
var helpers = require('./helpers'),
|
||||
should = require('should'),
|
||||
sinon = require('sinon'),
|
||||
http = require('http'),
|
||||
needle = require('./../');
|
||||
|
||||
var port = 7707;
|
||||
var url = 'localhost:' + port;
|
||||
var nonexisting_host = 'awepfokawepofawe.com';
|
||||
|
||||
describe('proxy option', function() {
|
||||
|
||||
var spy, opts;
|
||||
|
||||
function send_request(opts, done) {
|
||||
if (spy) spy.restore();
|
||||
spy = sinon.spy(http, 'request');
|
||||
needle.get(url, opts, done);
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// proxy opts helpers
|
||||
|
||||
function not_proxied(done) {
|
||||
return function(err, resp) {
|
||||
var path = spy.args[0][0].path;
|
||||
path.should.eql('/'); // not the full original URI
|
||||
spy.restore();
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
function proxied(host, port, done) {
|
||||
return function(err, resp) {
|
||||
var path = spy.args[0][0].path;
|
||||
path.should.eql('http://' + url); // the full original URI
|
||||
|
||||
var http_host = spy.args[0][0].host;
|
||||
if (http_host) http_host.should.eql(host);
|
||||
|
||||
var http_port = spy.args[0][0].port;
|
||||
if (http_port) http_port.should.eql(port);
|
||||
|
||||
spy.restore();
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////
|
||||
// auth helpers
|
||||
|
||||
var get_auth = function(header) {
|
||||
var token = header.split(/\s+/).pop();
|
||||
return token && new Buffer(token, 'base64').toString().split(':');
|
||||
}
|
||||
|
||||
function no_proxy_auth(done) {
|
||||
return function(err, resp) {
|
||||
var headers = spy.args[0][0].headers;
|
||||
Object.keys(headers).should.not.containEql('proxy-authorization');
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
function header_set(name, user, pass, done) {
|
||||
return function(err, resp) {
|
||||
var headers = spy.args[0][0].headers;
|
||||
var auth = get_auth(headers[name]);
|
||||
auth[0].should.eql(user);
|
||||
auth[1].should.eql(pass);
|
||||
done();
|
||||
}
|
||||
}
|
||||
|
||||
function proxy_auth_set(user, pass, done) {
|
||||
return header_set('Proxy-Authorization', user, pass, done);
|
||||
}
|
||||
|
||||
function basic_auth_set(user, pass, done) {
|
||||
return header_set('Authorization', user, pass, done);
|
||||
}
|
||||
|
||||
after(function() {
|
||||
spy.restore();
|
||||
})
|
||||
|
||||
describe('when null proxy is passed', function() {
|
||||
|
||||
it('does not proxy', function(done) {
|
||||
send_request({ proxy: null }, not_proxied(done))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('when weird string is passed', function() {
|
||||
|
||||
it('tries to proxy anyway', function(done) {
|
||||
send_request({ proxy: 'alfalfa' }, proxied('alfalfa', 80, done))
|
||||
})
|
||||
})
|
||||
|
||||
describe('when valid url is passed', function() {
|
||||
|
||||
it('proxies request', function(done) {
|
||||
send_request({ proxy: nonexisting_host + ':123/done' }, proxied(nonexisting_host, '123', done))
|
||||
})
|
||||
|
||||
it('does not set a Proxy-Authorization header', function(done) {
|
||||
send_request({ proxy: nonexisting_host + ':123/done' }, no_proxy_auth(done));
|
||||
})
|
||||
|
||||
describe('and proxy url contains user:pass', function() {
|
||||
|
||||
before(function() {
|
||||
opts = {
|
||||
proxy: 'http://mj:x@' + nonexisting_host + ':123/done'
|
||||
}
|
||||
})
|
||||
|
||||
it('proxies request', function(done) {
|
||||
send_request(opts, proxied(nonexisting_host, '123', done))
|
||||
})
|
||||
|
||||
it('sets Proxy-Authorization header', function(done) {
|
||||
send_request(opts, proxy_auth_set('mj', 'x', done));
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('and a proxy_user is passed', function() {
|
||||
|
||||
before(function() {
|
||||
opts = {
|
||||
proxy: nonexisting_host + ':123',
|
||||
proxy_user: 'someone',
|
||||
proxy_pass: 'else'
|
||||
}
|
||||
})
|
||||
|
||||
it('proxies request', function(done) {
|
||||
send_request(opts, proxied(nonexisting_host, '123', done))
|
||||
})
|
||||
|
||||
it('sets Proxy-Authorization header', function(done) {
|
||||
send_request(opts, proxy_auth_set('someone', 'else', done));
|
||||
})
|
||||
|
||||
describe('and url also contains user:pass', function() {
|
||||
|
||||
it('url user:pass wins', function(done) {
|
||||
var opts = {
|
||||
proxy: 'http://xxx:yyy@' + nonexisting_host + ':123',
|
||||
proxy_user: 'someone',
|
||||
proxy_pass: 'else'
|
||||
}
|
||||
|
||||
send_request(opts, proxy_auth_set('xxx', 'yyy', done));
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('and options.username is also present', function() {
|
||||
|
||||
before(function() {
|
||||
opts = { proxy_user: 'foobar', username: 'someone' };
|
||||
})
|
||||
|
||||
it('a separate Authorization header is set', function(done) {
|
||||
var opts = {
|
||||
proxy: nonexisting_host + ':123',
|
||||
proxy_user: 'someone',
|
||||
proxy_pass: 'else',
|
||||
username: 'test',
|
||||
password: 'X'
|
||||
}
|
||||
|
||||
send_request(opts, basic_auth_set('test', 'X', done));
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
110
node_modules/buildmail/node_modules/needle/test/querystring_spec.js
generated
vendored
Normal file
110
node_modules/buildmail/node_modules/needle/test/querystring_spec.js
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
var should = require('should'),
|
||||
stringify = require('../lib/querystring').build;
|
||||
|
||||
describe('stringify', function() {
|
||||
|
||||
describe('with null', function() {
|
||||
|
||||
it('throws', function() {
|
||||
(function() {
|
||||
var res = stringify(null);
|
||||
}).should.throw();
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('with a number', function() {
|
||||
|
||||
it('throws', function() {
|
||||
(function() {
|
||||
var res = stringify(100);
|
||||
}).should.throw();
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('with a string', function() {
|
||||
|
||||
describe('that is empty', function() {
|
||||
|
||||
it('throws', function() {
|
||||
(function() {
|
||||
var res = stringify('');
|
||||
}).should.throw();
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('that doesnt contain an equal sign', function() {
|
||||
|
||||
it('throws', function() {
|
||||
(function() {
|
||||
var res = stringify('boomshagalaga');
|
||||
}).should.throw();
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('that contains an equal sign', function() {
|
||||
|
||||
it('works', function() {
|
||||
var res = stringify('hello=123');
|
||||
res.should.eql('hello=123');
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('with an array', function() {
|
||||
|
||||
describe('with key val objects', function() {
|
||||
|
||||
it('works', function() {
|
||||
var res = stringify([ {foo: 'bar'} ]);
|
||||
res.should.eql('foo=bar');
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('where all elements are strings with an equal sign', function() {
|
||||
|
||||
it('works', function() {
|
||||
var res = stringify([ 'bar=123', 'quux=' ]);
|
||||
res.should.eql('bar=123&quux=');
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('with random words', function() {
|
||||
|
||||
it('throws', function() {
|
||||
(function() {
|
||||
var res = stringify(['hello', 'there']);
|
||||
}).should.throw();
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('with integers', function() {
|
||||
|
||||
it('throws', function() {
|
||||
(function() {
|
||||
var res = stringify([123, 432]);
|
||||
}).should.throw();
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('with an object', function() {
|
||||
|
||||
it('works', function() {
|
||||
var res = stringify({ test: 100 });
|
||||
res.should.eql('test=100');
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
387
node_modules/buildmail/node_modules/needle/test/redirect_spec.js
generated
vendored
Normal file
387
node_modules/buildmail/node_modules/needle/test/redirect_spec.js
generated
vendored
Normal file
@@ -0,0 +1,387 @@
|
||||
var helpers = require('./helpers'),
|
||||
should = require('should'),
|
||||
sinon = require('sinon'),
|
||||
needle = require('./../');
|
||||
|
||||
var ports = {
|
||||
http : 8888,
|
||||
https : 9999
|
||||
}
|
||||
|
||||
var protocols = {
|
||||
http : require('http'),
|
||||
https : require('https')
|
||||
}
|
||||
|
||||
var code = 301;
|
||||
var location; // var to set the response location
|
||||
|
||||
function response_code() {
|
||||
return code;
|
||||
}
|
||||
|
||||
function response_headers() {
|
||||
return { 'Content-Type': 'text/plain', 'Location': location }
|
||||
}
|
||||
|
||||
describe('redirects', function() {
|
||||
|
||||
var spies = {},
|
||||
servers = {};
|
||||
|
||||
var current_protocol;
|
||||
var hostname = require('os').hostname();
|
||||
|
||||
// open two servers, one that responds to a redirect
|
||||
before(function(done) {
|
||||
|
||||
var conf = {
|
||||
port : ports.http,
|
||||
code : response_code,
|
||||
headers : response_headers
|
||||
}
|
||||
|
||||
servers.http = helpers.server(conf, function() {
|
||||
conf.port = ports.https;
|
||||
conf.protocol = 'https';
|
||||
servers.https = helpers.server(conf, done);
|
||||
});
|
||||
})
|
||||
|
||||
after(function(done) {
|
||||
servers.http.close(function() {
|
||||
servers.https.close(done);
|
||||
});
|
||||
})
|
||||
|
||||
var prots = {'http': 'https'};
|
||||
Object.keys(prots).forEach(function(protocol) {
|
||||
|
||||
current_protocol = protocol;
|
||||
var other_protocol = protocol == 'http' ? 'https' : 'http';
|
||||
|
||||
var opts, // each test will modify this
|
||||
host = '127.0.0.1',
|
||||
url = protocol + '://' + host + ':' + ports[protocol] + '/hello';
|
||||
|
||||
function send_request(opts, cb) {
|
||||
opts.rejectUnauthorized = false;
|
||||
// console.log(' -- sending request ' + url + ' -- redirect to ' + location);
|
||||
needle.post(url, { foo: 'bar' }, opts, cb);
|
||||
}
|
||||
|
||||
function not_followed(done) {
|
||||
send_request(opts, function(err, resp) {
|
||||
resp.statusCode.should.eql(301);
|
||||
if (current_protocol == 'http') {
|
||||
spies.http.callCount.should.eql(1); // only original request
|
||||
spies.https.callCount.should.eql(0);
|
||||
} else {
|
||||
spies.http.callCount.should.eql(0);
|
||||
spies.https.callCount.should.eql(1); // only original request
|
||||
}
|
||||
done();
|
||||
})
|
||||
}
|
||||
|
||||
function followed_same_protocol(done) {
|
||||
send_request(opts, function(err, resp) {
|
||||
// the original request plus the redirect one
|
||||
spies[current_protocol].callCount.should.eql(2);
|
||||
done();
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
function followed_other_protocol(done) {
|
||||
send_request(opts, function(err, resp) {
|
||||
spies.http.callCount.should.eql(1); // the one from http.request
|
||||
spies.https.callCount.should.eql(1); // the one from https.request (redirect)
|
||||
done();
|
||||
})
|
||||
}
|
||||
|
||||
// set a spy on [protocol].request
|
||||
// so we can see how many times a request was made
|
||||
before(function() {
|
||||
spies.http = sinon.spy(protocols.http, 'request');
|
||||
spies.https = sinon.spy(protocols.https, 'request');
|
||||
})
|
||||
|
||||
// and make sure it is restored after each test
|
||||
afterEach(function() {
|
||||
spies.http.reset();
|
||||
spies.https.reset();
|
||||
})
|
||||
|
||||
after(function() {
|
||||
spies.http.restore();
|
||||
spies.https.restore();
|
||||
})
|
||||
|
||||
describe('when overriding defaults', function() {
|
||||
|
||||
before(function() {
|
||||
needle.defaults({ follow_max: 10 });
|
||||
opts = {};
|
||||
})
|
||||
|
||||
after(function() {
|
||||
// reset values to previous
|
||||
needle.defaults({ follow_max: 0 });
|
||||
})
|
||||
|
||||
describe('and redirected to the same path on same host and protocol', function() {
|
||||
before(function() {
|
||||
location = url;
|
||||
})
|
||||
it('does not follow redirect', not_followed);
|
||||
})
|
||||
|
||||
describe('and redirected to the same path on same host and different protocol', function() {
|
||||
before(function() {
|
||||
location = url.replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]);
|
||||
})
|
||||
|
||||
it('follows redirect', followed_other_protocol);
|
||||
})
|
||||
|
||||
describe('and redirected to a different path on same host, same protocol', function() {
|
||||
before(function() {
|
||||
location = url.replace('/hello', '/goodbye');
|
||||
})
|
||||
it('follows redirect', followed_same_protocol);
|
||||
})
|
||||
|
||||
describe('and redirected to a different path on same host, different protocol', function() {
|
||||
before(function() {
|
||||
location = url.replace('/hello', '/goodbye').replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]);
|
||||
})
|
||||
it('follows redirect', followed_other_protocol);
|
||||
})
|
||||
|
||||
describe('and redirected to same path on another host, same protocol', function() {
|
||||
before(function() {
|
||||
location = url.replace(host, hostname);
|
||||
})
|
||||
it('follows redirect', followed_same_protocol);
|
||||
})
|
||||
|
||||
describe('and redirected to same path on another host, different protocol', function() {
|
||||
before(function() {
|
||||
location = url.replace(host, hostname).replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]);
|
||||
})
|
||||
it('follows redirect', followed_other_protocol);
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
// false and null have the same result
|
||||
var values = [false, null];
|
||||
values.forEach(function(value) {
|
||||
|
||||
describe('when follow is ' + value, function() {
|
||||
|
||||
before(function() {
|
||||
opts = { follow: value };
|
||||
})
|
||||
|
||||
|
||||
|
||||
describe('and redirected to the same path on same host and protocol', function() {
|
||||
before(function() {
|
||||
location = url;
|
||||
})
|
||||
|
||||
it('throws an error', function() {
|
||||
(function() {
|
||||
send_request(opts, function() { });
|
||||
}).should.throw;
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('when follow is true', function() {
|
||||
|
||||
before(function() {
|
||||
opts = { follow: true };
|
||||
})
|
||||
|
||||
describe('and redirected to the same path on same host and protocol', function() {
|
||||
before(function() { location = url })
|
||||
|
||||
it('throws an error', function() {
|
||||
(function() {
|
||||
send_request(opts, function() { });
|
||||
}).should.throw;
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('when follow is > 0', function() {
|
||||
|
||||
before(function() {
|
||||
needle.defaults({ follow: 10 });
|
||||
})
|
||||
|
||||
after(function() {
|
||||
needle.defaults({ follow: 0 });
|
||||
})
|
||||
|
||||
describe('when keep_method is false', function() {
|
||||
|
||||
before(function() {
|
||||
opts = { follow_keep_method: false };
|
||||
})
|
||||
|
||||
// defaults to follow host and protocol
|
||||
describe('and redirected to the same path on same host and different protocol', function() {
|
||||
|
||||
before(function() {
|
||||
location = url.replace(protocol, other_protocol);
|
||||
})
|
||||
|
||||
it('follows redirect', followed_other_protocol);
|
||||
|
||||
it('sends a GET request with no data', function(done) {
|
||||
send_request(opts, function(err, resp) {
|
||||
spies.http.args[0][0].method.should.eql('GET');
|
||||
// spy.args[0][3].should.eql(null);
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('and set_referer is true', function() {
|
||||
|
||||
before(function() {
|
||||
opts = { follow_set_referer: true };
|
||||
})
|
||||
|
||||
// defaults to follow host and protocol
|
||||
describe('and redirected to the same path on same host and different protocol', function() {
|
||||
|
||||
before(function() {
|
||||
location = url.replace(protocol, other_protocol);
|
||||
})
|
||||
|
||||
it('follows redirect', followed_other_protocol);
|
||||
|
||||
it('sets Referer header when following redirect', function(done) {
|
||||
send_request(opts, function(err, resp) {
|
||||
spies.http.args[0][0].headers['Referer'].should.eql("http://" + host + ":8888/hello");
|
||||
// spies.http.args[0][3].should.eql({ foo: 'bar'});
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('and keep_method is true', function() {
|
||||
|
||||
before(function() {
|
||||
opts = { follow_keep_method: true };
|
||||
})
|
||||
|
||||
// defaults to follow host and protocol
|
||||
describe('and redirected to the same path on same host and different protocol', function() {
|
||||
|
||||
before(function() {
|
||||
location = url.replace(protocol, other_protocol);
|
||||
})
|
||||
|
||||
it('follows redirect', followed_other_protocol);
|
||||
|
||||
it('sends a POST request with the original data', function(done) {
|
||||
send_request(opts, function(err, resp) {
|
||||
spies.http.args[0][0].method.should.eql('post');
|
||||
// spies.http.args[0][3].should.eql({ foo: 'bar'});
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('and if_same_host is false', function() {
|
||||
|
||||
before(function() {
|
||||
opts = { follow_if_same_host: false };
|
||||
})
|
||||
|
||||
// by default it will follow other protocols
|
||||
describe('and redirected to same path on another domain, same protocol', function() {
|
||||
before(function() {
|
||||
location = url.replace(host, hostname);
|
||||
})
|
||||
it('follows redirect', followed_same_protocol);
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('and if_same_host is true', function() {
|
||||
|
||||
before(function() {
|
||||
opts = { follow_if_same_host: true };
|
||||
})
|
||||
|
||||
// by default it will follow other protocols
|
||||
describe('and redirected to same path on another domain, same protocol', function() {
|
||||
before(function() {
|
||||
location = url.replace(host, hostname);
|
||||
})
|
||||
|
||||
it('does not follow redirect', not_followed);
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('and if_same_protocol is false', function() {
|
||||
|
||||
before(function() {
|
||||
opts = { follow_if_same_protocol: false };
|
||||
})
|
||||
|
||||
// by default it will follow other hosts
|
||||
describe('and redirected to same path on another domain, different protocol', function() {
|
||||
before(function() {
|
||||
location = url.replace(host, hostname).replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]);
|
||||
})
|
||||
it('follows redirect', followed_other_protocol);
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('and if_same_protocol is true', function() {
|
||||
|
||||
before(function() {
|
||||
opts = { follow_if_same_protocol: true };
|
||||
})
|
||||
|
||||
// by default it will follow other hosts
|
||||
describe('and redirected to same path on another domain, different protocol', function() {
|
||||
before(function() {
|
||||
location = url.replace(host, hostname).replace(protocol, other_protocol).replace(ports[protocol], ports[other_protocol]);
|
||||
})
|
||||
it('does not follow redirect', not_followed);
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
});
|
||||
118
node_modules/buildmail/node_modules/needle/test/stream_spec.js
generated
vendored
Normal file
118
node_modules/buildmail/node_modules/needle/test/stream_spec.js
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
var should = require('should'),
|
||||
needle = require('./../'),
|
||||
http = require('http'),
|
||||
stream = require('stream'),
|
||||
fs = require('fs'),
|
||||
port = 11111,
|
||||
server;
|
||||
|
||||
describe('stream', function() {
|
||||
|
||||
describe('when the server sends back json', function(){
|
||||
|
||||
before(function(){
|
||||
server = http.createServer(function(req, res) {
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
res.end('{"foo":"bar"}')
|
||||
}).listen(port);
|
||||
});
|
||||
|
||||
after(function(){
|
||||
server.close();
|
||||
})
|
||||
|
||||
describe('and the client uses streams', function(){
|
||||
|
||||
it('should create a proper streams2 stream', function(done) {
|
||||
var stream = needle.get('localhost:' + port)
|
||||
|
||||
stream._readableState.flowing.should.be.false;
|
||||
|
||||
var readableCalled = false;
|
||||
stream.on('readable', function () {
|
||||
readableCalled = true;
|
||||
})
|
||||
|
||||
stream.on('end', function () {
|
||||
readableCalled.should.be.true;
|
||||
done();
|
||||
});
|
||||
|
||||
stream.resume();
|
||||
})
|
||||
|
||||
it('should should emit a single data item which is our JSON object', function(done) {
|
||||
var stream = needle.get('localhost:' + port)
|
||||
|
||||
var chunks = [];
|
||||
stream.on('readable', function () {
|
||||
while (chunk = this.read()) {
|
||||
chunk.should.be.an.Object;
|
||||
chunks.push(chunk);
|
||||
}
|
||||
})
|
||||
|
||||
stream.on('end', function () {
|
||||
chunks.should.have.length(1)
|
||||
chunks[0].should.have.property('foo', 'bar');
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
it('should should emit a raw buffer if we do not want to parse JSON', function(done) {
|
||||
var stream = needle.get('localhost:' + port, {parse: false})
|
||||
|
||||
var chunks = [];
|
||||
stream.on('readable', function () {
|
||||
while (chunk = this.read()) {
|
||||
Buffer.isBuffer(chunk).should.be.true;
|
||||
chunks.push(chunk);
|
||||
}
|
||||
})
|
||||
|
||||
stream.on('end', function () {
|
||||
var body = Buffer.concat(chunks).toString();
|
||||
body.should.equal('{"foo":"bar"}')
|
||||
done();
|
||||
});
|
||||
})
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
describe('when the server sends back what was posted to it', function () {
|
||||
var file = 'asdf.txt';
|
||||
|
||||
before(function(done){
|
||||
server = http.createServer(function(req, res) {
|
||||
res.setHeader('Content-Type', 'application/octet')
|
||||
req.pipe(res);
|
||||
}).listen(port);
|
||||
|
||||
fs.writeFile(file, 'contents of stream', done);
|
||||
});
|
||||
|
||||
after(function(done){
|
||||
server.close();
|
||||
fs.unlink(file, done);
|
||||
})
|
||||
|
||||
it('can PUT a stream', function (done) {
|
||||
var stream = needle.put('localhost:' + port, fs.createReadStream(file), { stream: true });
|
||||
|
||||
var chunks = [];
|
||||
stream.on('readable', function () {
|
||||
while (chunk = this.read()) {
|
||||
Buffer.isBuffer(chunk).should.be.true;
|
||||
chunks.push(chunk);
|
||||
}
|
||||
})
|
||||
|
||||
stream.on('end', function () {
|
||||
var body = Buffer.concat(chunks).toString();
|
||||
body.should.equal('contents of stream')
|
||||
done();
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
128
node_modules/buildmail/node_modules/needle/test/url_spec.js
generated
vendored
Normal file
128
node_modules/buildmail/node_modules/needle/test/url_spec.js
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
var needle = require('../'),
|
||||
sinon = require('sinon'),
|
||||
should = require('should'),
|
||||
http = require('http'),
|
||||
helpers = require('./helpers');
|
||||
|
||||
describe('urls', function() {
|
||||
|
||||
var server, url;
|
||||
|
||||
function send_request(cb) {
|
||||
return needle.get(url, cb);
|
||||
}
|
||||
|
||||
before(function(){
|
||||
server = helpers.server({ port: 3333 });
|
||||
})
|
||||
|
||||
after(function(){
|
||||
server.close();
|
||||
})
|
||||
|
||||
describe('null URL', function(){
|
||||
|
||||
it('throws', function(){
|
||||
(function() {
|
||||
send_request()
|
||||
}).should.throw();
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
describe('invalid protocol', function(){
|
||||
|
||||
before(function() {
|
||||
url = 'foo://google.com/what'
|
||||
})
|
||||
|
||||
it('fails', function(done) {
|
||||
send_request(function(err){
|
||||
err.should.be.an.Error;
|
||||
err.code.should.eql('ENOTFOUND');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('invalid host', function(){
|
||||
|
||||
before(function() {
|
||||
url = 'http://s1\\\2.com/'
|
||||
})
|
||||
|
||||
it('fails', function(done) {
|
||||
send_request(function(err){
|
||||
err.should.be.an.Error;
|
||||
err.code.should.eql('ENOTFOUND');
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
/*
|
||||
describe('invalid path', function(){
|
||||
|
||||
before(function() {
|
||||
url = 'http://www.google.com\\\/x\\\ %^&*() /x2.com/'
|
||||
})
|
||||
|
||||
it('fails', function(done) {
|
||||
send_request(function(err) {
|
||||
err.should.be.an.Error;
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
*/
|
||||
|
||||
describe('valid protocol and path', function() {
|
||||
|
||||
before(function() {
|
||||
url = 'http://localhost:3333/foo';
|
||||
})
|
||||
|
||||
it('works', function(done) {
|
||||
send_request(function(err){
|
||||
should.not.exist(err);
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('no protocol but with slashes and valid path', function() {
|
||||
|
||||
before(function() {
|
||||
url = '//localhost:3333/foo';
|
||||
})
|
||||
|
||||
it('works', function(done) {
|
||||
send_request(function(err){
|
||||
should.not.exist(err);
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
describe('no protocol nor slashes and valid path', function() {
|
||||
|
||||
before(function() {
|
||||
url = 'localhost:3333/foo';
|
||||
})
|
||||
|
||||
it('works', function(done) {
|
||||
send_request(function(err){
|
||||
should.not.exist(err);
|
||||
done();
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
17
node_modules/buildmail/node_modules/needle/test/utils/formidable.js
generated
vendored
Normal file
17
node_modules/buildmail/node_modules/needle/test/utils/formidable.js
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
var formidable = require('formidable'),
|
||||
http = require('http'),
|
||||
util = require('util');
|
||||
|
||||
var port = process.argv[2] || 8888;
|
||||
|
||||
http.createServer(function(req, res) {
|
||||
var form = new formidable.IncomingForm();
|
||||
form.parse(req, function(err, fields, files) {
|
||||
res.writeHead(200, {'content-type': 'text/plain'});
|
||||
res.write('received upload:\n\n');
|
||||
console.log(util.inspect({fields: fields, files: files}))
|
||||
res.end(util.inspect({fields: fields, files: files}));
|
||||
});
|
||||
}).listen(port);
|
||||
|
||||
console.log('HTTP server listening on port ' + port);
|
||||
62
node_modules/buildmail/node_modules/needle/test/utils/proxy.js
generated
vendored
Normal file
62
node_modules/buildmail/node_modules/needle/test/utils/proxy.js
generated
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
var http = require('http'),
|
||||
https = require('https'),
|
||||
url = require('url');
|
||||
|
||||
var port = 1234,
|
||||
log = true,
|
||||
request_auth = false;
|
||||
|
||||
http.createServer(function(request, response) {
|
||||
|
||||
console.log(request.headers);
|
||||
console.log("Got request: " + request.url);
|
||||
console.log("Forwarding request to " + request.headers['host']);
|
||||
|
||||
if (request_auth) {
|
||||
if (!request.headers['proxy-authorization']) {
|
||||
response.writeHead(407, {'Proxy-Authenticate': 'Basic realm="proxy.com"'})
|
||||
return response.end('Hello.');
|
||||
}
|
||||
}
|
||||
|
||||
var remote = url.parse(request.url);
|
||||
var protocol = remote.protocol == 'https:' ? https : http;
|
||||
|
||||
var opts = {
|
||||
host: request.headers['host'],
|
||||
port: remote.port || (remote.protocol == 'https:' ? 443 : 80),
|
||||
method: request.method,
|
||||
path: remote.pathname,
|
||||
headers: request.headers
|
||||
}
|
||||
|
||||
var proxy_request = protocol.request(opts, function(proxy_response){
|
||||
|
||||
proxy_response.on('data', function(chunk) {
|
||||
if (log) console.log(chunk.toString());
|
||||
response.write(chunk, 'binary');
|
||||
});
|
||||
proxy_response.on('end', function() {
|
||||
response.end();
|
||||
});
|
||||
|
||||
response.writeHead(proxy_response.statusCode, proxy_response.headers);
|
||||
});
|
||||
|
||||
request.on('data', function(chunk) {
|
||||
if (log) console.log(chunk.toString());
|
||||
proxy_request.write(chunk, 'binary');
|
||||
});
|
||||
|
||||
request.on('end', function() {
|
||||
proxy_request.end();
|
||||
});
|
||||
|
||||
}).listen(port);
|
||||
|
||||
process.on('uncaughtException', function(err){
|
||||
console.log('Uncaught exception!');
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
console.log("Proxy server listening on port " + port);
|
||||
104
node_modules/buildmail/node_modules/needle/test/utils/test.js
generated
vendored
Normal file
104
node_modules/buildmail/node_modules/needle/test/utils/test.js
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
// TODO: write specs. :)
|
||||
|
||||
var fs = require('fs'),
|
||||
client = require('./../../');
|
||||
|
||||
process.env.DEBUG = true;
|
||||
|
||||
var response_callback = function(err, resp, body){
|
||||
console.log(err);
|
||||
if(resp) console.log("Got status code " + resp.statusCode)
|
||||
console.log(body);
|
||||
}
|
||||
|
||||
function simple_head(){
|
||||
client.head('http://www.amazon.com', response_callback);
|
||||
}
|
||||
|
||||
function simple_get(){
|
||||
client.get('http://www.nodejs.org', response_callback);
|
||||
}
|
||||
|
||||
function proxy_get(){
|
||||
client.get('https://www.google.com/search?q=nodejs', {proxy: 'http://localhost:1234'}, response_callback);
|
||||
}
|
||||
|
||||
function auth_get(){
|
||||
client.get('https://www.twitter.com', {username: 'asd', password: '123'}, response_callback);
|
||||
}
|
||||
|
||||
function simple_post(url){
|
||||
|
||||
var data = {
|
||||
foo: 'bar',
|
||||
baz: {
|
||||
nested: 'attribute'
|
||||
}
|
||||
}
|
||||
|
||||
client.post(url, data, response_callback);
|
||||
|
||||
}
|
||||
|
||||
function multipart_post(url){
|
||||
|
||||
var filename = 'test_file.txt';
|
||||
var data = 'Plain text data.\nLorem ipsum dolor sit amet.\nBla bla bla.\n';
|
||||
fs.writeFileSync(filename, data);
|
||||
|
||||
var black_pixel = new Buffer("data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=".replace(/^data:image\/\w+;base64,/, ""), "base64");
|
||||
|
||||
var data = {
|
||||
foo: 'bar',
|
||||
bar: 'baz',
|
||||
nested: {
|
||||
my_document: { file: filename, content_type: 'text/plain' },
|
||||
even: {
|
||||
more: 'nesting'
|
||||
}
|
||||
},
|
||||
pixel: { filename: 'black_pixel.gif', buffer: black_pixel, content_type: 'image/gif' },
|
||||
field2: {value: JSON.stringify({"json":[ {"one":1}, {"two":2} ]}), content_type: 'application/json' }
|
||||
}
|
||||
|
||||
client.post(url, data, {multipart: true}, function(err, resp, body){
|
||||
|
||||
console.log(err);
|
||||
console.log("Got status code " + resp.statusCode)
|
||||
console.log(body);
|
||||
fs.unlink(filename);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
switch(process.argv[2]){
|
||||
case 'head':
|
||||
simple_head();
|
||||
break;
|
||||
case 'get':
|
||||
simple_get();
|
||||
break;
|
||||
case 'auth':
|
||||
auth_get();
|
||||
break;
|
||||
case 'proxy':
|
||||
proxy_get();
|
||||
break;
|
||||
case 'post':
|
||||
simple_post(process.argv[3] || 'http://posttestserver.com/post.php');
|
||||
break;
|
||||
case 'multipart':
|
||||
multipart_post(process.argv[3] || 'http://posttestserver.com/post.php?dir=example');
|
||||
break;
|
||||
case 'all':
|
||||
simple_head();
|
||||
simple_get();
|
||||
auth_get();
|
||||
proxy_get();
|
||||
simple_post(process.argv[3] || 'http://posttestserver.com/post.php');
|
||||
multipart_post(process.argv[3] || 'http://posttestserver.com/post.php?dir=example');
|
||||
break;
|
||||
default:
|
||||
console.log("Usage: ./test.js [head|get|auth|proxy|multipart]")
|
||||
}
|
||||
92
node_modules/buildmail/package.json
generated
vendored
Normal file
92
node_modules/buildmail/package.json
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"_args": [
|
||||
[
|
||||
"buildmail@^2.0.0",
|
||||
"/home/mitchell/Desktop/test-mywebsite/mywebsite/node_modules/mailcomposer"
|
||||
]
|
||||
],
|
||||
"_from": "buildmail@>=2.0.0 <3.0.0",
|
||||
"_id": "buildmail@2.0.0",
|
||||
"_inCache": true,
|
||||
"_installable": true,
|
||||
"_location": "/buildmail",
|
||||
"_nodeVersion": "4.1.0",
|
||||
"_npmUser": {
|
||||
"email": "andris@kreata.ee",
|
||||
"name": "andris"
|
||||
},
|
||||
"_npmVersion": "2.14.3",
|
||||
"_phantomChildren": {
|
||||
"debug": "2.2.0",
|
||||
"iconv-lite": "0.4.13"
|
||||
},
|
||||
"_requested": {
|
||||
"name": "buildmail",
|
||||
"raw": "buildmail@^2.0.0",
|
||||
"rawSpec": "^2.0.0",
|
||||
"scope": null,
|
||||
"spec": ">=2.0.0 <3.0.0",
|
||||
"type": "range"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/mailcomposer"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/buildmail/-/buildmail-2.0.0.tgz",
|
||||
"_shasum": "f0b7b0a59e9a4a1b5066bbfa051d248f3832eece",
|
||||
"_shrinkwrap": null,
|
||||
"_spec": "buildmail@^2.0.0",
|
||||
"_where": "/home/mitchell/Desktop/test-mywebsite/mywebsite/node_modules/mailcomposer",
|
||||
"author": {
|
||||
"email": "andris@kreata.ee",
|
||||
"name": "Andris Reinman"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/andris9/buildmail/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"addressparser": "^0.3.2",
|
||||
"libbase64": "^0.1.0",
|
||||
"libmime": "^1.2.0",
|
||||
"libqp": "^1.1.0",
|
||||
"needle": "^0.10.0"
|
||||
},
|
||||
"description": "buildmail is a low level rfc2822 message composer. Define your own mime tree, no magic included.",
|
||||
"devDependencies": {
|
||||
"chai": "~3.3.0",
|
||||
"grunt": "~0.4.5",
|
||||
"grunt-contrib-jshint": "~0.11.3",
|
||||
"grunt-mocha-test": "~0.12.7",
|
||||
"mocha": "^2.3.3",
|
||||
"sinon": "^1.17.1"
|
||||
},
|
||||
"directories": {},
|
||||
"dist": {
|
||||
"shasum": "f0b7b0a59e9a4a1b5066bbfa051d248f3832eece",
|
||||
"tarball": "http://registry.npmjs.org/buildmail/-/buildmail-2.0.0.tgz"
|
||||
},
|
||||
"gitHead": "27c3924957e45d5ea399fb16dd4c210b1dd77022",
|
||||
"homepage": "https://github.com/andris9/buildmail#readme",
|
||||
"keywords": [
|
||||
"RFC2822",
|
||||
"mime"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "src/buildmail",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "andris",
|
||||
"email": "andris@node.ee"
|
||||
}
|
||||
],
|
||||
"name": "buildmail",
|
||||
"optionalDependencies": {},
|
||||
"readme": "ERROR: No README data found!",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/andris9/buildmail.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "grunt"
|
||||
},
|
||||
"version": "2.0.0"
|
||||
}
|
||||
925
node_modules/buildmail/src/buildmail.js
generated
vendored
Normal file
925
node_modules/buildmail/src/buildmail.js
generated
vendored
Normal file
@@ -0,0 +1,925 @@
|
||||
'use strict';
|
||||
|
||||
var libmime = require('libmime');
|
||||
var libqp = require('libqp');
|
||||
var libbase64 = require('libbase64');
|
||||
var punycode = require('punycode');
|
||||
var addressparser = require('addressparser');
|
||||
var stream = require('stream');
|
||||
var PassThrough = stream.PassThrough;
|
||||
var fs = require('fs');
|
||||
var needle = require('needle');
|
||||
|
||||
module.exports = MimeNode;
|
||||
|
||||
/**
|
||||
* Creates a new mime tree node. Assumes 'multipart/*' as the content type
|
||||
* if it is a branch, anything else counts as leaf. If rootNode is missing from
|
||||
* the options, assumes this is the root.
|
||||
*
|
||||
* @param {String} contentType Define the content type for the node. Can be left blank for attachments (derived from filename)
|
||||
* @param {Object} [options] optional options
|
||||
* @param {Object} [options.rootNode] root node for this tree
|
||||
* @param {Object} [options.parentNode] immediate parent for this node
|
||||
* @param {Object} [options.filename] filename for an attachment node
|
||||
* @param {String} [options.baseBoundary] shared part of the unique multipart boundary
|
||||
* @param {Boolean} [options.keepBcc] If true, do not exclude Bcc from the generated headers
|
||||
*/
|
||||
function MimeNode(contentType, options) {
|
||||
this.nodeCounter = 0;
|
||||
|
||||
options = options || {};
|
||||
|
||||
/**
|
||||
* shared part of the unique multipart boundary
|
||||
*/
|
||||
this.baseBoundary = options.baseBoundary || Date.now().toString() + Math.random();
|
||||
|
||||
/**
|
||||
* If date headers is missing and current node is the root, this value is used instead
|
||||
*/
|
||||
this.date = new Date();
|
||||
|
||||
/**
|
||||
* Root node for current mime tree
|
||||
*/
|
||||
this.rootNode = options.rootNode || this;
|
||||
|
||||
/**
|
||||
* If true include Bcc in generated headers (if available)
|
||||
*/
|
||||
this.keepBcc = !!options.keepBcc;
|
||||
|
||||
/**
|
||||
* If filename is specified but contentType is not (probably an attachment)
|
||||
* detect the content type from filename extension
|
||||
*/
|
||||
if (options.filename) {
|
||||
/**
|
||||
* Filename for this node. Useful with attachments
|
||||
*/
|
||||
this.filename = options.filename;
|
||||
if (!contentType) {
|
||||
contentType = libmime.detectMimeType(this.filename.split('.').pop());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Immediate parent for this node (or undefined if not set)
|
||||
*/
|
||||
this.parentNode = options.parentNode;
|
||||
|
||||
/**
|
||||
* An array for possible child nodes
|
||||
*/
|
||||
this.childNodes = [];
|
||||
|
||||
/**
|
||||
* Used for generating unique boundaries (prepended to the shared base)
|
||||
*/
|
||||
this._nodeId = ++this.rootNode.nodeCounter;
|
||||
|
||||
/**
|
||||
* A list of header values for this node in the form of [{key:'', value:''}]
|
||||
*/
|
||||
this._headers = [];
|
||||
|
||||
/**
|
||||
* True if the content only uses ASCII printable characters
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this._isPlainText = false;
|
||||
|
||||
/**
|
||||
* True if the content is plain text but has longer lines than allowed
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this._canUseFlowedContent = false;
|
||||
this._isFlowedContent = false;
|
||||
|
||||
/**
|
||||
* If set, use instead this value for envelopes instead of generating one
|
||||
* @type {Boolean}
|
||||
*/
|
||||
this._envelope = false;
|
||||
|
||||
/**
|
||||
* Additional transform streams that the message will be piped before
|
||||
* exposing by createReadStream
|
||||
* @type {Array}
|
||||
*/
|
||||
this._transforms = [];
|
||||
|
||||
/**
|
||||
* If content type is set (or derived from the filename) add it to headers
|
||||
*/
|
||||
if (contentType) {
|
||||
this.setHeader('content-type', contentType);
|
||||
}
|
||||
}
|
||||
|
||||
/////// PUBLIC METHODS
|
||||
|
||||
/**
|
||||
* Creates and appends a child node.Arguments provided are passed to MimeNode constructor
|
||||
*
|
||||
* @param {String} [contentType] Optional content type
|
||||
* @param {Object} [options] Optional options object
|
||||
* @return {Object} Created node object
|
||||
*/
|
||||
MimeNode.prototype.createChild = function(contentType, options) {
|
||||
if (!options && typeof contentType === 'object') {
|
||||
options = contentType;
|
||||
contentType = undefined;
|
||||
}
|
||||
var node = new MimeNode(contentType, options);
|
||||
this.appendChild(node);
|
||||
return node;
|
||||
};
|
||||
|
||||
/**
|
||||
* Appends an existing node to the mime tree. Removes the node from an existing
|
||||
* tree if needed
|
||||
*
|
||||
* @param {Object} childNode node to be appended
|
||||
* @return {Object} Appended node object
|
||||
*/
|
||||
MimeNode.prototype.appendChild = function(childNode) {
|
||||
|
||||
if (childNode.rootNode !== this.rootNode) {
|
||||
childNode.rootNode = this.rootNode;
|
||||
childNode._nodeId = ++this.rootNode.nodeCounter;
|
||||
}
|
||||
|
||||
childNode.parentNode = this;
|
||||
|
||||
this.childNodes.push(childNode);
|
||||
return childNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Replaces current node with another node
|
||||
*
|
||||
* @param {Object} node Replacement node
|
||||
* @return {Object} Replacement node
|
||||
*/
|
||||
MimeNode.prototype.replace = function(node) {
|
||||
if (node === this) {
|
||||
return this;
|
||||
}
|
||||
|
||||
this.parentNode.childNodes.forEach(function(childNode, i) {
|
||||
if (childNode === this) {
|
||||
|
||||
node.rootNode = this.rootNode;
|
||||
node.parentNode = this.parentNode;
|
||||
node._nodeId = this._nodeId;
|
||||
|
||||
this.rootNode = this;
|
||||
this.parentNode = undefined;
|
||||
|
||||
node.parentNode.childNodes[i] = node;
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes current node from the mime tree
|
||||
*
|
||||
* @return {Object} removed node
|
||||
*/
|
||||
MimeNode.prototype.remove = function() {
|
||||
if (!this.parentNode) {
|
||||
return this;
|
||||
}
|
||||
|
||||
for (var i = this.parentNode.childNodes.length - 1; i >= 0; i--) {
|
||||
if (this.parentNode.childNodes[i] === this) {
|
||||
this.parentNode.childNodes.splice(i, 1);
|
||||
this.parentNode = undefined;
|
||||
this.rootNode = this;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets a header value. If the value for selected key exists, it is overwritten.
|
||||
* You can set multiple values as well by using [{key:'', value:''}] or
|
||||
* {key: 'value'} as the first argument.
|
||||
*
|
||||
* @param {String|Array|Object} key Header key or a list of key value pairs
|
||||
* @param {String} value Header value
|
||||
* @return {Object} current node
|
||||
*/
|
||||
MimeNode.prototype.setHeader = function(key, value) {
|
||||
var added = false,
|
||||
headerValue;
|
||||
|
||||
// Allow setting multiple headers at once
|
||||
if (!value && key && typeof key === 'object') {
|
||||
// allow {key:'content-type', value: 'text/plain'}
|
||||
if (key.key && key.value) {
|
||||
this.setHeader(key.key, key.value);
|
||||
}
|
||||
// allow [{key:'content-type', value: 'text/plain'}]
|
||||
else if (Array.isArray(key)) {
|
||||
key.forEach(function(i) {
|
||||
this.setHeader(i.key, i.value);
|
||||
}.bind(this));
|
||||
}
|
||||
// allow {'content-type': 'text/plain'}
|
||||
else {
|
||||
Object.keys(key).forEach(function(i) {
|
||||
this.setHeader(i, key[i]);
|
||||
}.bind(this));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
key = this._normalizeHeaderKey(key);
|
||||
|
||||
headerValue = {
|
||||
key: key,
|
||||
value: value
|
||||
};
|
||||
|
||||
// Check if the value exists and overwrite
|
||||
for (var i = 0, len = this._headers.length; i < len; i++) {
|
||||
if (this._headers[i].key === key) {
|
||||
if (!added) {
|
||||
// replace the first match
|
||||
this._headers[i] = headerValue;
|
||||
added = true;
|
||||
} else {
|
||||
// remove following matches
|
||||
this._headers.splice(i, 1);
|
||||
i--;
|
||||
len--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// match not found, append the value
|
||||
if (!added) {
|
||||
this._headers.push(headerValue);
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a header value. If the value for selected key exists, the value is appended
|
||||
* as a new field and old one is not touched.
|
||||
* You can set multiple values as well by using [{key:'', value:''}] or
|
||||
* {key: 'value'} as the first argument.
|
||||
*
|
||||
* @param {String|Array|Object} key Header key or a list of key value pairs
|
||||
* @param {String} value Header value
|
||||
* @return {Object} current node
|
||||
*/
|
||||
MimeNode.prototype.addHeader = function(key, value) {
|
||||
|
||||
// Allow setting multiple headers at once
|
||||
if (!value && key && typeof key === 'object') {
|
||||
// allow {key:'content-type', value: 'text/plain'}
|
||||
if (key.key && key.value) {
|
||||
this.addHeader(key.key, key.value);
|
||||
}
|
||||
// allow [{key:'content-type', value: 'text/plain'}]
|
||||
else if (Array.isArray(key)) {
|
||||
key.forEach(function(i) {
|
||||
this.addHeader(i.key, i.value);
|
||||
}.bind(this));
|
||||
}
|
||||
// allow {'content-type': 'text/plain'}
|
||||
else {
|
||||
Object.keys(key).forEach(function(i) {
|
||||
this.addHeader(i, key[i]);
|
||||
}.bind(this));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
this._headers.push({
|
||||
key: this._normalizeHeaderKey(key),
|
||||
value: value
|
||||
});
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the first mathcing value of a selected key
|
||||
*
|
||||
* @param {String} key Key to search for
|
||||
* @retun {String} Value for the key
|
||||
*/
|
||||
MimeNode.prototype.getHeader = function(key) {
|
||||
key = this._normalizeHeaderKey(key);
|
||||
for (var i = 0, len = this._headers.length; i < len; i++) {
|
||||
if (this._headers[i].key === key) {
|
||||
return this._headers[i].value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets body content for current node. If the value is a string, charset is added automatically
|
||||
* to Content-Type (if it is text/*). If the value is a Buffer, you need to specify
|
||||
* the charset yourself
|
||||
*
|
||||
* @param (String|Buffer) content Body content
|
||||
* @return {Object} current node
|
||||
*/
|
||||
MimeNode.prototype.setContent = function(content) {
|
||||
var _self = this;
|
||||
this.content = content;
|
||||
if (typeof this.content.pipe === 'function') {
|
||||
this._contentErrorHandler = function(err) {
|
||||
_self.content.removeListener('error', _self._contentErrorHandler);
|
||||
_self.content = '<' + err.message + '>';
|
||||
};
|
||||
this.content.once('error', this._contentErrorHandler);
|
||||
} else if (typeof this.content === 'string') {
|
||||
this._isPlainText = libmime.isPlainText(this.content);
|
||||
if (this._isPlainText && libmime.hasLongerLines(this.content, 76)) {
|
||||
// If there are lines longer than 76 symbols/bytes, use 'format=flowed' for text nodes
|
||||
this._canUseFlowedContent = true;
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
MimeNode.prototype.build = function(callback) {
|
||||
var stream = this.createReadStream();
|
||||
var buf = [];
|
||||
var buflen = 0;
|
||||
|
||||
stream.on('data', function(chunk) {
|
||||
if (chunk && chunk.length) {
|
||||
buf.push(chunk);
|
||||
buflen += chunk.length;
|
||||
}
|
||||
});
|
||||
|
||||
stream.once('end', function(chunk) {
|
||||
if (chunk && chunk.length) {
|
||||
buf.push(chunk);
|
||||
buflen += chunk.length;
|
||||
}
|
||||
return callback(null, Buffer.concat(buf, buflen));
|
||||
});
|
||||
};
|
||||
|
||||
MimeNode.prototype.getTransferEncoding = function() {
|
||||
var transferEncoding = false;
|
||||
var contentType = (this.getHeader('Content-Type') || '').toString().toLowerCase().trim();
|
||||
|
||||
if (this.content) {
|
||||
transferEncoding = (this.getHeader('Content-Transfer-Encoding') || '').toString().toLowerCase().trim();
|
||||
if (!transferEncoding || ['base64', 'quoted-printable'].indexOf(transferEncoding) < 0) {
|
||||
if (/^text\//i.test(contentType)) {
|
||||
// If there are no special symbols, no need to modify the text
|
||||
if (this._isPlainText && (/^text\/plain/i.test(contentType) || !this._canUseFlowedContent)) {
|
||||
transferEncoding = '7bit';
|
||||
} else {
|
||||
transferEncoding = 'quoted-printable';
|
||||
}
|
||||
} else if (!/^multipart\//i.test(contentType)) {
|
||||
transferEncoding = transferEncoding || 'base64';
|
||||
}
|
||||
}
|
||||
}
|
||||
return transferEncoding;
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds the header block for the mime node. Append \r\n\r\n before writing the content
|
||||
*
|
||||
* @returns {String} Headers
|
||||
*/
|
||||
MimeNode.prototype.buildHeaders = function() {
|
||||
var _self = this;
|
||||
var transferEncoding = this.getTransferEncoding();
|
||||
var headers = [];
|
||||
|
||||
if (transferEncoding) {
|
||||
this.setHeader('Content-Transfer-Encoding', transferEncoding);
|
||||
}
|
||||
|
||||
if (this.filename && !this.getHeader('Content-Disposition')) {
|
||||
this.setHeader('Content-Disposition', 'attachment');
|
||||
}
|
||||
|
||||
// Ensure mandatory header fields
|
||||
if (this.rootNode === this) {
|
||||
if (!this.getHeader('Date')) {
|
||||
this.setHeader('Date', this.date.toUTCString().replace(/GMT/, '+0000'));
|
||||
}
|
||||
// You really should define your own Message-Id field!
|
||||
if (!this.getHeader('Message-Id')) {
|
||||
this.setHeader('Message-Id', '<' +
|
||||
// crux to generate random strings like this:
|
||||
// "1401391905590-58aa8c32-d32a065c-c1a2aad2"
|
||||
[0, 0, 0].reduce(function(prev) {
|
||||
return prev + '-' + Math.floor((1 + Math.random()) * 0x100000000).
|
||||
toString(16).
|
||||
substring(1);
|
||||
}, Date.now()) +
|
||||
'@' +
|
||||
// try to use the domain of the FROM address or fallback localhost
|
||||
(this.getEnvelope().from || 'localhost').split('@').pop() +
|
||||
'>');
|
||||
}
|
||||
if (!this.getHeader('MIME-Version')) {
|
||||
this.setHeader('MIME-Version', '1.0');
|
||||
}
|
||||
}
|
||||
|
||||
this._headers.forEach(function(header) {
|
||||
var key = header.key,
|
||||
value = header.value,
|
||||
structured,
|
||||
param;
|
||||
|
||||
switch (header.key) {
|
||||
case 'Content-Disposition':
|
||||
structured = libmime.parseHeaderValue(value);
|
||||
if (_self.filename) {
|
||||
structured.params.filename = _self.filename;
|
||||
}
|
||||
value = libmime.buildHeaderValue(structured);
|
||||
break;
|
||||
case 'Content-Type':
|
||||
structured = libmime.parseHeaderValue(value);
|
||||
|
||||
_self._handleContentType(structured);
|
||||
|
||||
if (structured.value.match(/^text\/plain\b/) && typeof _self.content === 'string') {
|
||||
if (_self._canUseFlowedContent) {
|
||||
structured.params.format = 'flowed';
|
||||
}
|
||||
|
||||
if (/[\u0080-\uFFFF]/.test(_self.content)) {
|
||||
structured.params.charset = 'utf-8';
|
||||
}
|
||||
}
|
||||
|
||||
_self._isFlowedContent = String(structured.params.format).toLowerCase().trim() === 'flowed';
|
||||
|
||||
value = libmime.buildHeaderValue(structured);
|
||||
|
||||
if (_self.filename) {
|
||||
// add support for non-compliant clients like QQ webmail
|
||||
// we can't build the value with buildHeaderValue as the value is non standard and
|
||||
// would be converted to parameter continuation encoding that we do not want
|
||||
param = libmime.encodeWords(_self.filename, 'Q', 52);
|
||||
if (param !== _self.filename || /[\s"=;]/.test(param)) {
|
||||
// include value in quotes if needed
|
||||
param = '"' + param + '"';
|
||||
}
|
||||
value += '; name=' + param;
|
||||
}
|
||||
break;
|
||||
case 'Bcc':
|
||||
if (!_self.keepBcc) {
|
||||
// skip BCC values
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// skip empty lines
|
||||
value = _self._encodeHeaderValue(key, value);
|
||||
if (!(value || '').toString().trim()) {
|
||||
return;
|
||||
}
|
||||
|
||||
headers.push(libmime.foldLines(key + ': ' + value, 76));
|
||||
});
|
||||
|
||||
return headers.join('\r\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* Streams the rfc2822 message from the current node. If this is a root node,
|
||||
* mandatory header fields are set if missing (Date, Message-Id, MIME-Version)
|
||||
*
|
||||
* @return {String} Compiled message
|
||||
*/
|
||||
MimeNode.prototype.createReadStream = function(options) {
|
||||
options = options || {};
|
||||
|
||||
var outputStream = new PassThrough(options);
|
||||
|
||||
this.stream(outputStream, options, function() {
|
||||
outputStream.end();
|
||||
});
|
||||
|
||||
for (var i = 0, len = this._transforms.length; i < len; i++) {
|
||||
outputStream = outputStream.pipe(typeof this._transforms[i] === 'function' ? this._transforms[i]() : this._transforms[i]);
|
||||
}
|
||||
|
||||
return outputStream;
|
||||
};
|
||||
|
||||
/**
|
||||
* Appends a transform stream object to the transforms list. Final output
|
||||
* is passed through this stream before exposing
|
||||
*
|
||||
* @param {Object} transform Read-Write stream
|
||||
*/
|
||||
MimeNode.prototype.transform = function(transform) {
|
||||
this._transforms.push(transform);
|
||||
};
|
||||
|
||||
MimeNode.prototype.stream = function(outputStream, options, callback) {
|
||||
var _self = this;
|
||||
var transferEncoding = this.getTransferEncoding();
|
||||
var contentStream;
|
||||
var localStream;
|
||||
|
||||
// pushes node content
|
||||
function sendContent() {
|
||||
if (_self.content) {
|
||||
|
||||
if (typeof _self.content.pipe === 'function') {
|
||||
_self.content.removeListener('error', _self._contentErrorHandler);
|
||||
_self._contentErrorHandler = function(err) {
|
||||
if (contentStream) {
|
||||
contentStream.write('<' + err.message + '>');
|
||||
contentStream.end();
|
||||
} else {
|
||||
outputStream.write('<' + err.message + '>');
|
||||
setImmediate(finalize);
|
||||
}
|
||||
};
|
||||
_self.content.once('error', _self._contentErrorHandler);
|
||||
}
|
||||
|
||||
if (['quoted-printable', 'base64'].indexOf(transferEncoding) >= 0) {
|
||||
contentStream = new(transferEncoding === 'base64' ? libbase64 : libqp).Encoder(options);
|
||||
|
||||
contentStream.pipe(outputStream, {
|
||||
end: false
|
||||
});
|
||||
contentStream.once('end', finalize);
|
||||
|
||||
localStream = _self._getStream(_self.content);
|
||||
localStream.once('error', function(err) {
|
||||
contentStream.end('<' + err.message + '>');
|
||||
});
|
||||
localStream.pipe(contentStream);
|
||||
return;
|
||||
} else {
|
||||
if (_self._isFlowedContent) {
|
||||
localStream = _self._getStream(libmime.encodeFlowed(_self.content));
|
||||
} else {
|
||||
localStream = _self._getStream(_self.content);
|
||||
}
|
||||
localStream.pipe(outputStream, {
|
||||
end: false
|
||||
});
|
||||
localStream.once('end', finalize);
|
||||
localStream.once('error', function(err) {
|
||||
localStream.write('<' + err.message + '>');
|
||||
finalize();
|
||||
});
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
return setImmediate(finalize);
|
||||
}
|
||||
}
|
||||
|
||||
// for multipart nodes, push child nodes
|
||||
// for content nodes end the stream
|
||||
function finalize() {
|
||||
var childId = 0;
|
||||
var processChildNode = function() {
|
||||
if (childId >= _self.childNodes.length) {
|
||||
outputStream.write('\r\n--' + _self.boundary + '--\r\n');
|
||||
return callback();
|
||||
}
|
||||
var child = _self.childNodes[childId++];
|
||||
outputStream.write((childId > 1 ? '\r\n' : '') + '--' + _self.boundary + '\r\n');
|
||||
child.stream(outputStream, options, function() {
|
||||
setImmediate(processChildNode);
|
||||
});
|
||||
};
|
||||
|
||||
if (_self.multipart) {
|
||||
setImmediate(processChildNode);
|
||||
} else {
|
||||
return callback();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
outputStream.write(this.buildHeaders() + '\r\n\r\n');
|
||||
setImmediate(sendContent);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets envelope to be used instead of the generated one
|
||||
*
|
||||
* @return {Object} SMTP envelope in the form of {from: 'from@example.com', to: ['to@example.com']}
|
||||
*/
|
||||
MimeNode.prototype.setEnvelope = function(envelope) {
|
||||
this._envelope = {
|
||||
from: envelope.from || false,
|
||||
to: [].concat(envelope.to || [])
|
||||
};
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates and returns an object with parsed address fields
|
||||
*
|
||||
* @return {Object} Address object
|
||||
*/
|
||||
MimeNode.prototype.getAddresses = function() {
|
||||
var addresses = {};
|
||||
|
||||
this._headers.forEach(function(header) {
|
||||
var key = header.key.toLowerCase();
|
||||
if (['from', 'sender', 'reply-to', 'to', 'cc', 'bcc'].indexOf(key) >= 0) {
|
||||
if (!Array.isArray(addresses[key])) {
|
||||
addresses[key] = [];
|
||||
}
|
||||
|
||||
this._convertAddresses(this._parseAddresses(header.value), addresses[key]);
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
return addresses;
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates and returns SMTP envelope with the sender address and a list of recipients addresses
|
||||
*
|
||||
* @return {Object} SMTP envelope in the form of {from: 'from@example.com', to: ['to@example.com']}
|
||||
*/
|
||||
MimeNode.prototype.getEnvelope = function() {
|
||||
if (this._envelope) {
|
||||
return this._envelope;
|
||||
}
|
||||
|
||||
var envelope = {
|
||||
from: false,
|
||||
to: []
|
||||
};
|
||||
this._headers.forEach(function(header) {
|
||||
var list = [];
|
||||
if (header.key === 'From' || (!envelope.from && ['Reply-To', 'Sender'].indexOf(header.key) >= 0)) {
|
||||
this._convertAddresses(this._parseAddresses(header.value), list);
|
||||
if (list.length && list[0]) {
|
||||
envelope.from = list[0].address;
|
||||
}
|
||||
} else if (['To', 'Cc', 'Bcc'].indexOf(header.key) >= 0) {
|
||||
this._convertAddresses(this._parseAddresses(header.value), envelope.to);
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
envelope.to = envelope.to.map(function(to) {
|
||||
return to.address;
|
||||
});
|
||||
|
||||
return envelope;
|
||||
};
|
||||
|
||||
/////// PRIVATE METHODS
|
||||
|
||||
/**
|
||||
* Detects and returns handle to a stream related with the content.
|
||||
*
|
||||
* @param {Mixed} content Node content
|
||||
* @returns {Object} Stream object
|
||||
*/
|
||||
MimeNode.prototype._getStream = function(content) {
|
||||
var contentStream;
|
||||
|
||||
if (typeof content.pipe === 'function') {
|
||||
return content;
|
||||
} else if (content && typeof content.path === 'string' && !content.href) {
|
||||
return fs.createReadStream(content.path);
|
||||
} else if (content && typeof content.href === 'string') {
|
||||
contentStream = new PassThrough();
|
||||
needle.get(content.href, {
|
||||
decode_response: false,
|
||||
parse_response: false,
|
||||
compressed: true,
|
||||
follow_max: 5
|
||||
}).on('end', function(err) {
|
||||
if (err) {
|
||||
contentStream.emit('error', err);
|
||||
}
|
||||
contentStream.emit('end');
|
||||
}).pipe(contentStream, {
|
||||
end: false
|
||||
});
|
||||
return contentStream;
|
||||
} else {
|
||||
contentStream = new PassThrough();
|
||||
contentStream.end(content || '');
|
||||
return contentStream;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses addresses. Takes in a single address or an array or an
|
||||
* array of address arrays (eg. To: [[first group], [second group],...])
|
||||
*
|
||||
* @param {Mixed} addresses Addresses to be parsed
|
||||
* @return {Array} An array of address objects
|
||||
*/
|
||||
MimeNode.prototype._parseAddresses = function(addresses) {
|
||||
return [].concat.apply([], [].concat(addresses).map(function(address) {
|
||||
if (address && address.address) {
|
||||
address = this._convertAddresses(address);
|
||||
}
|
||||
return addressparser(address);
|
||||
}.bind(this)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalizes a header key, uses Camel-Case form, except for uppercase MIME-
|
||||
*
|
||||
* @param {String} key Key to be normalized
|
||||
* @return {String} key in Camel-Case form
|
||||
*/
|
||||
MimeNode.prototype._normalizeHeaderKey = function(key) {
|
||||
return (key || '').toString().
|
||||
// no newlines in keys
|
||||
replace(/\r?\n|\r/g, ' ').
|
||||
trim().toLowerCase().
|
||||
// use uppercase words, except MIME
|
||||
replace(/^MIME\b|^[a-z]|\-[a-z]/ig, function(c) {
|
||||
return c.toUpperCase();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if the content type is multipart and defines boundary if needed.
|
||||
* Doesn't return anything, modifies object argument instead.
|
||||
*
|
||||
* @param {Object} structured Parsed header value for 'Content-Type' key
|
||||
*/
|
||||
MimeNode.prototype._handleContentType = function(structured) {
|
||||
this.contentType = structured.value.trim().toLowerCase();
|
||||
|
||||
this.multipart = this.contentType.split('/').reduce(function(prev, value) {
|
||||
return prev === 'multipart' ? value : false;
|
||||
});
|
||||
|
||||
if (this.multipart) {
|
||||
this.boundary = structured.params.boundary = structured.params.boundary || this.boundary || this._generateBoundary();
|
||||
} else {
|
||||
this.boundary = false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Generates a multipart boundary value
|
||||
*
|
||||
* @return {String} boundary value
|
||||
*/
|
||||
MimeNode.prototype._generateBoundary = function() {
|
||||
return '----sinikael-?=_' + this._nodeId + '-' + this.rootNode.baseBoundary;
|
||||
};
|
||||
|
||||
/**
|
||||
* Encodes a header value for use in the generated rfc2822 email.
|
||||
*
|
||||
* @param {String} key Header key
|
||||
* @param {String} value Header value
|
||||
*/
|
||||
MimeNode.prototype._encodeHeaderValue = function(key, value) {
|
||||
key = this._normalizeHeaderKey(key);
|
||||
|
||||
switch (key) {
|
||||
|
||||
// Structured headers
|
||||
case 'From':
|
||||
case 'Sender':
|
||||
case 'To':
|
||||
case 'Cc':
|
||||
case 'Bcc':
|
||||
case 'Reply-To':
|
||||
return this._convertAddresses(this._parseAddresses(value));
|
||||
|
||||
// values enclosed in <>
|
||||
case 'Message-Id':
|
||||
case 'In-Reply-To':
|
||||
case 'Content-Id':
|
||||
value = (value || '').toString().replace(/\r?\n|\r/g, ' ');
|
||||
|
||||
if (value.charAt(0) !== '<') {
|
||||
value = '<' + value;
|
||||
}
|
||||
|
||||
if (value.charAt(value.length - 1) !== '>') {
|
||||
value = value + '>';
|
||||
}
|
||||
return value;
|
||||
|
||||
// space separated list of values enclosed in <>
|
||||
case 'References':
|
||||
value = [].concat.apply([], [].concat(value || '').map(function(elm) {
|
||||
elm = (elm || '').toString().replace(/\r?\n|\r/g, ' ').trim();
|
||||
return elm.replace(/<[^>]*>/g, function(str) {
|
||||
return str.replace(/\s/g, '');
|
||||
}).split(/\s+/);
|
||||
})).map(function(elm) {
|
||||
if (elm.charAt(0) !== '<') {
|
||||
elm = '<' + elm;
|
||||
}
|
||||
if (elm.charAt(elm.length - 1) !== '>') {
|
||||
elm = elm + '>';
|
||||
}
|
||||
return elm;
|
||||
});
|
||||
|
||||
return value.join(' ').trim();
|
||||
|
||||
case 'Date':
|
||||
if (Object.prototype.toString.call(value) === '[object Date]') {
|
||||
return value.toUTCString().replace(/GMT/, '+0000');
|
||||
}
|
||||
|
||||
value = (value || '').toString().replace(/\r?\n|\r/g, ' ');
|
||||
return libmime.encodeWords(value, 'Q', 52);
|
||||
|
||||
default:
|
||||
value = (value || '').toString().replace(/\r?\n|\r/g, ' ');
|
||||
// encodeWords only encodes if needed, otherwise the original string is returned
|
||||
return libmime.encodeWords(value, 'Q', 52);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Rebuilds address object using punycode and other adjustments
|
||||
*
|
||||
* @param {Array} addresses An array of address objects
|
||||
* @param {Array} [uniqueList] An array to be populated with addresses
|
||||
* @return {String} address string
|
||||
*/
|
||||
MimeNode.prototype._convertAddresses = function(addresses, uniqueList) {
|
||||
var values = [];
|
||||
|
||||
uniqueList = uniqueList || [];
|
||||
|
||||
[].concat(addresses || []).forEach(function(address) {
|
||||
if (address.address) {
|
||||
address.address = address.address.replace(/^.*?(?=\@)/, function(user) {
|
||||
// pretty bad solution but what you gonna do
|
||||
// unicode usernames are converted to encoded words
|
||||
// 'jõgeva@hot.ee' will be converted to '=?utf-8?Q?j=C3=B5geva?=@hot.ee'
|
||||
return libmime.encodeWords(user, 'Q', 52);
|
||||
}).replace(/@.+$/, function(domain) {
|
||||
// domains are punycoded by default
|
||||
// 'jõgeva.ee' will be converted to 'xn--jgeva-dua.ee'
|
||||
// non-unicode domains are left as is
|
||||
return '@' + punycode.toASCII(domain.substr(1));
|
||||
});
|
||||
|
||||
if (!address.name) {
|
||||
values.push(address.address);
|
||||
} else if (address.name) {
|
||||
values.push(this._encodeAddressName(address.name) + ' <' + address.address + '>');
|
||||
}
|
||||
|
||||
if (address.address) {
|
||||
if (!uniqueList.filter(function(a) {
|
||||
return a.address === address.address;
|
||||
}).length) {
|
||||
uniqueList.push(address);
|
||||
}
|
||||
}
|
||||
} else if (address.group) {
|
||||
values.push(this._encodeAddressName(address.name) + ':' + (address.group.length ? this._convertAddresses(address.group, uniqueList) : '').trim() + ';');
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
return values.join(', ');
|
||||
};
|
||||
|
||||
/**
|
||||
* If needed, mime encodes the name part
|
||||
*
|
||||
* @param {String} name Name part of an address
|
||||
* @returns {String} Mime word encoded string if needed
|
||||
*/
|
||||
MimeNode.prototype._encodeAddressName = function(name) {
|
||||
if (!/^[\w ']*$/.test(name)) {
|
||||
if (/^[\x20-\x7e]*$/.test(name)) {
|
||||
return '"' + name.replace(/([\\"])/g, '\\$1') + '"';
|
||||
} else {
|
||||
return libmime.encodeWord(name, 'Q', 52);
|
||||
}
|
||||
}
|
||||
return name;
|
||||
};
|
||||
1009
node_modules/buildmail/test/libbuildmail-unit.js
generated
vendored
Normal file
1009
node_modules/buildmail/test/libbuildmail-unit.js
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user