First Commit

This commit is contained in:
2025-11-02 22:52:08 +01:00
commit 73fbbf1be2
5821 changed files with 977526 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
'use strict';
module.exports = {
rules: {
indent: [2, 4, {
SwitchCase: 1
}],
quotes: [2, 'single'],
'linebreak-style': [2, 'unix'],
semi: [2, 'always'],
strict: [2, 'global'],
eqeqeq: 2,
'dot-notation': 2,
curly: 2,
'no-fallthrough': 2,
'quote-props': [2, 'as-needed'],
'no-unused-expressions': [2, {
allowShortCircuit: true
}],
'no-unused-vars': 2,
'no-undef': 2,
'handle-callback-err': 2,
'no-new': 2,
'new-cap': 2,
'no-eval': 2,
'no-invalid-this': 2,
radix: [2, 'always'],
'no-use-before-define': [2, 'nofunc'],
'callback-return': [2, ['callback', 'cb', 'done']],
'comma-dangle': [2, 'never'],
'comma-style': [2, 'last'],
'no-regex-spaces': 2,
'no-empty': 2,
'no-duplicate-case': 2,
'no-empty-character-class': 2,
'no-redeclare': [2, {
builtinGlobals: true
}],
'block-scoped-var': 2,
'no-sequences': 2,
'no-throw-literal': 2,
'no-useless-concat': 2,
'no-void': 2,
yoda: 2,
'no-bitwise': 2,
'no-lonely-if': 2,
'no-mixed-spaces-and-tabs': 2,
'no-console': 0
},
env: {
es6: false,
node: true
},
extends: 'eslint:recommended',
fix: true
};

View File

@@ -0,0 +1,3 @@
node_modules
npm-debug.log
.DS_Store

View File

@@ -0,0 +1,19 @@
language: node_js
sudo: false
node_js:
- "0.10"
- 0.12
- iojs
- '4'
- '5'
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

View File

@@ -0,0 +1,30 @@
# Changelog
## v1.6.0 2016-08-18
* Added new option `headers`
## v1.5.0 2016-08-18
* Allow streams as POST body
## v1.3.0 2016-02-11
* Added new option `timeout`
## v1.2.1 2016-01-18
* Enclose http.request into try..catch to get url parse errors
## v1.2.0 2016-01-18
* Export `Cookies` constructor
## v1.1.0 2016-01-18
* Exposed `options` object
* Added new options `maxRedirects`, `userAgent` and `cookie`
## v1.0.0 2015-12-30
* Initial version

View File

@@ -0,0 +1,27 @@
'use strict';
module.exports = function (grunt) {
// Project configuration.
grunt.initConfig({
eslint: {
all: ['lib/*.js', 'test/*.js', 'Gruntfile.js', '.eslintrc.js']
},
mochaTest: {
all: {
options: {
reporter: 'spec'
},
src: ['test/*-test.js']
}
}
});
// Load the plugin(s)
grunt.loadNpmTasks('grunt-eslint');
grunt.loadNpmTasks('grunt-mocha-test');
// Tasks
grunt.registerTask('default', ['eslint', 'mochaTest']);
};

View File

