haraka-plugin-headers
Advanced tools
Comparing version
# 1.0.2 - 2020-08-22 | ||
- additional test | ||
- updated test to newer JS standards | ||
- don't call tests that aren't enabled in config (performance) | ||
# 1.0.0 - 2020-07-28 | ||
@@ -3,0 +9,0 @@ |
37
index.js
@@ -16,14 +16,16 @@ // validate message headers and some fields | ||
} | ||
this.register_hook('data_post', 'duplicate_singular') | ||
this.register_hook('data_post', 'missing_required') | ||
this.register_hook('data_post', 'invalid_date') | ||
this.register_hook('data_post', 'invalid_return_path') | ||
this.register_hook('data_post', 'user_agent') | ||
this.register_hook('data_post', 'direct_to_mx') | ||
if (this.cfg.check.duplicate_singular) this.register_hook('data_post', 'duplicate_singular') | ||
if (this.cfg.check.missing_required) this.register_hook('data_post', 'missing_required') | ||
if (this.cfg.check.invalid_return_path) this.register_hook('data_post', 'invalid_return_path') | ||
if (this.cfg.check.invalid_date) this.register_hook('data_post', 'invalid_date') | ||
if (this.cfg.check.user_agent) this.register_hook('data_post', 'user_agent') | ||
if (this.cfg.check.direct_to_mx) this.register_hook('data_post', 'direct_to_mx') | ||
if (this.addrparser) { | ||
this.register_hook('data_post', 'from_match') | ||
this.register_hook('data_post', 'delivered_to') | ||
if (this.cfg.check.from_match) this.register_hook('data_post', 'from_match') | ||
if (this.cfg.check.delivered_to) this.register_hook('data_post', 'delivered_to') | ||
} | ||
this.register_hook('data_post', 'mailing_list') | ||
this.register_hook('data_post', 'from_phish') | ||
if (this.cfg.check.mailing_list) this.register_hook('data_post', 'mailing_list') | ||
if (this.cfg.check.from_phish) this.register_hook('data_post', 'from_phish') | ||
} | ||
@@ -60,2 +62,3 @@ | ||
} | ||
// console.log(phish_targets) | ||
} | ||
@@ -65,3 +68,2 @@ | ||
const plugin = this; | ||
if (!plugin.cfg.check.duplicate_singular) return next(); | ||
@@ -100,3 +102,2 @@ // RFC 5322 Section 3.6, Headers that MUST be unique if present | ||
const plugin = this; | ||
if (!plugin.cfg.check.missing_required) return next(); | ||
@@ -129,3 +130,2 @@ // Enforce RFC 5322 Section 3.6, Headers that MUST be present | ||
const plugin = this; | ||
if (!plugin.cfg.check.invalid_return_path) return next(); | ||
@@ -163,6 +163,4 @@ // Tests for Return-Path headers that shouldn't be present | ||
const plugin = this; | ||
if (!plugin.cfg.check.invalid_date) return next(); | ||
// Assure Date header value is [somewhat] sane | ||
let msg_date = connection.transaction.header.get_all('Date'); | ||
@@ -215,3 +213,2 @@ if (!msg_date || msg_date.length === 0) return next(); | ||
const plugin = this; | ||
if (!plugin.cfg.check.user_agent) return next(); | ||
@@ -250,3 +247,2 @@ if (!connection.transaction) return next(); | ||
const plugin = this; | ||
if (!plugin.cfg.check.direct_to_mx) return next(); | ||
@@ -283,3 +279,2 @@ if (!connection.transaction) return next(); | ||
const plugin = this; | ||
if (!plugin.cfg.check.from_match) return next(); | ||
@@ -349,3 +344,2 @@ // see if the header From matches the envelope FROM. There are valid | ||
const plugin = this; | ||
if (!plugin.cfg.check.delivered_to) return next(); | ||
@@ -371,3 +365,2 @@ const txn = connection.transaction; | ||
const plugin = this; | ||
if (!plugin.cfg.check.mailing_list) return next(); | ||
if (!connection.transaction) return next(); | ||
@@ -438,3 +431,2 @@ | ||
const plugin = this; | ||
if (!plugin.cfg.check.from_phish) return next() | ||
if (!connection.transaction) return next(); | ||
@@ -450,3 +442,4 @@ | ||
for (const addr of phish_targets) { | ||
if (addr.test(hdr_from) && !exports.has_auth_match(addr, connection)) { | ||
if (!addr.test(hdr_from)) continue; // not a sender match | ||
if (!exports.has_auth_match(addr, connection)) { | ||
@@ -453,0 +446,0 @@ connection.transaction.results.add(plugin, {fail: `from_phish(${hdr_from}`}) |
{ | ||
"name": "haraka-plugin-headers", | ||
"version": "1.0.1", | ||
"description": "Haraka plugin that frobnicates email connections", | ||
"version": "1.0.2", | ||
"description": "Haraka plugin that performs tests on email headers", | ||
"main": "index.js", | ||
@@ -6,0 +6,0 @@ "scripts": { |
[![Build Status][ci-img]][ci-url] | ||
[![Windows Build Status][ci-win-img]][ci-win-url] | ||
[![Code Climate][clim-img]][clim-url] | ||
@@ -129,3 +130,2 @@ [![NPM][npm-img]][npm-url] | ||
[![Code Climate][clim-img]][clim-url] | ||
<!-- leave these buried at the bottom of the document --> | ||
@@ -132,0 +132,0 @@ [ci-img]: https://github.com/haraka/haraka-plugin-headers/workflows/Plugin%20Tests/badge.svg |
@@ -31,3 +31,3 @@ | ||
assert.ok(this.plugin) | ||
done(); | ||
done() | ||
}) | ||
@@ -54,6 +54,5 @@ }) | ||
it('none', function (done) { | ||
const outer = this; | ||
this.plugin.cfg.check.user_agent=true; | ||
this.plugin.user_agent(function () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
this.plugin.user_agent(() => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.equal(/UA/.test(r.fail), true); | ||
@@ -66,7 +65,6 @@ assert.equal(/UA/.test(r.pass), false); | ||
it('thunderbird', function (done) { | ||
const outer = this | ||
outer.plugin.cfg.check.user_agent=true | ||
outer.connection.transaction.header.add_end('User-Agent', 'Thunderbird') | ||
outer.plugin.user_agent(function () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers') | ||
this.plugin.cfg.check.user_agent=true | ||
this.connection.transaction.header.add_end('User-Agent', 'Thunderbird') | ||
this.plugin.user_agent( () => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers') | ||
// console.log(r) | ||
@@ -76,15 +74,14 @@ assert.equal(true, /UA/.test(r.pass)) | ||
done() | ||
}, outer.connection) | ||
}, this.connection) | ||
}) | ||
it('X-mailer', function (done) { | ||
const outer = this | ||
outer.plugin.cfg.check.user_agent=true | ||
outer.connection.transaction.header.add_end('X-Mailer', 'Apple Mail'); | ||
outer.plugin.user_agent(function () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
this.plugin.cfg.check.user_agent=true | ||
this.connection.transaction.header.add_end('X-Mailer', 'Apple Mail'); | ||
this.plugin.user_agent( () => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.equal(true, /UA/.test(r.pass)) | ||
assert.equal(false, /UA/.test(r.fail)) | ||
done() | ||
}, outer.connection); | ||
}, this.connection); | ||
}) | ||
@@ -97,6 +94,5 @@ }) | ||
this.connection.notes.auth_user = 'test@example.com'; | ||
const outer = this; | ||
this.plugin.cfg.check.direct_to_mx=true; | ||
this.plugin.direct_to_mx(function () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
this.plugin.direct_to_mx( () => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.equal(true, /^direct-to-mx/.test(r.skip)); | ||
@@ -110,29 +106,26 @@ assert.equal(false, /^direct-to-mx/.test(r.pass)); | ||
it('received 0', function (done) { | ||
const outer = this; | ||
this.plugin.cfg.check.direct_to_mx=true; | ||
this.plugin.direct_to_mx(function () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
this.plugin.direct_to_mx(() => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.equal(true, /^direct-to-mx/.test(r.fail)); | ||
assert.equal(false, /^direct-to-mx/.test(r.pass)); | ||
assert.equal(false, /^direct-to-mx/.test(r.skip)); | ||
done() | ||
}, this.connection); | ||
done() | ||
}) | ||
it('received 1', function (done) { | ||
const outer = this; | ||
this.plugin.cfg.check.direct_to_mx=true; | ||
this.connection.transaction.header.add_end('Received', 'blah'); | ||
this.plugin.direct_to_mx(function () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
this.plugin.direct_to_mx(() => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.equal(true, /^direct-to-mx/.test(r.fail)); | ||
done() | ||
}, this.connection); | ||
done() | ||
}) | ||
it('received 2', function (done) { | ||
const outer = this; | ||
this.plugin.cfg.check.direct_to_mx=true; | ||
this.connection.transaction.header.add_end('Received', 'blah1'); | ||
this.connection.transaction.header.add_end('Received', 'blah2'); | ||
this.plugin.direct_to_mx(function () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
this.plugin.direct_to_mx(() => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.equal(true, /^direct-to-mx/.test(r.pass)); | ||
@@ -149,8 +142,7 @@ assert.equal(false, /^direct-to-mx/.test(r.fail)); | ||
it('match bare', function (done) { | ||
const outer = this; | ||
this.plugin.cfg.check.from_match=true; | ||
this.connection.transaction.mail_from = new Address('<test@example.com>'); | ||
this.connection.transaction.header.add_end('From', 'test@example.com'); | ||
this.plugin.from_match(function () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
this.plugin.from_match(() => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.notEqual(-1, r.pass.indexOf('from_match')); | ||
@@ -161,19 +153,17 @@ done() | ||
it('match typical', function (done) { | ||
const outer = this; | ||
this.plugin.cfg.check.from_match=true; | ||
this.connection.transaction.mail_from = new Address('<test@example.com>'); | ||
this.connection.transaction.header.add_end('From', '"Test User" <test@example.com>'); | ||
this.plugin.from_match(function () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
this.plugin.from_match(() => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.notEqual(-1, r.pass.indexOf('from_match')); | ||
done() | ||
}, outer.connection); | ||
}, this.connection); | ||
}) | ||
it('match unquoted', function (done) { | ||
const outer = this; | ||
this.plugin.cfg.check.from_match=true; | ||
this.connection.transaction.mail_from = new Address('<test@example.com>'); | ||
this.connection.transaction.header.add_end('From', 'Test User <test@example.com>'); | ||
this.plugin.from_match(function () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
this.plugin.from_match(() => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.notEqual(-1, r.pass.indexOf('from_match')); | ||
@@ -185,3 +175,2 @@ done() | ||
it('mismatch', function (done) { | ||
const outer = this; | ||
this.plugin.cfg.check.from_match=true; | ||
@@ -191,4 +180,4 @@ this.connection.transaction.mail_from = new Address('<test@example.com>'); | ||
// console.log(this.connection.transaction.results); | ||
this.plugin.from_match(function () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
this.plugin.from_match(() => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.equal(true, /^from_match/.test(r.fail)); | ||
@@ -203,79 +192,65 @@ done() | ||
it('ezmlm true', function (done) { | ||
const outer = this; | ||
function next_cb () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.equal(true, /ezmlm/.test(r.pass)); | ||
assert.equal(0, r.fail.length); | ||
} | ||
this.plugin.cfg.check.mailing_list=true; | ||
this.connection.transaction.header.add_end('Mailing-List', "blah blah: run by ezmlm"); | ||
this.plugin.mailing_list(next_cb, this.connection); | ||
done() | ||
this.plugin.mailing_list(() => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.equal(true, /ezmlm/.test(r.pass)) | ||
assert.equal(0, r.fail.length) | ||
done() | ||
}, this.connection); | ||
}) | ||
it('ezmlm false', function (done) { | ||
const outer = this; | ||
function next_cb () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
this.plugin.cfg.check.mailing_list=true; | ||
this.connection.transaction.header.add_end('Mailing-List', "blah blah random header tokens"); | ||
this.plugin.mailing_list(() => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.equal(r.pass.length, 0); | ||
assert.equal(true, /not/.test(r.msg)); | ||
} | ||
this.plugin.cfg.check.mailing_list=true; | ||
this.connection.transaction.header.add_end('Mailing-List', "blah blah random header tokens"); | ||
this.plugin.mailing_list(next_cb, this.connection); | ||
done() | ||
done() | ||
}, this.connection); | ||
}) | ||
it('yahoogroups', function (done) { | ||
const outer = this; | ||
function next_cb () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
this.plugin.cfg.check.mailing_list=true; | ||
this.connection.transaction.header.add_end('Mailing-List', "blah blah such-and-such@yahoogroups.com email list"); | ||
this.plugin.mailing_list(() => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.equal(true, /yahoogroups/.test(r.pass)); | ||
} | ||
this.plugin.cfg.check.mailing_list=true; | ||
outer.connection.transaction.header.add_end('Mailing-List', "blah blah such-and-such@yahoogroups.com email list"); | ||
this.plugin.mailing_list(next_cb, this.connection); | ||
done() | ||
done() | ||
}, this.connection); | ||
}) | ||
it('majordomo', function (done) { | ||
const outer = this; | ||
function next_cb () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
this.plugin.cfg.check.mailing_list=true; | ||
this.connection.transaction.header.add_end('Sender', "owner-blah-blah whatcha"); | ||
this.plugin.mailing_list(() => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.equal(true, /majordomo/.test(r.pass)); | ||
} | ||
this.plugin.cfg.check.mailing_list=true; | ||
outer.connection.transaction.header.add_end('Sender', "owner-blah-blah whatcha"); | ||
outer.plugin.mailing_list(next_cb, outer.connection); | ||
done() | ||
done() | ||
}, this.connection); | ||
}) | ||
it('mailman', function (done) { | ||
const outer = this; | ||
outer.connection.transaction.header.add_end('X-Mailman-Version', "owner-blah-blah whatcha"); | ||
function next_cb () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
this.connection.transaction.header.add_end('X-Mailman-Version', "owner-blah-blah whatcha"); | ||
this.plugin.cfg.check.mailing_list=true; | ||
this.plugin.mailing_list(() => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.equal(true, /mailman/.test(r.pass)); | ||
} | ||
this.plugin.cfg.check.mailing_list=true; | ||
this.plugin.mailing_list(next_cb, this.connection); | ||
done() | ||
done() | ||
}, this.connection); | ||
}) | ||
it('majordomo v', function (done) { | ||
const outer = this; | ||
function next_cb () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.equal(true, /majordomo/.test(r.pass)); | ||
} | ||
this.plugin.cfg.check.mailing_list=true; | ||
this.connection.transaction.header.add_end('X-Majordomo-Version', "owner-blah-blah whatcha"); | ||
this.plugin.mailing_list(next_cb, this.connection); | ||
done() | ||
this.plugin.mailing_list(() => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.equal(true, /majordomo/.test(r.pass)); | ||
done() | ||
}, this.connection); | ||
}) | ||
it('google groups', function (done) { | ||
const outer = this; | ||
function next_cb () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.equal(true, /googlegroups/.test(r.pass)); | ||
} | ||
this.plugin.cfg.check.mailing_list=true; | ||
this.connection.transaction.header.add_end('X-Google-Loop', "blah-blah whatcha"); | ||
this.plugin.mailing_list(next_cb, this.connection); | ||
done() | ||
this.connection.transaction.header.add_end('X-Google-Loop', "blah-blah whatcha") | ||
this.plugin.mailing_list(() => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers') | ||
assert.equal(true, /googlegroups/.test(r.pass)) | ||
done() | ||
}, this.connection); | ||
}) | ||
@@ -287,54 +262,49 @@ }) | ||
it('disabled', function (done) { | ||
const next_cb = function (res, msg) { | ||
this.plugin.cfg.check.delivered_to=false; | ||
this.plugin.delivered_to(function (res, msg) { | ||
assert.equal(undefined, res); | ||
assert.equal(undefined, msg); | ||
done() | ||
}.bind(this); | ||
this.plugin.cfg.check.delivered_to=false; | ||
this.plugin.delivered_to(next_cb, this.connection); | ||
}, this.connection); | ||
}) | ||
it('header not present', function (done) { | ||
const next_cb = function (res, msg) { | ||
this.plugin.cfg.check.delivered_to=true; | ||
this.plugin.delivered_to(function (res, msg) { | ||
assert.equal(undefined, res); | ||
assert.equal(undefined, msg); | ||
done() | ||
}.bind(this); | ||
this.plugin.cfg.check.delivered_to=true; | ||
this.plugin.delivered_to(next_cb, this.connection); | ||
}, this.connection); | ||
}) | ||
it('no recipient match', function (done) { | ||
const next_cb = function (res, msg) { | ||
this.plugin.cfg.check.delivered_to=true; | ||
// this.connection.transaction.mail_from = new Address('<test@example.com>'); | ||
this.connection.transaction.header.add_end('Delivered-To', "user@example.com"); | ||
this.plugin.delivered_to(function (res, msg) { | ||
assert.equal(undefined, res); | ||
assert.equal(undefined, msg); | ||
done() | ||
}.bind(this); | ||
}, this.connection); | ||
}) | ||
it('recipient match', function (done) { | ||
this.plugin.cfg.check.delivered_to=true; | ||
// this.connection.transaction.mail_from = new Address('<test@example.com>'); | ||
this.connection.transaction.header.add_end('Delivered-To', "user@example.com"); | ||
this.plugin.delivered_to(next_cb, this.connection); | ||
}) | ||
it('recipient match', function (done) { | ||
const next_cb = function (res, msg) { | ||
this.connection.transaction.rcpt_to.push(new Address('user@example.com')); | ||
this.plugin.delivered_to(function (res, msg) { | ||
assert.equal(DENY, res); | ||
assert.equal('Invalid Delivered-To header content', msg); | ||
done() | ||
}.bind(this); | ||
}, this.connection); | ||
}) | ||
it('recipient match, reject disabled', function (done) { | ||
this.plugin.cfg.check.delivered_to=true; | ||
this.plugin.cfg.reject.delivered_to=false; | ||
// this.connection.transaction.mail_from = new Address('<test@example.com>'); | ||
this.connection.transaction.header.add_end('Delivered-To', "user@example.com"); | ||
this.connection.transaction.rcpt_to.push(new Address('user@example.com')); | ||
this.plugin.delivered_to(next_cb, this.connection); | ||
}) | ||
it('recipient match, reject disabled', function (done) { | ||
const next_cb = function (res, msg) { | ||
this.plugin.delivered_to(function (res, msg) { | ||
assert.equal(undefined, res); | ||
assert.equal(undefined, msg); | ||
done() | ||
}.bind(this); | ||
this.plugin.cfg.check.delivered_to=true; | ||
this.plugin.cfg.reject.delivered_to=false; | ||
// this.connection.transaction.mail_from = new Address('<test@example.com>'); | ||
this.connection.transaction.header.add_end('Delivered-To', "user@example.com"); | ||
this.connection.transaction.rcpt_to.push(new Address('user@example.com')); | ||
this.plugin.delivered_to(next_cb, this.connection); | ||
}, this.connection); | ||
}) | ||
@@ -376,21 +346,17 @@ }) | ||
it('passes typical', function (done) { | ||
const outer = this; | ||
this.plugin.cfg.check.from_phish=true; | ||
it('passes mfrom match', function (done) { | ||
this.connection.transaction.mail_from = new Address('<test@example.com>'); | ||
this.connection.transaction.header.add_end('From', '"Test User" <test@example.com>'); | ||
this.plugin.from_phish(function () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
this.plugin.from_phish(() => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
assert.notEqual(r.pass.indexOf('from_phish'), -1); | ||
done() | ||
}, outer.connection); | ||
}, this.connection); | ||
}) | ||
it('fails when amazon.com is in the From header and not envelope sender', function (done) { | ||
const outer = this; | ||
this.plugin.cfg.check.from_phish=true; | ||
this.connection.transaction.mail_from = new Address('<test@example.com>'); | ||
this.connection.transaction.header.add_end('From', 'Amazon.com <test@ayodongbanyak08.com>'); | ||
this.plugin.from_phish(function () { | ||
const r = outer.connection.transaction.results.get('haraka-plugin-headers'); | ||
this.plugin.from_phish(() => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
// console.log(r) | ||
@@ -401,2 +367,17 @@ assert.equal(r.fail.length, 1); | ||
}) | ||
it('passes dkim_verify match', function (done) { | ||
// this.plugin.cfg.check.from_phish=true; | ||
this.connection.transaction.mail_from = new Address('<01010173e2d51ce9-fda858da-b513-412f-b03b-6db12012417e-000000@us-west-2.amazonses.com>'); | ||
this.connection.transaction.header.add_end('From', 'Amazon Business <no-reply@business.amazon.com>'); | ||
this.connection.transaction.results.add({name: 'dkim_verify'}, { pass: ['business.amazon.com','amazonses.com'] }) | ||
this.plugin.from_phish(() => { | ||
const r = this.connection.transaction.results.get('haraka-plugin-headers'); | ||
// console.log(r) | ||
assert.deepEqual(r.fail, []) | ||
assert.deepEqual(r.pass, ['from_phish']); | ||
done() | ||
}, this.connection); | ||
}) | ||
}) |
Sorry, the diff of this file is not supported yet
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
41382
0722
-3.86%