diff --git a/package.json b/package.json
index 2a22cdb0..81e3218d 100644
--- a/package.json
+++ b/package.json
@@ -37,7 +37,7 @@
"eslint-config-nodemailer": "^1.2.0",
"grunt": "^1.0.1",
"grunt-cli": "^1.2.0",
- "grunt-contrib-nodeunit": "^1.0.0",
+ "grunt-contrib-nodeunit": "^2.0.0",
"grunt-eslint": "^20.1.0",
"jsxgettext-andris": "^0.9.0-patch.1",
"lodash": "^4.17.4",
@@ -61,10 +61,11 @@
"connect-redis": "^3.3.0",
"cookie-parser": "^1.4.3",
"cors": "^2.8.4",
+ "country-list": "^2.2.0",
"csurf": "^1.9.0",
- "csv-parse": "^1.2.3",
+ "csv-parse": "^4.6.5",
"device": "^0.3.8",
- "dompurify": "^1.0.2",
+ "dompurify": "^2.0.7",
"escape-html": "^1.0.3",
"escape-string-regexp": "^1.0.5",
"express": "^4.15.5",
@@ -72,11 +73,11 @@
"faker": "^4.1.0",
"feedparser": "^2.2.1",
"fs-extra": "^4.0.2",
- "geoip-ultralight": "^0.1.5",
+ "geoip-country": "^3.2.6",
"gettext-parser": "^1.3.0",
"gm": "^1.23.0",
- "handlebars": "^4.0.10",
- "hbs": "^4.0.1",
+ "handlebars": "^4.4.5",
+ "hbs": "^4.0.6",
"he": "^1.1.1",
"html-to-text": "^3.3.0",
"humanize": "0.0.9",
@@ -88,7 +89,7 @@
"juice": "^4.1.1",
"libmime": "^3.1.0",
"mailparser": "^2.0.5",
- "marked": "^0.3.6",
+ "marked": "^0.7.0",
"memory-cache": "^0.2.0",
"mjml": "4.5.0",
"mkdirp": "^0.5.1",
@@ -100,13 +101,14 @@
"node-gettext": "^2.0.0",
"node-mocks-http": "^1.6.5",
"nodemailer": "^4.1.1",
- "nodemailer-openpgp": "^1.1.0",
+ "nodemailer-openpgp": "^1.2.0",
"npmlog": "^4.1.2",
"object-hash": "^1.1.8",
- "openpgp": "^2.5.11",
+ "openpgp": "^4.6.2",
"passport": "^0.4.0",
+ "passport-ldapauth": "^2.1.3",
+ "passport-ldapjs": "^1.0.3",
"passport-local": "^1.0.0",
- "passport-ldapauth": "^2.0.0",
"premailer-api": "^1.0.4",
"redfour": "^1.0.2",
"redis": "^2.8.0",
diff --git a/routes/lists.js b/routes/lists.js
index 0318db12..4247afb3 100644
--- a/routes/lists.js
+++ b/routes/lists.js
@@ -206,57 +206,85 @@ router.post('/ajax/:id', (req, res) => {
let statuses = [_('Unknown'), _('Subscribed'), _('Unsubscribed'), _('Bounced'), _('Complained')];
- res.json({
- draw: req.body.draw,
- recordsTotal: total,
- recordsFiltered: filteredTotal,
- data: data.map((row, i) => [
+ let dataPromise = Promise.all(data.map((row, i) => Promise.all([
+ Promise.resolve([
(Number(req.body.start) || 0) + 1 + i,
htmlescape(row.email || ''),
htmlescape(row.firstName || ''),
- htmlescape(row.lastName || '')
- ].concat(fields.getRow(fieldList, row).map(cRow => {
- if (cRow.type === 'number') {
- return htmlescape(cRow.value && humanize.numberFormat(cRow.value, 0) || '');
- } else if (cRow.type === 'longtext') {
- let value = (cRow.value || '');
- if (value.length > 50) {
- value = value.substr(0, 47).trim() + '…';
- }
- return htmlescape(value);
- } else if (cRow.type === 'gpg') {
- let value = (cRow.value || '').trim();
- try {
- value = openpgp.key.readArmored(value);
- if (value) {
-
- let keys = value.keys;
- for (let i = 0; i < keys.length; i++) {
- let key = keys[i];
- switch (key.verifyPrimaryKey()) {
- case 0:
- return _('Invalid key');
- case 1:
- return _('Expired key');
- case 2:
- return _('Revoked key');
+ htmlescape(row.lastName || ''),
+ ]),
+ new Promise((resolve) => {
+ let fieldsPromise = Promise.all(fields.getRow(fieldList, row).map( async (cRow) => {
+ switch (cRow.type) {
+ case 'number':
+ return Promise.resolve(htmlescape(cRow.value && humanize.numberFormat(cRow.value, 0) || ''));
+ case 'longtext':
+ let value = (cRow.value || '');
+ if (value.length > 50) {
+ value = value.substr(0, 47).trim() + '…';
+ }
+ return Promise.resolve(htmlescape(value));
+ case 'gpg':
+ return new Promise((resolve) => {
+ let value = (cRow.value || '').trim();
+ if (!value.length) {
+ resolve(''); // no key
}
- }
+ openpgp.key.readArmored(value).
+ then((value) => {
+ const noResultsError = new Error('No armored keys returned');
- value = value.keys && value.keys[0] && value.keys[0].primaryKey.fingerprint;
- if (value) {
- value = '0x' + value.substr(-16).toUpperCase();
- }
- }
- } catch (E) {
- value = 'parse error';
+ if (!(typeof value === 'object') || !('keys' in value)) { throw noResultsError }
+ if (value.err && value.err.length) { throw value.err[0] }
+ if (!value.keys.length) { throw noResultsError }
+
+ let keysVerification = Promise.race(value.keys.map((key) => key.verifyPrimaryKey()));
+
+ keysVerification.then((verifiedKey) => {
+ switch (verifiedKey) {
+ case 0:
+ return resolve(_('Invalid key'));
+ case 1:
+ return resolve(_('Expired key'));
+ case 2:
+ return resolve(_('Revoked key'));
+ }
+ value = value.keys[0].primaryKey.fingerprint.join('');
+ if (value) {
+ value = '0x' + value.substr(-16).toUpperCase();
+ }
+ return resolve(htmlescape(value || ''));
+ });
+ }).
+ catch((error) => {
+ const message = error.message || error || _('Unexpected error');
+ resolve('' + _('Parse error') + ' ');
+ });
+ }).catch((error) => log.error('GPG', error));
+ default:
+ return Promise.resolve(htmlescape(cRow.value || ''));
}
- return htmlescape(value || '');
- } else {
- return htmlescape(cRow.value || '');
- }
- })).concat(config.views.lists.statuscolored ? '' + statuses[row.status] + '' : statuses[row.status]).concat(row.created && row.created.toISOString ? '' + row.created.toISOString() + '' : 'N/A').concat('' + _('Edit') + ''))
- });
+ }));
+
+ fieldsPromise.then(fieldsResults => {
+ resolve(fieldsResults);
+ }).catch((error) => log.error('Lists', error));
+ }),
+ Promise.resolve(config.views.lists.statuscolored ? '' + statuses[row.status] + '' : statuses[row.status] ),
+ Promise.resolve(row.created && row.created.toISOString ? '' + row.created.toISOString() + '' : 'N/A' ),
+ Promise.resolve('' + _('Edit') + '' )
+ ])));
+
+ dataPromise.then(data => {
+ data = data.map(row => row.reduce((acc, val) => acc.concat(val), [])); // flatten results
+ res.json({
+ draw: req.body.draw,
+ recordsTotal: total,
+ recordsFiltered: filteredTotal,
+ data: data
+ })
+ }).catch((error) => res.json({ draw: req.body.draw, recordsTotal: 0, recordsFiltered: 0, data:[], error: error.message }));
+
});
});
});
diff --git a/routes/subscription.js b/routes/subscription.js
index a1d24644..3c198eff 100644
--- a/routes/subscription.js
+++ b/routes/subscription.js
@@ -864,30 +864,43 @@ router.post('/publickey', passport.parseForm, (req, res, next) => {
return next(err);
}
- let privKey;
- try {
- privKey = openpgp.key.readArmored(configItems.pgpPrivateKey).keys[0];
- if (configItems.pgpPassphrase && !privKey.decrypt(configItems.pgpPassphrase)) {
- privKey = false;
- }
- } catch (E) {
- // just ignore if failed
- }
-
- if (!privKey) {
+ const sendError = () => {
err = new Error(_('Public key is not set'));
err.status = 404;
return next(err);
}
- let pubkey = privKey.toPublic().armor();
+ const sendResult = (privKey) => {
+ let pubkey = privKey.toPublic().armor();
+ res.writeHead(200, {
+ 'Content-Type': 'application/octet-stream',
+ 'Content-Disposition': 'attachment; filename=public.asc'
+ });
+ res.end(pubkey);
+ }
- res.writeHead(200, {
- 'Content-Type': 'application/octet-stream',
- 'Content-Disposition': 'attachment; filename=public.asc'
- });
-
- res.end(pubkey);
+ openpgp.key.readArmored(configItems.pgpPrivateKey).
+ then((armored) => {
+ let privKey = armored.keys[0];
+ if (configItems.pgpPassphrase) {
+ privKey.decrypt(configItems.pgpPassphrase).
+ then((success) => {
+ if (success) {
+ sendResult(privKey);
+ }
+ }).
+ catch((error) => {
+ log.error('GPG', error);
+ sendError();
+ })
+ } else {
+ sendResult(privKey);
+ }
+ }).
+ catch((error) => {
+ log.error('GPG', error);
+ sendError();
+ });
});
});
diff --git a/views/subscription/partials/subscription-custom-fields.hbs b/views/subscription/partials/subscription-custom-fields.hbs
index f63a1b80..dab3d0a2 100644
--- a/views/subscription/partials/subscription-custom-fields.hbs
+++ b/views/subscription/partials/subscription-custom-fields.hbs
@@ -70,7 +70,7 @@
{{/if}}
-
+
{{#translate}}Insert your GPG public key here to encrypt messages sent to your address{{/translate}} ({{#translate}}optional{{/translate}})