@@ -0,0 +1,16 @@
Copyright (c) 2015-2016 Andris Reinman
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,55 @@
# nodemailer-fetch
Fetches HTTP URL contents for [nodemailer](https://github.com/nodemailer/nodemailer).
[![Build Status](https://secure.travis-ci.org/nodemailer/nodemailer-fetch.svg)](http://travis-ci.org/nodemailer/nodemailer-fetch)
<a href="http://badge.fury.io/js/nodemailer-fetch"><img src="https://badge.fury.io/js/nodemailer-fetch.svg" alt="NPM version" height="18"></a>
## Usage
```javascript
var fetch = require('nodemailer-fetch');
fetch('http://www.google.com/').pipe(process.stdout);
```
The method takes the destination URL as the first and optional options object as the second argument.
The defaults are the following:
* Default method is GET
* Basic auth is supported
* Up to 5 redirects are followed (Basic auth gets lost after first redirect)
* gzip is handled if present
* Cookies are supported
* No shared HTTP Agent
* Invalid SSL certs are allowed. Can be overwritten with the `tls` option
### options
Possible options are the following:
* **userAgent** a string defining the User Agent of the request (by default not set)
* **cookie** a cookie string or an array of cookie strings where a cookie is the value used by 'Set-Cookie' header
* **maxRedirects** how many redirects to allow (defaults to 5, set to 0 to disable redirects entirely)
* **method** HTTP method to use, defaults to GET (if `body` is set defaults to POST)
* **body** HTTP payload to send. If the value is an object it is converted to an *x-www-form-urlencoded* payload, other values are passed as is. Unlike authentication data payload and method is preserved between redirects
* **contentType** optional content type for the HTTP payload. Defaults to *x-www-form-urlencoded*. If the value is `false` then Content-Type header is not set
* **tls** optional object of TLS options
* **timeout** (milliseconds) sets timeout for the connection. Returns an error if timeout occurs
* **headers** custom headers as an object where key is the header key and value is either a string or an array of strings for multiple values
```javascript
var fetch = require('nodemailer-fetch');
fetch('http://www.google.com/', {
cookie: [
'cookie_name1=cookie_value1',
'cookie_name2=cookie_value2; expires=Sun, 16 Jul 3567 06:23:41 GMT',
],
userAgent: 'MyFetcher/1.0'
}).pipe(process.stdout);
```
> Cookies are domain specific like normal browser cookies, so if a redirect happens to another domain, then cookies are not passed to it, HTTPS-only cookies are not passed to HTTP etc.
## License
**MIT**

View File

@@ -0,0 +1,275 @@
'use strict';
// module to handle cookies
var urllib = require('url');
var SESSION_TIMEOUT = 1800; // 30 min
module.exports = Cookies;
/**
* Creates a biskviit cookie jar for managing cookie values in memory
*
* @constructor
* @param {Object} [options] Optional options object
*/
function Cookies(options) {
this.options = options || {};
this.cookies = [];
}
/**
* Stores a cookie string to the cookie storage
*
* @param {String} cookieStr Value from the 'Set-Cookie:' header
* @param {String} url Current URL
*/
Cookies.prototype.set = function (cookieStr, url) {
var urlparts = urllib.parse(url || '');
var cookie = this.parse(cookieStr);
var domain;
if (cookie.domain) {
domain = cookie.domain.replace(/^\./, '');
// do not allow cross origin cookies
if (
// can't be valid if the requested domain is shorter than current hostname
urlparts.hostname.length < domain.length ||
// prefix domains with dot to be sure that partial matches are not used
('.' + urlparts.hostname).substr(-domain.length + 1) !== ('.' + domain)) {
cookie.domain = urlparts.hostname;
}
} else {
cookie.domain = urlparts.hostname;
}
if (!cookie.path) {
cookie.path = this.getPath(urlparts.pathname);
}
// if no expire date, then use sessionTimeout value
if (!cookie.expires) {
cookie.expires = new Date(Date.now() + (Number(this.options.sessionTimeout || SESSION_TIMEOUT) || SESSION_TIMEOUT) * 1000);
}
return this.add(cookie);
};
/**
* Returns cookie string for the 'Cookie:' header.
*
* @param {String} url URL to check for
* @returns {String} Cookie header or empty string if no matches were found
*/
Cookies.prototype.get = function (url) {
return this.list(url).map(function (cookie) {
return cookie.name + '=' + cookie.value;
}).join('; ');
};
/**
* Lists all valied cookie objects for the specified URL
*
* @param {String} url URL to check for
* @returns {Array} An array of cookie objects
*/
Cookies.prototype.list = function (url) {
var result = [];
var i;
var cookie;
for (i = this.cookies.length - 1; i >= 0; i--) {
cookie = this.cookies[i];
if (this.isExpired(cookie)) {
this.cookies.splice(i, i);
continue;
}
if (this.match(cookie, url)) {
result.unshift(cookie);
}
}
return result;
};
/**
* Parses cookie string from the 'Set-Cookie:' header
*
* @param {String} cookieStr String from the 'Set-Cookie:' header
* @returns {Object} Cookie object
*/
Cookies.prototype.parse = function (cookieStr) {
var cookie = {};
(cookieStr || '').toString().split(';').forEach(function (cookiePart) {
var valueParts = cookiePart.split('=');
var key = valueParts.shift().trim().toLowerCase();
var value = valueParts.join('=').trim();
var domain;
if (!key) {
// skip empty parts
return;
}
switch (key) {
case 'expires':
value = new Date(value);
// ignore date if can not parse it
if (value.toString() !== 'Invalid Date') {
cookie.expires = value;
}
break;
case 'path':
cookie.path = value;
break;
case 'domain':
domain = value.toLowerCase();
if (domain.length && domain.charAt(0) !== '.') {
domain = '.' + domain; // ensure preceeding dot for user set domains
}
cookie.domain = domain;
break;
case 'max-age':
cookie.expires = new Date(Date.now() + (Number(value) || 0) * 1000);
break;
case 'secure':
cookie.secure = true;
break;
case 'httponly':
cookie.httponly = true;
break;
default:
if (!cookie.name) {
cookie.name = key;
cookie.value = value;
}
}
});
return cookie;
};
/**
* Checks if a cookie object is valid for a specified URL
*
* @param {Object} cookie Cookie object
* @param {String} url URL to check for
* @returns {Boolean} true if cookie is valid for specifiec URL
*/
Cookies.prototype.match = function (cookie, url) {
var urlparts = urllib.parse(url || '');
// check if hostname matches
// .foo.com also matches subdomains, foo.com does not
if (urlparts.hostname !== cookie.domain && (cookie.domain.charAt(0) !== '.' || ('.' + urlparts.hostname).substr(-cookie.domain.length) !== cookie.domain)) {
return false;
}
// check if path matches
var path = this.getPath(urlparts.pathname);
if (path.substr(0, cookie.path.length) !== cookie.path) {
return false;
}
// check secure argument
if (cookie.secure && urlparts.protocol !== 'https:') {
return false;
}
return true;
};
/**
* Adds (or updates/removes if needed) a cookie object to the cookie storage
*
* @param {Object} cookie Cookie value to be stored
*/
Cookies.prototype.add = function (cookie) {
var i;
var len;
// nothing to do here
if (!cookie || !cookie.name) {
return false;
}
// overwrite if has same params
for (i = 0, len = this.cookies.length; i < len; i++) {
if (this.compare(this.cookies[i], cookie)) {
// check if the cookie needs to be removed instead
if (this.isExpired(cookie)) {
this.cookies.splice(i, 1); // remove expired/unset cookie
return false;
}
this.cookies[i] = cookie;
return true;
}
}
// add as new if not already expired
if (!this.isExpired(cookie)) {
this.cookies.push(cookie);
}
return true;
};
/**
* Checks if two cookie objects are the same
*
* @param {Object} a Cookie to check against
* @param {Object} b Cookie to check against
* @returns {Boolean} True, if the cookies are the same
*/
Cookies.prototype.compare = function (a, b) {
return a.name === b.name && a.path === b.path && a.domain === b.domain && a.secure === b.secure && a.httponly === a.httponly;
};
/**
* Checks if a cookie is expired
*
* @param {Object} cookie Cookie object to check against
* @returns {Boolean} True, if the cookie is expired
*/
Cookies.prototype.isExpired = function (cookie) {
return (cookie.expires && cookie.expires < new Date()) || !cookie.value;
};
/**
* Returns normalized cookie path for an URL path argument
*
* @param {String} pathname
* @returns {String} Normalized path
*/
Cookies.prototype.getPath = function (pathname) {
var path = (pathname || '/').split('/');
path.pop(); // remove filename part
path = path.join('/').trim();
// ensure path prefix /
if (path.charAt(0) !== '/') {
path = '/' + path;
}
// ensure path suffix /
if (path.substr(-1) !== '/') {
path += '/';
}
return path;
};

View File

@@ -0,0 +1,224 @@
'use strict';
var http = require('http');
var https = require('https');
var urllib = require('url');
var zlib = require('zlib');
var PassThrough = require('stream').PassThrough;
var Cookies = require('./cookies');
var MAX_REDIRECTS = 5;
module.exports = function (url, options) {
return fetch(url, options);
};
module.exports.Cookies = Cookies;
function fetch(url, options) {
options = options || {};
options.fetchRes = options.fetchRes || new PassThrough();
options.cookies = options.cookies || new Cookies();
options.redirects = options.redirects || 0;
options.maxRedirects = isNaN(options.maxRedirects) ? MAX_REDIRECTS : options.maxRedirects;
if (options.cookie) {
[].concat(options.cookie || []).forEach(function (cookie) {
options.cookies.set(cookie, url);
});
options.cookie = false;
}
var fetchRes = options.fetchRes;
var parsed = urllib.parse(url);
var method = (options.method || '').toString().trim().toUpperCase() || 'GET';
var finished = false;
var cookies;
var body;
var handler = parsed.protocol === 'https:' ? https : http;
var headers = {
'accept-encoding': 'gzip,deflate'
};
Object.keys(options.headers || {}).forEach(function (key) {
headers[key.toLowerCase().trim()] = options.headers[key];
});
if (options.userAgent) {
headers['User-Agent'] = options.userAgent;
}
if (parsed.auth) {
headers.Authorization = 'Basic ' + new Buffer(parsed.auth).toString('base64');
}
if ((cookies = options.cookies.get(url))) {
headers.cookie = cookies;
}
if (options.body) {
if (options.contentType !== false) {
headers['Content-Type'] = options.contentType || 'application/x-www-form-urlencoded';
}
if (typeof options.body.pipe === 'function') {
// it's a stream
headers['Transfer-Encoding'] = 'chunked';
body = options.body;
body.on('error', function (err) {
if (finished) {
return;
}
finished = true;
fetchRes.emit('error', err);
});
} else {
if (options.body instanceof Buffer) {
body = options.body;
} else if (typeof options.body === 'object') {
body = new Buffer(Object.keys(options.body).map(function (key) {
var value = options.body[key].toString().trim();
return encodeURIComponent(key) + '=' + encodeURIComponent(value);
}).join('&'));
} else {
body = new Buffer(options.body.toString().trim());
}
headers['Content-Type'] = options.contentType || 'application/x-www-form-urlencoded';
headers['Content-Length'] = body.length;
}
// if method is not provided, use POST instead of GET
method = (options.method || '').toString().trim().toUpperCase() || 'POST';
}
var req;
var reqOptions = {
method: method,
host: parsed.hostname,
path: parsed.path,
port: parsed.port ? parsed.port : (parsed.protocol === 'https:' ? 443 : 80),
headers: headers,
rejectUnauthorized: false,
agent: false
};
if (options.tls) {
Object.keys(options.tls).forEach(function (key) {
reqOptions[key] = options.tls[key];
});
}
try {
req = handler.request(reqOptions);
} catch (E) {
finished = true;
setImmediate(function () {
fetchRes.emit('error', E);
});
return fetchRes;
}
if (options.timeout) {
req.setTimeout(options.timeout, function () {
if (finished) {
return;
}
finished = true;
req.abort();
fetchRes.emit('error', new Error('Request Tiemout'));
});
}
req.on('error', function (err) {
if (finished) {
return;
}
finished = true;
fetchRes.emit('error', err);
});
req.on('response', function (res) {
var inflate;
if (finished) {
return;
}
switch (res.headers['content-encoding']) {
case 'gzip':
case 'deflate':
inflate = zlib.createUnzip();
break;
}
if (res.headers['set-cookie']) {
[].concat(res.headers['set-cookie'] || []).forEach(function (cookie) {
options.cookies.set(cookie, url);
});
}
if ([301, 302, 303, 307, 308].indexOf(res.statusCode) >= 0 && res.headers.location) {
// redirect
options.redirects++;
if (options.redirects > options.maxRedirects) {
finished = true;
fetchRes.emit('error', new Error('Maximum redirect count exceeded'));
req.abort();
return;
}
return fetch(urllib.resolve(url, res.headers.location), options);
}
if (res.statusCode >= 300) {
finished = true;
fetchRes.emit('error', new Error('Invalid status code ' + res.statusCode));
req.abort();
return;
}
res.on('error', function (err) {
if (finished) {
return;
}
finished = true;
fetchRes.emit('error', err);
req.abort();
});
if (inflate) {
res.pipe(inflate).pipe(fetchRes);
inflate.on('error', function (err) {
if (finished) {
return;
}
finished = true;
fetchRes.emit('error', err);
req.abort();
});
} else {
res.pipe(fetchRes);
}
});
setImmediate(function () {
if (body) {
try {
if (typeof body.pipe === 'function') {
return body.pipe(req);
} else {
req.write(body);
}
} catch (err) {
finished = true;
fetchRes.emit('error', err);
return;
}
}
req.end();
});
return fetchRes;
}

View File

@@ -0,0 +1,58 @@
{
"_from": "nodemailer-fetch@1.6.0",
"_id": "nodemailer-fetch@1.6.0",
"_inBundle": false,
"_integrity": "sha1-ecSQihwPXzdbc/6IjamCj23JY6Q=",
"_location": "/nodemailer-fetch",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "nodemailer-fetch@1.6.0",
"name": "nodemailer-fetch",
"escapedName": "nodemailer-fetch",
"rawSpec": "1.6.0",
"saveSpec": null,
"fetchSpec": "1.6.0"
},
"_requiredBy": [
"/buildmail",
"/nodemailer-shared"
],
"_resolved": "https://registry.npmjs.org/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz",
"_shasum": "79c4908a1c0f5f375b73fe888da9828f6dc963a4",
"_spec": "nodemailer-fetch@1.6.0",
"_where": "/Users/rxf/Projekte/SternDBase/sternwarte/checkfuehrung/node_modules/buildmail",
"author": {
"name": "Andris Reinman"
},
"bugs": {
"url": "https://github.com/nodemailer/nodemailer-fetch/issues"
},
"bundleDependencies": false,
"deprecated": false,
"description": "GET HTTP contents",
"devDependencies": {
"chai": "^3.5.0",
"grunt": "^1.0.1",
"grunt-eslint": "^19.0.0",
"grunt-mocha-test": "^0.12.7",
"mocha": "^3.0.2"
},
"homepage": "https://github.com/nodemailer/nodemailer-fetch#readme",
"keywords": [
"nodemailer",
"http"
],
"license": "MIT",
"main": "lib/fetch.js",
"name": "nodemailer-fetch",
"repository": {
"type": "git",
"url": "git+https://github.com/nodemailer/nodemailer-fetch.git"
},
"scripts": {
"test": "grunt mochaTest"
},
"version": "1.6.0"
}

View File

@@ -0,0 +1,391 @@
/* eslint no-unused-expressions:0 */
/* globals beforeEach, describe, it */
'use strict';
var chai = require('chai');
var expect = chai.expect;
//var http = require('http');
var Cookies = require('../lib/cookies');
chai.config.includeStack = true;
describe('Cookies Unit Tests', function () {
var biskviit;
beforeEach(function () {
biskviit = new Cookies();
});
describe('#getPath', function () {
it('should return root path', function () {
expect(biskviit.getPath('/')).to.equal('/');
expect(biskviit.getPath('')).to.equal('/');
expect(biskviit.getPath('/index.php')).to.equal('/');
});
it('should return without file', function () {
expect(biskviit.getPath('/path/to/file')).to.equal('/path/to/');
});
});
describe('#isExpired', function () {
it('should match expired cookie', function () {
expect(biskviit.isExpired({
name: 'a',
value: 'b',
expires: new Date(Date.now() + 10000)
})).to.be.false;
expect(biskviit.isExpired({
name: 'a',
value: '',
expires: new Date(Date.now() + 10000)
})).to.be.true;
expect(biskviit.isExpired({
name: 'a',
value: 'b',
expires: new Date(Date.now() - 10000)
})).to.be.true;
});
});
describe('#compare', function () {
it('should match similar cookies', function () {
expect(biskviit.compare({
name: 'zzz',
path: '/',
domain: 'example.com',
secure: false,
httponly: false
}, {
name: 'zzz',
path: '/',
domain: 'example.com',
secure: false,
httponly: false
})).to.be.true;
expect(biskviit.compare({
name: 'zzz',
path: '/',
domain: 'example.com',
secure: false,
httponly: false
}, {
name: 'yyy',
path: '/',
domain: 'example.com',
secure: false,
httponly: false
})).to.be.false;
expect(biskviit.compare({
name: 'zzz',
path: '/',
domain: 'example.com',
secure: false,
httponly: false
}, {
name: 'zzz',
path: '/amp',
domain: 'example.com',
secure: false,
httponly: false
})).to.be.false;
expect(biskviit.compare({
name: 'zzz',
path: '/',
domain: 'example.com',
secure: false,
httponly: false
}, {
name: 'zzz',
path: '/',
domain: 'examples.com',
secure: false,
httponly: false
})).to.be.false;
expect(biskviit.compare({
name: 'zzz',
path: '/',
domain: 'example.com',
secure: false,
httponly: false
}, {
name: 'zzz',
path: '/',
domain: 'example.com',
secure: true,
httponly: false
})).to.be.false;
});
});
describe('#add', function () {
it('should append new cookie', function () {
expect(biskviit.cookies.length).to.equal(0);
biskviit.add({
name: 'zzz',
value: 'abc',
path: '/',
expires: new Date(Date.now() + 10000),
domain: 'example.com',
secure: false,
httponly: false
});
expect(biskviit.cookies.length).to.equal(1);
expect(biskviit.cookies[0].name).to.equal('zzz');
expect(biskviit.cookies[0].value).to.equal('abc');
});
it('should update existing cookie', function () {
expect(biskviit.cookies.length).to.equal(0);
biskviit.add({
name: 'zzz',
value: 'abc',
path: '/',
expires: new Date(Date.now() + 10000),
domain: 'example.com',
secure: false,
httponly: false
});
biskviit.add({
name: 'zzz',
value: 'def',
path: '/',
expires: new Date(Date.now() + 10000),
domain: 'example.com',
secure: false,
httponly: false
});
expect(biskviit.cookies.length).to.equal(1);
expect(biskviit.cookies[0].name).to.equal('zzz');
expect(biskviit.cookies[0].value).to.equal('def');
});
});
describe('#match', function () {
it('should check if a cookie matches particular domain and path', function () {
var cookie = {
name: 'zzz',
value: 'abc',
path: '/def/',
expires: new Date(Date.now() + 10000),
domain: 'example.com',
secure: false,
httponly: false
};
expect(biskviit.match(cookie, 'http://example.com/def/')).to.be.true;
expect(biskviit.match(cookie, 'http://example.com/bef/')).to.be.false;
});
it('should check if a cookie matches particular domain and path', function () {
var cookie = {
name: 'zzz',
value: 'abc',
path: '/def',
expires: new Date(Date.now() + 10000),
domain: 'example.com',
secure: false,
httponly: false
};
expect(biskviit.match(cookie, 'http://example.com/def/')).to.be.true;
expect(biskviit.match(cookie, 'http://example.com/bef/')).to.be.false;
});
it('should check if a cookie is secure', function () {
var cookie = {
name: 'zzz',
value: 'abc',
path: '/def/',
expires: new Date(Date.now() + 10000),
domain: 'example.com',
secure: true,
httponly: false
};
expect(biskviit.match(cookie, 'https://example.com/def/')).to.be.true;
expect(biskviit.match(cookie, 'http://example.com/def/')).to.be.false;
});
});
describe('#parse', function () {
it('should parse Set-Cookie value', function () {
expect(biskviit.parse('theme=plain')).to.deep.equal({
name: 'theme',
value: 'plain'
});
expect(biskviit.parse('SSID=Ap4P….GTEq; Domain=foo.com; Path=/; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly')).to.deep.equal({
name: 'ssid',
value: 'Ap4P….GTEq',
domain: '.foo.com',
path: '/',
httponly: true,
secure: true,
expires: new Date('Wed, 13 Jan 2021 22:23:01 GMT')
});
});
it('should ignore invalid expire header', function () {
expect(biskviit.parse('theme=plain; Expires=Wed, 13 Jan 2021 22:23:01 GMT')).to.deep.equal({
name: 'theme',
value: 'plain',
expires: new Date('Wed, 13 Jan 2021 22:23:01 GMT')
});
expect(biskviit.parse('theme=plain; Expires=ZZZZZZZZ GMT')).to.deep.equal({
name: 'theme',
value: 'plain'
});
});
});
describe('Listing', function () {
beforeEach(function () {
biskviit.cookies = [{
name: 'ssid1',
value: 'Ap4P….GTEq1',
domain: '.foo.com',
path: '/',
httponly: true,
secure: true,
expires: new Date('Wed, 13 Jan 2021 22:23:01 GMT')
}, {
name: 'ssid2',
value: 'Ap4P….GTEq2',
domain: '.foo.com',
path: '/',
httponly: true,
secure: true,
expires: new Date('Wed, 13 Jan 1900 22:23:01 GMT')
}, {
name: 'ssid3',
value: 'Ap4P….GTEq3',
domain: 'foo.com',
path: '/',
httponly: true,
secure: true,
expires: new Date('Wed, 13 Jan 2021 22:23:01 GMT')
}, {
name: 'ssid4',
value: 'Ap4P….GTEq4',
domain: 'www.foo.com',
path: '/',
httponly: true,
secure: true,
expires: new Date('Wed, 13 Jan 2021 22:23:01 GMT')
}, {
name: 'ssid5',
value: 'Ap4P….GTEq5',
domain: 'broo.com',
path: '/',
httponly: true,
secure: true,
expires: new Date('Wed, 13 Jan 2021 22:23:01 GMT')
}];
});
describe('#list', function () {
it('should return matching cookies for an URL', function () {
expect(biskviit.list('https://www.foo.com')).to.deep.equal([{
name: 'ssid1',
value: 'Ap4P….GTEq1',
domain: '.foo.com',
path: '/',
httponly: true,
secure: true,
expires: new Date('Wed, 13 Jan 2021 22:23:01 GMT')
}, {
name: 'ssid4',
value: 'Ap4P….GTEq4',
domain: 'www.foo.com',
path: '/',
httponly: true,
secure: true,
expires: new Date('Wed, 13 Jan 2021 22:23:01 GMT')
}]);
});
});
describe('#get', function () {
it('should return matching cookies for an URL', function () {
expect(biskviit.get('https://www.foo.com')).to.equal('ssid1=Ap4P….GTEq1; ssid4=Ap4P….GTEq4');
});
});
});
describe('#set', function () {
it('should set cookie', function () {
// short
biskviit.set('theme=plain', 'https://foo.com/');
// long
biskviit.set('SSID=Ap4P….GTEq; Domain=foo.com; Path=/test; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly', 'https://foo.com/');
// subdomains
biskviit.set('SSID=Ap4P….GTEq; Domain=.foo.com; Path=/; Expires=Wed, 13 Jan 2021 22:23:01 GMT; Secure; HttpOnly', 'https://www.foo.com/');
// invalid cors
biskviit.set('invalid_1=cors; domain=example.com', 'https://foo.com/');
biskviit.set('invalid_2=cors; domain=www.foo.com', 'https://foo.com/');
// invalid date
biskviit.set('invalid_3=date; Expires=zzzz', 'https://foo.com/');
// invalid tld
biskviit.set('invalid_4=cors; domain=.co.uk', 'https://foo.co.uk/');
// should not be added
biskviit.set('expired_1=date; Expires=1999-01-01 01:01:01 GMT', 'https://foo.com/');
expect(biskviit.cookies.map(function (cookie) {
delete cookie.expires;
return cookie;
})).to.deep.equal([{
name: 'theme',
value: 'plain',
domain: 'foo.com',
path: '/'
}, {
name: 'ssid',
value: 'Ap4P….GTEq',
domain: 'foo.com',
path: '/test',
secure: true,
httponly: true
}, {
name: 'ssid',
value: 'Ap4P….GTEq',
domain: 'www.foo.com',
path: '/',
secure: true,
httponly: true
}, {
name: 'invalid_1',
value: 'cors',
domain: 'foo.com',
path: '/'
}, {
name: 'invalid_2',
value: 'cors',
domain: 'foo.com',
path: '/'
}, {
name: 'invalid_3',
value: 'date',
domain: 'foo.com',
path: '/'
}, {
name: 'invalid_4',
value: 'cors',
domain: 'foo.co.uk',
path: '/'
}]);
});
});
});

View File

@@ -0,0 +1,486 @@
/* eslint no-unused-expressions:0 */
/* globals afterEach, beforeEach, describe, it */
'use strict';
var chai = require('chai');
var expect = chai.expect;
//var http = require('http');
var fetch = require('../lib/fetch');
var http = require('http');
var https = require('https');
var zlib = require('zlib');
var PassThrough = require('stream').PassThrough;
chai.config.includeStack = true;
var HTTP_PORT = 9998;
var HTTPS_PORT = 9993;
var httpsOptions = {
key: '-----BEGIN RSA PRIVATE KEY-----\n' +
'MIIEpAIBAAKCAQEA6Z5Qqhw+oWfhtEiMHE32Ht94mwTBpAfjt3vPpX8M7DMCTwHs\n' +
'1xcXvQ4lQ3rwreDTOWdoJeEEy7gMxXqH0jw0WfBx+8IIJU69xstOyT7FRFDvA1yT\n' +
'RXY2yt9K5s6SKken/ebMfmZR+03ND4UFsDzkz0FfgcjrkXmrMF5Eh5UXX/+9YHeU\n' +
'xlp0gMAt+/SumSmgCaysxZLjLpd4uXz+X+JVxsk1ACg1NoEO7lWJC/3WBP7MIcu2\n' +
'wVsMd2XegLT0gWYfT1/jsIH64U/mS/SVXC9QhxMl9Yfko2kx1OiYhDxhHs75RJZh\n' +
'rNRxgfiwgSb50Gw4NAQaDIxr/DJPdLhgnpY6UQIDAQABAoIBAE+tfzWFjJbgJ0ql\n' +
's6Ozs020Sh4U8TZQuonJ4HhBbNbiTtdDgNObPK1uNadeNtgW5fOeIRdKN6iDjVeN\n' +
'AuXhQrmqGDYVZ1HSGUfD74sTrZQvRlWPLWtzdhybK6Css41YAyPFo9k4bJ2ZW2b/\n' +
'p4EEQ8WsNja9oBpttMU6YYUchGxo1gujN8hmfDdXUQx3k5Xwx4KA68dveJ8GasIt\n' +
'd+0Jd/FVwCyyx8HTiF1FF8QZYQeAXxbXJgLBuCsMQJghlcpBEzWkscBR3Ap1U0Zi\n' +
'4oat8wrPZGCblaA6rNkRUVbc/+Vw0stnuJ/BLHbPxyBs6w495yBSjBqUWZMvljNz\n' +
'm9/aK0ECgYEA9oVIVAd0enjSVIyAZNbw11ElidzdtBkeIJdsxqhmXzeIFZbB39Gd\n' +
'bjtAVclVbq5mLsI1j22ER2rHA4Ygkn6vlLghK3ZMPxZa57oJtmL3oP0RvOjE4zRV\n' +
'dzKexNGo9gU/x9SQbuyOmuauvAYhXZxeLpv+lEfsZTqqrvPUGeBiEQcCgYEA8poG\n' +
'WVnykWuTmCe0bMmvYDsWpAEiZnFLDaKcSbz3O7RMGbPy1cypmqSinIYUpURBT/WY\n' +
'wVPAGtjkuTXtd1Cy58m7PqziB7NNWMcsMGj+lWrTPZ6hCHIBcAImKEPpd+Y9vGJX\n' +
'oatFJguqAGOz7rigBq6iPfeQOCWpmprNAuah++cCgYB1gcybOT59TnA7mwlsh8Qf\n' +
'bm+tSllnin2A3Y0dGJJLmsXEPKtHS7x2Gcot2h1d98V/TlWHe5WNEUmx1VJbYgXB\n' +
'pw8wj2ACxl4ojNYqWPxegaLd4DpRbtW6Tqe9e47FTnU7hIggR6QmFAWAXI+09l8y\n' +
'amssNShqjE9lu5YDi6BTKwKBgQCuIlKGViLfsKjrYSyHnajNWPxiUhIgGBf4PI0T\n' +
'/Jg1ea/aDykxv0rKHnw9/5vYGIsM2st/kR7l5mMecg/2Qa145HsLfMptHo1ZOPWF\n' +
'9gcuttPTegY6aqKPhGthIYX2MwSDMM+X0ri6m0q2JtqjclAjG7yG4CjbtGTt/UlE\n' +
'WMlSZwKBgQDslGeLUnkW0bsV5EG3AKRUyPKz/6DVNuxaIRRhOeWVKV101claqXAT\n' +
'wXOpdKrvkjZbT4AzcNrlGtRl3l7dEVXTu+dN7/ZieJRu7zaStlAQZkIyP9O3DdQ3\n' +
'rIcetQpfrJ1cAqz6Ng0pD0mh77vQ13WG1BBmDFa2A9BuzLoBituf4g==\n' +
'-----END RSA PRIVATE KEY-----',
cert: '-----BEGIN CERTIFICATE-----\n' +
'MIICpDCCAYwCCQCuVLVKVTXnAjANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDEwls\n' +
'b2NhbGhvc3QwHhcNMTUwMjEyMTEzMjU4WhcNMjUwMjA5MTEzMjU4WjAUMRIwEAYD\n' +
'VQQDEwlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDp\n' +
'nlCqHD6hZ+G0SIwcTfYe33ibBMGkB+O3e8+lfwzsMwJPAezXFxe9DiVDevCt4NM5\n' +
'Z2gl4QTLuAzFeofSPDRZ8HH7wgglTr3Gy07JPsVEUO8DXJNFdjbK30rmzpIqR6f9\n' +
'5sx+ZlH7Tc0PhQWwPOTPQV+ByOuReaswXkSHlRdf/71gd5TGWnSAwC379K6ZKaAJ\n' +
'rKzFkuMul3i5fP5f4lXGyTUAKDU2gQ7uVYkL/dYE/swhy7bBWwx3Zd6AtPSBZh9P\n' +
'X+OwgfrhT+ZL9JVcL1CHEyX1h+SjaTHU6JiEPGEezvlElmGs1HGB+LCBJvnQbDg0\n' +
'BBoMjGv8Mk90uGCeljpRAgMBAAEwDQYJKoZIhvcNAQELBQADggEBABXm8GPdY0sc\n' +
'mMUFlgDqFzcevjdGDce0QfboR+M7WDdm512Jz2SbRTgZD/4na42ThODOZz9z1AcM\n' +
'zLgx2ZNZzVhBz0odCU4JVhOCEks/OzSyKeGwjIb4JAY7dh+Kju1+6MNfQJ4r1Hza\n' +
'SVXH0+JlpJDaJ73NQ2JyfqELmJ1mTcptkA/N6rQWhlzycTBSlfogwf9xawgVPATP\n' +
'4AuwgjHl12JI2HVVs1gu65Y3slvaHRCr0B4+Kg1GYNLLcbFcK+NEHrHmPxy9TnTh\n' +
'Zwp1dsNQU+Xkylz8IUANWSLHYZOMtN2e5SKIdwTtl5C8YxveuY8YKb1gDExnMraT\n' +
'VGXQDqPleug=\n' +
'-----END CERTIFICATE-----'
};
describe('fetch tests', function () {
var httpServer, httpsServer;
beforeEach(function (done) {
httpServer = http.createServer(function (req, res) {
switch (req.url) {
case '/redirect6':
res.writeHead(302, {
Location: '/redirect5'
});
res.end();
break;
case '/redirect5':
res.writeHead(302, {
Location: '/redirect4'
});
res.end();
break;
case '/redirect4':
res.writeHead(302, {
Location: '/redirect3'
});
res.end();
break;
case '/redirect3':
res.writeHead(302, {
Location: '/redirect2'
});
res.end();
break;
case '/redirect2':
res.writeHead(302, {
Location: '/redirect1'
});
res.end();
break;
case '/redirect1':
res.writeHead(302, {
Location: '/'
});
res.end();
break;
case '/forever':
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.write('This connection is never closed');
// never end the request
break;
case '/gzip':
res.writeHead(200, {
'Content-Type': 'text/plain',
'Content-Encoding': 'gzip'
});
var stream = zlib.createGzip();
stream.pipe(res);
stream.end('Hello World HTTP\n');
break;
case '/invalid':
res.writeHead(500, {
'Content-Type': 'text/plain'
});
res.end('Hello World HTTP\n');
break;
case '/auth':
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end(new Buffer(req.headers.authorization.split(' ').pop(), 'base64'));
break;
case '/cookie':
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end(req.headers.cookie);
break;
case '/ua':
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end(req.headers['user-agent']);
break;
case '/post':
var body = [];
req.on('readable', function () {
var chunk;
while ((chunk = req.read()) !== null) {
body.push(chunk);
}
});
req.on('end', function () {
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end(Buffer.concat(body));
});
break;
default:
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end('Hello World HTTP\n');
}
});
httpsServer = https.createServer(httpsOptions, function (req, res) {
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end('Hello World HTTPS\n');
});
httpServer.listen(HTTP_PORT, function () {
httpsServer.listen(HTTPS_PORT, done);
});
});
afterEach(function (done) {
httpServer.close(function () {
httpsServer.close(done);
});
});
it('should fetch HTTP data', function (done) {
var req = fetch('http://localhost:' + HTTP_PORT);
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('end', function () {
expect(Buffer.concat(buf).toString()).to.equal('Hello World HTTP\n');
done();
});
});
it('should fetch HTTPS data', function (done) {
var req = fetch('https://localhost:' + HTTPS_PORT);
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('end', function () {
expect(Buffer.concat(buf).toString()).to.equal('Hello World HTTPS\n');
done();
});
});
it('should fetch HTTP data with redirects', function (done) {
var req = fetch('http://localhost:' + HTTP_PORT + '/redirect3');
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('end', function () {
expect(Buffer.concat(buf).toString()).to.equal('Hello World HTTP\n');
done();
});
});
it('should return error for too many redirects', function (done) {
var req = fetch('http://localhost:' + HTTP_PORT + '/redirect6');
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('error', function (err) {
expect(err).to.exist;
done();
});
req.on('end', function () {});
});
it('should fetch HTTP data with custom redirect limit', function (done) {
var req = fetch('http://localhost:' + HTTP_PORT + '/redirect3', {
maxRedirects: 3
});
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('end', function () {
expect(Buffer.concat(buf).toString()).to.equal('Hello World HTTP\n');
done();
});
});
it('should return error for custom redirect limit', function (done) {
var req = fetch('http://localhost:' + HTTP_PORT + '/redirect3', {
maxRedirects: 2
});
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('error', function (err) {
expect(err).to.exist;
done();
});
req.on('end', function () {});
});
it('should return disable redirects', function (done) {
var req = fetch('http://localhost:' + HTTP_PORT + '/redirect1', {
maxRedirects: 0
});
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('error', function (err) {
expect(err).to.exist;
done();
});
req.on('end', function () {});
});
it('should unzip compressed HTTP data', function (done) {
var req = fetch('http://localhost:' + HTTP_PORT + '/gzip');
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('end', function () {
expect(Buffer.concat(buf).toString()).to.equal('Hello World HTTP\n');
done();
});
});
it('should return error for unresolved host', function (done) {
var req = fetch('http://asfhaskhhgbjdsfhgbsdjgk');
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('error', function (err) {
expect(err).to.exist;
done();
});
req.on('end', function () {});
});
it('should return error for invalid status', function (done) {
var req = fetch('http://localhost:' + HTTP_PORT + '/invalid');
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('error', function (err) {
expect(err).to.exist;
done();
});
req.on('end', function () {});
});
it('should return error for invalid url', function (done) {
var req = fetch('http://localhost:99999999/');
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('error', function (err) {
expect(err).to.exist;
done();
});
req.on('end', function () {});
});
it('should return timeout error', function (done) {
var req = fetch('http://localhost:' + HTTP_PORT + '/forever', {
timeout: 1000
});
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('error', function (err) {
expect(err).to.exist;
done();
});
req.on('end', function () {});
});
it('should handle basic HTTP auth', function (done) {
var req = fetch('http://user:pass@localhost:' + HTTP_PORT + '/auth');
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('end', function () {
expect(Buffer.concat(buf).toString()).to.equal('user:pass');
done();
});
});
if (!/^0\.10\./.test(process.versions.node)) {
// disabled for node 0.10
it('should return error for invalid protocol', function (done) {
var req = fetch('http://localhost:' + HTTPS_PORT);
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('error', function (err) {
expect(err).to.exist;
done();
});
req.on('end', function () {});
});
}
it('should set cookie value', function (done) {
var req = fetch('http://localhost:' + HTTP_PORT + '/cookie', {
cookie: 'test=pest'
});
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('end', function () {
expect(Buffer.concat(buf).toString()).to.equal('test=pest');
done();
});
});
it('should set user agent', function (done) {
var req = fetch('http://localhost:' + HTTP_PORT + '/ua', {
userAgent: 'nodemailer-fetch'
});
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('end', function () {
expect(Buffer.concat(buf).toString()).to.equal('nodemailer-fetch');
done();
});
});
it('should post data', function (done) {
var req = fetch('http://localhost:' + HTTP_PORT + '/post', {
method: 'post',
body: {
hello: 'world 😭',
another: 'value'
}
});
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('end', function () {
expect(Buffer.concat(buf).toString()).to.equal('hello=world%20%F0%9F%98%AD&another=value');
done();
});
});
it('should post stream data', function (done) {
var body = new PassThrough();
var data = new Buffer('hello=world%20%F0%9F%98%AD&another=value');
var req = fetch('http://localhost:' + HTTP_PORT + '/post', {
method: 'post',
body: body
});
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('end', function () {
expect(Buffer.concat(buf).toString()).to.equal(data.toString());
done();
});
var pos = 0;
var writeNext = function () {
if (pos >= data.length) {
return body.end();
}
var char = data.slice(pos++, pos);
body.write(char);
setImmediate(writeNext);
};
setImmediate(writeNext);
});
it('should return error for invalid cert', function (done) {
var req = fetch('https://localhost:' + HTTPS_PORT, {
tls: {
rejectUnauthorized: true
}
});
var buf = [];
req.on('data', function (chunk) {
buf.push(chunk);
});
req.on('error', function (err) {
expect(err).to.exist;
done();
});
req.on('end', function () {});
});
});