mirror of
https://github.com/nodemailer/wildduck.git
synced 2025-09-30 08:54:34 +08:00
v1.0.107
This commit is contained in:
parent
2daaa8127d
commit
0efcf316a5
8 changed files with 102 additions and 115 deletions
|
@ -1,3 +1,6 @@
|
||||||
{
|
{
|
||||||
|
"rules": {
|
||||||
|
"indent": 0
|
||||||
|
},
|
||||||
"extends": "nodemailer"
|
"extends": "nodemailer"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
# Push messages to ZoneMTA queue for delivery
|
|
||||||
# if `false` then no messages are sent
|
|
||||||
enabled=true
|
|
||||||
|
|
||||||
# which ZoneMTA queue to use by default
|
# which ZoneMTA queue to use by default
|
||||||
zone="default"
|
zone="default"
|
||||||
|
|
||||||
|
|
|
@ -1002,7 +1002,7 @@ module.exports = (db, server) => {
|
||||||
* @apiParam {Number} [forwards] Daily allowed forwarding count for this address
|
* @apiParam {Number} [forwards] Daily allowed forwarding count for this address
|
||||||
* @apiParam {Boolean} [allowWildcard=false] If <code>true</code> then address value can be in the form of <code>*@example.com</code>, otherwise using * is not allowed
|
* @apiParam {Boolean} [allowWildcard=false] If <code>true</code> then address value can be in the form of <code>*@example.com</code>, otherwise using * is not allowed
|
||||||
* @apiParam {Object} [autoreply] Autoreply information
|
* @apiParam {Object} [autoreply] Autoreply information
|
||||||
* @apiParam {Boolean} [autoreply.enabled] If true, then autoreply is enabled for this address
|
* @apiParam {Boolean} [autoreply.status] If true, then autoreply is enabled for this address
|
||||||
* @apiParam {String} [autoreply.start] Either a date string or boolean false to disable start time checks
|
* @apiParam {String} [autoreply.start] Either a date string or boolean false to disable start time checks
|
||||||
* @apiParam {String} [autoreply.end] Either a date string or boolean false to disable end time checks
|
* @apiParam {String} [autoreply.end] Either a date string or boolean false to disable end time checks
|
||||||
* @apiParam {String} [autoreply.subject] Autoreply subject line
|
* @apiParam {String} [autoreply.subject] Autoreply subject line
|
||||||
|
@ -1062,7 +1062,7 @@ module.exports = (db, server) => {
|
||||||
.default(0),
|
.default(0),
|
||||||
allowWildcard: Joi.boolean().truthy(['Y', 'true', 'yes', 1]),
|
allowWildcard: Joi.boolean().truthy(['Y', 'true', 'yes', 1]),
|
||||||
autoreply: Joi.object().keys({
|
autoreply: Joi.object().keys({
|
||||||
enabled: Joi.boolean()
|
status: Joi.boolean()
|
||||||
.truthy(['Y', 'true', 'yes', 1])
|
.truthy(['Y', 'true', 'yes', 1])
|
||||||
.default(true),
|
.default(true),
|
||||||
start: Joi.date()
|
start: Joi.date()
|
||||||
|
@ -1127,7 +1127,7 @@ module.exports = (db, server) => {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
result.value.autoreply = {
|
result.value.autoreply = {
|
||||||
enabled: false
|
status: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1299,7 +1299,7 @@ module.exports = (db, server) => {
|
||||||
* @apiParam {String[]} [targets] An array of forwarding targets. The value could either be an email address or a relay url to next MX server ("smtp://mx2.zone.eu:25"). If set then overwrites previous targets array
|
* @apiParam {String[]} [targets] An array of forwarding targets. The value could either be an email address or a relay url to next MX server ("smtp://mx2.zone.eu:25"). If set then overwrites previous targets array
|
||||||
* @apiParam {Number} [forwards] Daily allowed forwarding count for this address
|
* @apiParam {Number} [forwards] Daily allowed forwarding count for this address
|
||||||
* @apiParam {Object} [autoreply] Autoreply information
|
* @apiParam {Object} [autoreply] Autoreply information
|
||||||
* @apiParam {Boolean} [autoreply.enabled] If true, then autoreply is enabled for this address
|
* @apiParam {Boolean} [autoreply.status] If true, then autoreply is enabled for this address
|
||||||
* @apiParam {String} [autoreply.start] Either a date string or boolean false to disable start time checks
|
* @apiParam {String} [autoreply.start] Either a date string or boolean false to disable start time checks
|
||||||
* @apiParam {String} [autoreply.end] Either a date string or boolean false to disable end time checks
|
* @apiParam {String} [autoreply.end] Either a date string or boolean false to disable end time checks
|
||||||
* @apiParam {String} [autoreply.subject] Autoreply subject line
|
* @apiParam {String} [autoreply.subject] Autoreply subject line
|
||||||
|
@ -1351,7 +1351,7 @@ module.exports = (db, server) => {
|
||||||
.min(1),
|
.min(1),
|
||||||
forwards: Joi.number().min(0),
|
forwards: Joi.number().min(0),
|
||||||
autoreply: Joi.object().keys({
|
autoreply: Joi.object().keys({
|
||||||
enabled: Joi.boolean().truthy(['Y', 'true', 'yes', 1]),
|
status: Joi.boolean().truthy(['Y', 'true', 'yes', 1]),
|
||||||
start: Joi.date()
|
start: Joi.date()
|
||||||
.empty('')
|
.empty('')
|
||||||
.allow(false),
|
.allow(false),
|
||||||
|
@ -1708,7 +1708,7 @@ module.exports = (db, server) => {
|
||||||
* @apiSuccess {Number} limits.forwards.used How many messages are forwarded during current 24 hour period
|
* @apiSuccess {Number} limits.forwards.used How many messages are forwarded during current 24 hour period
|
||||||
* @apiSuccess {Number} limits.forwards.ttl Time until the end of current 24 hour period
|
* @apiSuccess {Number} limits.forwards.ttl Time until the end of current 24 hour period
|
||||||
* @apiSuccess {Object} autoreply Autoreply information
|
* @apiSuccess {Object} autoreply Autoreply information
|
||||||
* @apiSuccess {Boolean} autoreply.enabled If true, then autoreply is enabled for this address
|
* @apiSuccess {Boolean} autoreply.status If true, then autoreply is enabled for this address
|
||||||
* @apiSuccess {String} autoreply.subject Autoreply subject line
|
* @apiSuccess {String} autoreply.subject Autoreply subject line
|
||||||
* @apiSuccess {String} autoreply.text Autoreply plaintext content
|
* @apiSuccess {String} autoreply.text Autoreply plaintext content
|
||||||
* @apiSuccess {String} autoreply.html Autoreply HTML content
|
* @apiSuccess {String} autoreply.html Autoreply HTML content
|
||||||
|
@ -1818,7 +1818,7 @@ module.exports = (db, server) => {
|
||||||
ttl: forwardsTtl >= 0 ? forwardsTtl : false
|
ttl: forwardsTtl >= 0 ? forwardsTtl : false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
autoreply: addressData.autoreply || { enabled: false },
|
autoreply: addressData.autoreply || { status: false },
|
||||||
created: addressData.created
|
created: addressData.created
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1851,7 +1851,7 @@ module.exports = (db, server) => {
|
||||||
* @apiSuccess {Number} limits.forwards.used How many messages are forwarded during current 24 hour period
|
* @apiSuccess {Number} limits.forwards.used How many messages are forwarded during current 24 hour period
|
||||||
* @apiSuccess {Number} limits.forwards.ttl Time until the end of current 24 hour period
|
* @apiSuccess {Number} limits.forwards.ttl Time until the end of current 24 hour period
|
||||||
* @apiSuccess {Object} autoreply Autoreply information
|
* @apiSuccess {Object} autoreply Autoreply information
|
||||||
* @apiSuccess {Boolean} autoreply.enabled If true, then autoreply is enabled for this address
|
* @apiSuccess {Boolean} autoreply.status If true, then autoreply is enabled for this address
|
||||||
* @apiSuccess {String} autoreply.subject Autoreply subject line
|
* @apiSuccess {String} autoreply.subject Autoreply subject line
|
||||||
* @apiSuccess {String} autoreply.text Autoreply plaintext content
|
* @apiSuccess {String} autoreply.text Autoreply plaintext content
|
||||||
* @apiSuccess {String} autoreply.html Autoreply HTML content
|
* @apiSuccess {String} autoreply.html Autoreply HTML content
|
||||||
|
@ -1889,7 +1889,7 @@ module.exports = (db, server) => {
|
||||||
* }
|
* }
|
||||||
* },
|
* },
|
||||||
* "autoreply": {
|
* "autoreply": {
|
||||||
* "enabled": false
|
* "status": false
|
||||||
* },
|
* },
|
||||||
* "created": "2017-10-24T11:19:10.911Z"
|
* "created": "2017-10-24T11:19:10.911Z"
|
||||||
* }
|
* }
|
||||||
|
@ -1995,7 +1995,7 @@ module.exports = (db, server) => {
|
||||||
ttl: forwardsTtl >= 0 ? forwardsTtl : false
|
ttl: forwardsTtl >= 0 ? forwardsTtl : false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
autoreply: addressData.autoreply || { enabled: false },
|
autoreply: addressData.autoreply || { status: false },
|
||||||
created: addressData.created
|
created: addressData.created
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ module.exports = (options, autoreplyData, callback) => {
|
||||||
},
|
},
|
||||||
headers: {
|
headers: {
|
||||||
'Auto-Submitted': 'auto-replied',
|
'Auto-Submitted': 'auto-replied',
|
||||||
'X-WD-Autoreply-For': (options.parentId || '').toString()
|
'X-WD-Autoreply-For': (options.parentId || options.queueId).toString()
|
||||||
},
|
},
|
||||||
inReplyTo: headers.getFirst('Message-ID'),
|
inReplyTo: headers.getFirst('Message-ID'),
|
||||||
references: (headers.getFirst('Message-ID') + ' ' + headers.getFirst('References')).trim(),
|
references: (headers.getFirst('Message-ID') + ' ' + headers.getFirst('References')).trim(),
|
||||||
|
@ -104,18 +104,24 @@ module.exports = (options, autoreplyData, callback) => {
|
||||||
}
|
}
|
||||||
return callback(err, ...args);
|
return callback(err, ...args);
|
||||||
}
|
}
|
||||||
options.db.database.collection('messagelog').insertOne(
|
let logentry = {
|
||||||
{
|
id: args[0].id,
|
||||||
id: args[0].id,
|
messageId: args[0].messageId,
|
||||||
messageId: args[0].messageId,
|
action: 'AUTOREPLY',
|
||||||
parentId: options.parentId,
|
from: '',
|
||||||
action: 'AUTOREPLY',
|
to: options.sender,
|
||||||
from: '',
|
sender: options.recipient,
|
||||||
to: options.sender,
|
created: new Date()
|
||||||
created: new Date()
|
};
|
||||||
},
|
|
||||||
() => callback(err, args && args[0].id)
|
if (options.parentId) {
|
||||||
);
|
logentry.parentId = options.parentId;
|
||||||
|
}
|
||||||
|
if (options.queueId) {
|
||||||
|
logentry.queueId = options.queueId;
|
||||||
|
}
|
||||||
|
|
||||||
|
options.db.database.collection('messagelog').insertOne(logentry, () => callback(err, args && args[0].id));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,6 @@ class FilterHandler {
|
||||||
this.spamChecks = options.spamChecks || tools.prepareSpamChecks(defaultSpamHeaderKeys);
|
this.spamChecks = options.spamChecks || tools.prepareSpamChecks(defaultSpamHeaderKeys);
|
||||||
this.spamHeaderKeys = options.spamHeaderKeys || this.spamChecks.map(check => check.key);
|
this.spamHeaderKeys = options.spamHeaderKeys || this.spamChecks.map(check => check.key);
|
||||||
|
|
||||||
this.senderEnabled = options.sender.enabled;
|
|
||||||
this.maildrop = new Maildropper({
|
this.maildrop = new Maildropper({
|
||||||
db: this.db,
|
db: this.db,
|
||||||
zone: options.sender.zone,
|
zone: options.sender.zone,
|
||||||
|
@ -329,10 +328,6 @@ class FilterHandler {
|
||||||
};
|
};
|
||||||
|
|
||||||
let forwardMessage = done => {
|
let forwardMessage = done => {
|
||||||
if (!this.senderEnabled) {
|
|
||||||
return setImmediate(done);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userData.forward && !filterActions.get('delete')) {
|
if (userData.forward && !filterActions.get('delete')) {
|
||||||
// forward to default recipient only if the message is not deleted
|
// forward to default recipient only if the message is not deleted
|
||||||
(Array.isArray(userData.forward) ? userData.forward : [].concat(userData.forward || [])).forEach(forward => {
|
(Array.isArray(userData.forward) ? userData.forward : [].concat(userData.forward || [])).forEach(forward => {
|
||||||
|
@ -397,10 +392,6 @@ class FilterHandler {
|
||||||
};
|
};
|
||||||
|
|
||||||
let sendAutoreply = done => {
|
let sendAutoreply = done => {
|
||||||
if (!this.senderEnabled) {
|
|
||||||
return setImmediate(done);
|
|
||||||
}
|
|
||||||
|
|
||||||
// never reply to messages marked as spam
|
// never reply to messages marked as spam
|
||||||
if (!sender || !userData.autoreply || filterActions.get('spam')) {
|
if (!sender || !userData.autoreply || filterActions.get('spam')) {
|
||||||
return setImmediate(done);
|
return setImmediate(done);
|
||||||
|
|
|
@ -7,16 +7,10 @@ const Maildropper = require('./maildropper');
|
||||||
let maildropper;
|
let maildropper;
|
||||||
|
|
||||||
module.exports = (options, callback) => {
|
module.exports = (options, callback) => {
|
||||||
if (!config.sender.enabled) {
|
|
||||||
setImmediate(() => callback(null, false));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
maildropper =
|
maildropper =
|
||||||
maildropper ||
|
maildropper ||
|
||||||
new Maildropper({
|
new Maildropper({
|
||||||
db,
|
db,
|
||||||
enabled: config.sender.enabled,
|
|
||||||
zone: config.sender.zone,
|
zone: config.sender.zone,
|
||||||
collection: config.sender.collection,
|
collection: config.sender.collection,
|
||||||
gfs: config.sender.gfs
|
gfs: config.sender.gfs
|
||||||
|
|
|
@ -48,16 +48,22 @@ class UserHandler {
|
||||||
let username = address.substr(0, address.indexOf('@')).replace(/\./g, '');
|
let username = address.substr(0, address.indexOf('@')).replace(/\./g, '');
|
||||||
let domain = address.substr(address.indexOf('@') + 1);
|
let domain = address.substr(address.indexOf('@') + 1);
|
||||||
|
|
||||||
|
let fields = {
|
||||||
|
user: true,
|
||||||
|
targets: true
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys(options.fields || {}).forEach(key => {
|
||||||
|
fields[key] = true;
|
||||||
|
});
|
||||||
|
|
||||||
// try exact match
|
// try exact match
|
||||||
this.users.collection('addresses').findOne(
|
this.users.collection('addresses').findOne(
|
||||||
{
|
{
|
||||||
addrview: username + '@' + domain
|
addrview: username + '@' + domain
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fields: {
|
fields
|
||||||
user: true,
|
|
||||||
targets: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
(err, addressData) => {
|
(err, addressData) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -87,10 +93,7 @@ class UserHandler {
|
||||||
addrview: username + '@' + aliasDomain
|
addrview: username + '@' + aliasDomain
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fields: {
|
fields
|
||||||
user: true,
|
|
||||||
targets: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
done
|
done
|
||||||
);
|
);
|
||||||
|
@ -124,10 +127,7 @@ class UserHandler {
|
||||||
this.users.collection('addresses').findOne(
|
this.users.collection('addresses').findOne(
|
||||||
query,
|
query,
|
||||||
{
|
{
|
||||||
fields: {
|
fields
|
||||||
user: true,
|
|
||||||
targets: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
(err, addressData) => {
|
(err, addressData) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -145,10 +145,7 @@ class UserHandler {
|
||||||
addrview: address.substr(0, address.indexOf('@')).replace(/\./g, '') + '@*'
|
addrview: address.substr(0, address.indexOf('@')).replace(/\./g, '') + '@*'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
fields: {
|
fields
|
||||||
user: true,
|
|
||||||
targets: true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
(err, addressData) => {
|
(err, addressData) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -1246,15 +1243,15 @@ class UserHandler {
|
||||||
let update =
|
let update =
|
||||||
!userData.enabled2fa || typeof userData.enabled2fa === 'boolean'
|
!userData.enabled2fa || typeof userData.enabled2fa === 'boolean'
|
||||||
? {
|
? {
|
||||||
$set: {
|
$set: {
|
||||||
enabled2fa: ['totp']
|
enabled2fa: ['totp']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
$addToSet: {
|
$addToSet: {
|
||||||
enabled2fa: 'totp'
|
enabled2fa: 'totp'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// token was valid, update user settings
|
// token was valid, update user settings
|
||||||
return this.users.collection('users').findOneAndUpdate(
|
return this.users.collection('users').findOneAndUpdate(
|
||||||
|
@ -1328,19 +1325,19 @@ class UserHandler {
|
||||||
let update =
|
let update =
|
||||||
!userData.enabled2fa || typeof userData.enabled2fa === 'boolean'
|
!userData.enabled2fa || typeof userData.enabled2fa === 'boolean'
|
||||||
? {
|
? {
|
||||||
$set: {
|
$set: {
|
||||||
enabled2fa: [],
|
enabled2fa: [],
|
||||||
seed: ''
|
seed: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
$pull: {
|
$pull: {
|
||||||
enabled2fa: 'totp'
|
enabled2fa: 'totp'
|
||||||
},
|
},
|
||||||
$set: {
|
$set: {
|
||||||
seed: ''
|
seed: ''
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.users.collection('users').findOneAndUpdate(
|
return this.users.collection('users').findOneAndUpdate(
|
||||||
{
|
{
|
||||||
|
@ -1627,25 +1624,25 @@ class UserHandler {
|
||||||
let update =
|
let update =
|
||||||
!userData.enabled2fa || typeof userData.enabled2fa === 'boolean'
|
!userData.enabled2fa || typeof userData.enabled2fa === 'boolean'
|
||||||
? {
|
? {
|
||||||
$set: {
|
$set: {
|
||||||
enabled2fa: ['u2f'],
|
enabled2fa: ['u2f'],
|
||||||
u2fKeyHandle: result.keyHandle,
|
u2fKeyHandle: result.keyHandle,
|
||||||
u2fPubKey: result.publicKey,
|
u2fPubKey: result.publicKey,
|
||||||
u2fCert: result.certificate,
|
u2fCert: result.certificate,
|
||||||
u2fDate: curDate
|
u2fDate: curDate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
$addToSet: {
|
$addToSet: {
|
||||||
enabled2fa: 'u2f'
|
enabled2fa: 'u2f'
|
||||||
},
|
},
|
||||||
$set: {
|
$set: {
|
||||||
u2fKeyHandle: result.keyHandle,
|
u2fKeyHandle: result.keyHandle,
|
||||||
u2fPubKey: result.publicKey,
|
u2fPubKey: result.publicKey,
|
||||||
u2fCert: result.certificate,
|
u2fCert: result.certificate,
|
||||||
u2fDate: curDate
|
u2fDate: curDate
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.users.collection('users').findOneAndUpdate(
|
return this.users.collection('users').findOneAndUpdate(
|
||||||
{
|
{
|
||||||
|
@ -1719,23 +1716,23 @@ class UserHandler {
|
||||||
let update =
|
let update =
|
||||||
!userData.enabled2fa || typeof userData.enabled2fa === 'boolean'
|
!userData.enabled2fa || typeof userData.enabled2fa === 'boolean'
|
||||||
? {
|
? {
|
||||||
$set: {
|
$set: {
|
||||||
enabled2fa: [],
|
enabled2fa: [],
|
||||||
u2fKeyHandle: '',
|
u2fKeyHandle: '',
|
||||||
u2fPubKey: '',
|
u2fPubKey: '',
|
||||||
u2fCert: ''
|
u2fCert: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
$pull: {
|
$pull: {
|
||||||
enabled2fa: 'u2f'
|
enabled2fa: 'u2f'
|
||||||
},
|
},
|
||||||
$set: {
|
$set: {
|
||||||
u2fKeyHandle: '',
|
u2fKeyHandle: '',
|
||||||
u2fPubKey: '',
|
u2fPubKey: '',
|
||||||
u2fCert: ''
|
u2fCert: ''
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.users.collection('users').findOneAndUpdate(
|
return this.users.collection('users').findOneAndUpdate(
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "wildduck",
|
"name": "wildduck",
|
||||||
"version": "1.0.106",
|
"version": "1.0.107",
|
||||||
"description": "IMAP server built with Node.js and MongoDB",
|
"description": "IMAP server built with Node.js and MongoDB",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -31,7 +31,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"addressparser": "1.0.1",
|
"addressparser": "1.0.1",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
"bugsnag": "2.0.1",
|
"bugsnag": "2.1.1",
|
||||||
"generate-password": "1.3.0",
|
"generate-password": "1.3.0",
|
||||||
"he": "1.1.1",
|
"he": "1.1.1",
|
||||||
"html-to-text": "3.3.0",
|
"html-to-text": "3.3.0",
|
||||||
|
@ -40,7 +40,7 @@
|
||||||
"iconv-lite": "0.4.19",
|
"iconv-lite": "0.4.19",
|
||||||
"ioredfour": "1.0.2-ioredis",
|
"ioredfour": "1.0.2-ioredis",
|
||||||
"ioredis": "3.2.2",
|
"ioredis": "3.2.2",
|
||||||
"joi": "13.0.2",
|
"joi": "13.1.0",
|
||||||
"js-yaml": "3.10.0",
|
"js-yaml": "3.10.0",
|
||||||
"key-fingerprint": "1.1.0",
|
"key-fingerprint": "1.1.0",
|
||||||
"libbase64": "1.0.2",
|
"libbase64": "1.0.2",
|
||||||
|
|
Loading…
Add table
Reference in a new issue