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,59 @@
'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,
globals: {
Promise: false
}
};

View File

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

View File

@@ -0,0 +1,18 @@
language: node_js
sudo: false
node_js:
- 0.12
- iojs
- '4'
- '6'
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,27 @@
'use strict';
module.exports = function (grunt) {
// Project configuration.
grunt.initConfig({
eslint: {
all: ['lib/*.js', 'test/*.js', 'Gruntfile.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) 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,14 @@
# nodemailer-shared
Shared methods for the [Nodemailer](https://github.com/nodemailer/nodemailer) stack.
## Methods
* `parseConnectionUrl(str)` parses a connection url into a nodemailer configuration object
* `getLogger(options)` returns a bunyan compatible logger instance
* `callbackPromise(resolve, reject)` returns a promise-resolving function suitable for using as a callback
* `resolveContent(data, key, callback)` converts a key of a data object from stream/url/path to a buffer
## License
**MIT**

View File

@@ -0,0 +1,282 @@
'use strict';
var urllib = require('url');
var util = require('util');
var fs = require('fs');
var fetch = require('nodemailer-fetch');
/**
* Parses connection url to a structured configuration object
*
* @param {String} str Connection url
* @return {Object} Configuration object
*/
module.exports.parseConnectionUrl = function (str) {
str = str || '';
var options = {};
[urllib.parse(str, true)].forEach(function (url) {
var auth;
switch (url.protocol) {
case 'smtp:':
options.secure = false;
break;
case 'smtps:':
options.secure = true;
break;
case 'direct:':
options.direct = true;
break;
}
if (!isNaN(url.port) && Number(url.port)) {
options.port = Number(url.port);
}
if (url.hostname) {
options.host = url.hostname;
}
if (url.auth) {
auth = url.auth.split(':');
if (!options.auth) {
options.auth = {};
}
options.auth.user = auth.shift();
options.auth.pass = auth.join(':');
}
Object.keys(url.query || {}).forEach(function (key) {
var obj = options;
var lKey = key;
var value = url.query[key];
if (!isNaN(value)) {
value = Number(value);
}
switch (value) {
case 'true':
value = true;
break;
case 'false':
value = false;
break;
}
// tls is nested object
if (key.indexOf('tls.') === 0) {
lKey = key.substr(4);
if (!options.tls) {
options.tls = {};
}
obj = options.tls;
} else if (key.indexOf('.') >= 0) {
// ignore nested properties besides tls
return;
}
if (!(lKey in obj)) {
obj[lKey] = value;
}
});
});
return options;
};
/**
* Returns a bunyan-compatible logger interface. Uses either provided logger or
* creates a default console logger
*
* @param {Object} [options] Options object that might include 'logger' value
* @return {Object} bunyan compatible logger
*/
module.exports.getLogger = function (options) {
options = options || {};
if (!options.logger) {
// use vanity logger
return {
info: function () {},
debug: function () {},
error: function () {}
};
}
if (options.logger === true) {
// create console logger
return createDefaultLogger();
}
// return whatever was passed
return options.logger;
};
/**
* Wrapper for creating a callback than either resolves or rejects a promise
* based on input
*
* @param {Function} resolve Function to run if callback is called
* @param {Function} reject Function to run if callback ends with an error
*/
module.exports.callbackPromise = function (resolve, reject) {
return function () {
var args = Array.prototype.slice.call(arguments);
var err = args.shift();
if (err) {
reject(err);
} else {
resolve.apply(null, args);
}
};
};
/**
* Resolves a String or a Buffer value for content value. Useful if the value
* is a Stream or a file or an URL. If the value is a Stream, overwrites
* the stream object with the resolved value (you can't stream a value twice).
*
* This is useful when you want to create a plugin that needs a content value,
* for example the `html` or `text` value as a String or a Buffer but not as
* a file path or an URL.
*
* @param {Object} data An object or an Array you want to resolve an element for
* @param {String|Number} key Property name or an Array index
* @param {Function} callback Callback function with (err, value)
*/
module.exports.resolveContent = function (data, key, callback) {
var promise;
if (!callback && typeof Promise === 'function') {
promise = new Promise(function (resolve, reject) {
callback = module.exports.callbackPromise(resolve, reject);
});
}
var content = data && data[key] && data[key].content || data[key];
var contentStream;
var encoding = (typeof data[key] === 'object' && data[key].encoding || 'utf8')
.toString()
.toLowerCase()
.replace(/[-_\s]/g, '');
if (!content) {
return callback(null, content);
}
if (typeof content === 'object') {
if (typeof content.pipe === 'function') {
return resolveStream(content, function (err, value) {
if (err) {
return callback(err);
}
// we can't stream twice the same content, so we need
// to replace the stream object with the streaming result
data[key] = value;
callback(null, value);
});
} else if (/^https?:\/\//i.test(content.path || content.href)) {
contentStream = fetch(content.path || content.href);
return resolveStream(contentStream, callback);
} else if (/^data:/i.test(content.path || content.href)) {
var parts = (content.path || content.href).match(/^data:((?:[^;]*;)*(?:[^,]*)),(.*)$/i);
if (!parts) {
return callback(null, new Buffer(0));
}
return callback(null, /\bbase64$/i.test(parts[1]) ? new Buffer(parts[2], 'base64') : new Buffer(decodeURIComponent(parts[2])));
} else if (content.path) {
return resolveStream(fs.createReadStream(content.path), callback);
}
}
if (typeof data[key].content === 'string' && ['utf8', 'usascii', 'ascii'].indexOf(encoding) < 0) {
content = new Buffer(data[key].content, encoding);
}
// default action, return as is
setImmediate(callback.bind(null, null, content));
return promise;
};
/**
* Streams a stream value into a Buffer
*
* @param {Object} stream Readable stream
* @param {Function} callback Callback function with (err, value)
*/
function resolveStream(stream, callback) {
var responded = false;
var chunks = [];
var chunklen = 0;
stream.on('error', function (err) {
if (responded) {
return;
}
responded = true;
callback(err);
});
stream.on('readable', function () {
var chunk;
while ((chunk = stream.read()) !== null) {
chunks.push(chunk);
chunklen += chunk.length;
}
});
stream.on('end', function () {
if (responded) {
return;
}
responded = true;
var value;
try {
value = Buffer.concat(chunks, chunklen);
} catch (E) {
return callback(E);
}
callback(null, value);
});
}
/**
* Generates a bunyan-like logger that prints to console
*
* @returns {Object} Bunyan logger instance
*/
function createDefaultLogger() {
var logger = {
_print: function ( /* level, message */ ) {
var args = Array.prototype.slice.call(arguments);
var level = args.shift();
var message;
if (args.length > 1) {
message = util.format.apply(util, args);
} else {
message = args.shift();
}
console.log('[%s] %s: %s',
new Date().toISOString().substr(0, 19).replace(/T/, ' '),
level.toUpperCase(),
message);
}
};
logger.info = logger._print.bind(null, 'info');
logger.debug = logger._print.bind(null, 'debug');
logger.error = logger._print.bind(null, 'error');
return logger;
}

View File

@@ -0,0 +1,63 @@
{
"_from": "nodemailer-shared@1.1.0",
"_id": "nodemailer-shared@1.1.0",
"_inBundle": false,
"_integrity": "sha1-z1mU4v0mjQD1zw+nZ6CBae2wfsA=",
"_location": "/nodemailer-shared",
"_phantomChildren": {},
"_requested": {
"type": "version",
"registry": true,
"raw": "nodemailer-shared@1.1.0",
"name": "nodemailer-shared",
"escapedName": "nodemailer-shared",
"rawSpec": "1.1.0",
"saveSpec": null,
"fetchSpec": "1.1.0"
},
"_requiredBy": [
"/buildmail"
],
"_resolved": "https://registry.npmjs.org/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz",
"_shasum": "cf5994e2fd268d00f5cf0fa767a08169edb07ec0",
"_spec": "nodemailer-shared@1.1.0",
"_where": "/Users/rxf/Projekte/SternDBase/sternwarte/checkfuehrung/node_modules/buildmail",
"author": {
"name": "Andris Reinman"
},
"bugs": {
"url": "https://github.com/nodemailer/nodemailer-shared/issues"
},
"bundleDependencies": false,
"dependencies": {
"nodemailer-fetch": "1.6.0"
},
"deprecated": false,
"description": "Shared methods for the nodemailer stack",
"devDependencies": {
"chai": "^3.5.0",
"grunt": "^1.0.1",
"grunt-cli": "^1.2.0",
"grunt-eslint": "^19.0.0",
"grunt-mocha-test": "^0.12.7",
"mocha": "^3.0.2"
},
"directories": {
"test": "test"
},
"homepage": "https://github.com/nodemailer/nodemailer-shared#readme",
"keywords": [
"nodemailer"
],
"license": "MIT",
"main": "lib/shared.js",
"name": "nodemailer-shared",
"repository": {
"type": "git",
"url": "git+https://github.com/nodemailer/nodemailer-shared.git"
},
"scripts": {
"test": "grunt mochaTest"
},
"version": "1.1.0"
}

View File

@@ -0,0 +1 @@
<p>Tere, tere</p><p>vana kere!</p>

View File

@@ -0,0 +1,291 @@
/* eslint no-unused-expressions:0, no-invalid-this:0 */
/* globals beforeEach, afterEach, describe, it */
'use strict';
var chai = require('chai');
var expect = chai.expect;
var shared = require('../lib/shared');
var http = require('http');
var fs = require('fs');
var zlib = require('zlib');
chai.config.includeStack = true;
describe('Logger tests', function () {
it('Should create a logger', function () {
expect(typeof shared.getLogger({
logger: false
})).to.equal('object');
expect(typeof shared.getLogger()).to.equal('object');
expect(typeof shared.getLogger({
logger: 'stri'
})).to.equal('string');
});
});
describe('Connection url parser tests', function () {
it('Should parse connection url', function () {
var url = 'smtps://user:pass@localhost:123?tls.rejectUnauthorized=false&name=horizon';
expect(shared.parseConnectionUrl(url)).to.deep.equal({
secure: true,
port: 123,
host: 'localhost',
auth: {
user: 'user',
pass: 'pass'
},
tls: {
rejectUnauthorized: false
},
name: 'horizon'
});
});
it('should not choke on special symbols in auth', function () {
var url = 'smtps://user%40gmail.com:%3Apasswith%25Char@smtp.gmail.com';
expect(shared.parseConnectionUrl(url)).to.deep.equal({
secure: true,
host: 'smtp.gmail.com',
auth: {
user: 'user@gmail.com',
pass: ':passwith%Char'
}
});
});
});
describe('Resolver tests', function () {
var port = 10337;
var server;
beforeEach(function (done) {
server = http.createServer(function (req, res) {
if (/redirect/.test(req.url)) {
res.writeHead(302, {
Location: 'http://localhost:' + port + '/message.html'
});
res.end('Go to http://localhost:' + port + '/message.html');
} else if (/compressed/.test(req.url)) {
res.writeHead(200, {
'Content-Type': 'text/plain',
'Content-Encoding': 'gzip'
});
var stream = zlib.createGzip();
stream.pipe(res);
stream.write('<p>Tere, tere</p><p>vana kere!</p>\n');
stream.end();
} else {
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end('<p>Tere, tere</p><p>vana kere!</p>\n');
}
});
server.listen(port, done);
});
afterEach(function (done) {
server.close(done);
});
it('should set text from html string', function (done) {
var mail = {
data: {
html: '<p>Tere, tere</p><p>vana kere!</p>\n'
}
};
shared.resolveContent(mail.data, 'html', function (err, value) {
expect(err).to.not.exist;
expect(value).to.equal('<p>Tere, tere</p><p>vana kere!</p>\n');
done();
});
});
it('should set text from html buffer', function (done) {
var mail = {
data: {
html: new Buffer('<p>Tere, tere</p><p>vana kere!</p>\n')
}
};
shared.resolveContent(mail.data, 'html', function (err, value) {
expect(err).to.not.exist;
expect(value).to.deep.equal(mail.data.html);
done();
});
});
it('should set text from a html file', function (done) {
var mail = {
data: {
html: {
path: __dirname + '/fixtures/message.html'
}
}
};
shared.resolveContent(mail.data, 'html', function (err, value) {
expect(err).to.not.exist;
expect(value).to.deep.equal(new Buffer('<p>Tere, tere</p><p>vana kere!</p>\n'));
done();
});
});
it('should set text from an html url', function (done) {
var mail = {
data: {
html: {
path: 'http://localhost:' + port + '/message.html'
}
}
};
shared.resolveContent(mail.data, 'html', function (err, value) {
expect(err).to.not.exist;
expect(value).to.deep.equal(new Buffer('<p>Tere, tere</p><p>vana kere!</p>\n'));
done();
});
});
it('should set text from redirecting url', function (done) {
var mail = {
data: {
html: {
path: 'http://localhost:' + port + '/redirect.html'
}
}
};
shared.resolveContent(mail.data, 'html', function (err, value) {
expect(err).to.not.exist;
expect(value).to.deep.equal(new Buffer('<p>Tere, tere</p><p>vana kere!</p>\n'));
done();
});
});
it('should set text from gzipped url', function (done) {
var mail = {
data: {
html: {
path: 'http://localhost:' + port + '/compressed.html'
}
}
};
shared.resolveContent(mail.data, 'html', function (err, value) {
expect(err).to.not.exist;
expect(value).to.deep.equal(new Buffer('<p>Tere, tere</p><p>vana kere!</p>\n'));
done();
});
});
it('should set text from a html stream', function (done) {
var mail = {
data: {
html: fs.createReadStream(__dirname + '/fixtures/message.html')
}
};
shared.resolveContent(mail.data, 'html', function (err, value) {
expect(err).to.not.exist;
expect(mail).to.deep.equal({
data: {
html: new Buffer('<p>Tere, tere</p><p>vana kere!</p>\n')
}
});
expect(value).to.deep.equal(new Buffer('<p>Tere, tere</p><p>vana kere!</p>\n'));
done();
});
});
it('should return an error', function (done) {
var mail = {
data: {
html: {
path: 'http://localhost:' + (port + 1000) + '/message.html'
}
}
};
shared.resolveContent(mail.data, 'html', function (err) {
expect(err).to.exist;
done();
});
});
it('should return encoded string as buffer', function (done) {
var str = '<p>Tere, tere</p><p>vana kere!</p>\n';
var mail = {
data: {
html: {
encoding: 'base64',
content: new Buffer(str).toString('base64')
}
}
};
shared.resolveContent(mail.data, 'html', function (err, value) {
expect(err).to.not.exist;
expect(value).to.deep.equal(new Buffer(str));
done();
});
});
describe('data uri tests', function () {
it('should resolve with mime type and base64', function (done) {
var mail = {
data: {
attachment: {
path: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
}
}
};
shared.resolveContent(mail.data, 'attachment', function (err, value) {
expect(err).to.not.exist;
expect(value).to.deep.equal(new Buffer('iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==', 'base64'));
done();
});
});
it('should resolve with mime type and plaintext', function (done) {
var mail = {
data: {
attachment: {
path: 'data:image/png,tere%20tere'
}
}
};
shared.resolveContent(mail.data, 'attachment', function (err, value) {
expect(err).to.not.exist;
expect(value).to.deep.equal(new Buffer('tere tere'));
done();
});
});
it('should resolve with plaintext', function (done) {
var mail = {
data: {
attachment: {
path: 'data:,tere%20tere'
}
}
};
shared.resolveContent(mail.data, 'attachment', function (err, value) {
expect(err).to.not.exist;
expect(value).to.deep.equal(new Buffer('tere tere'));
done();
});
});
it('should resolve with mime type, charset and base64', function (done) {
var mail = {
data: {
attachment: {
path: 'data:image/png;charset=iso-8859-1;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg=='
}
}
};
shared.resolveContent(mail.data, 'attachment', function (err, value) {
expect(err).to.not.exist;
expect(value).to.deep.equal(new Buffer('iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==', 'base64'));
done();
});
});
});
});