Merge pull request #1009 from omnivore-app/fix/unsubscribe

fix not getting unsubscribe email address from beehiv newsletter by decoding the list-unsubscribe header
This commit is contained in:
Hongbo Wu
2022-07-27 09:41:42 +08:00
committed by GitHub
5 changed files with 45 additions and 19 deletions

View File

@ -23,6 +23,7 @@
"@types/addressparser": "^1.0.1",
"@types/json-bigint": "^1.0.1",
"@types/node": "^14.11.2",
"@types/rfc2047": "^2.0.1",
"eslint-plugin-prettier": "^4.0.0"
},
"dependencies": {
@ -34,6 +35,7 @@
"axios": "^0.27.2",
"jsonwebtoken": "^8.5.1",
"parse-headers": "^2.0.4",
"parse-multipart-data": "^1.2.1"
"parse-multipart-data": "^1.2.1",
"rfc2047": "^4.0.1"
}
}

View File

@ -65,29 +65,24 @@ export const inboundEmailHandler = Sentry.GCPFunction.wrapHttpFunction(
console.log('headers: ', headers)
try {
const from = parsed.from.toString()
const from = parsed.from
const subject = parsed.subject
const html = parsed.html
const text = parsed.text
const forwardedAddress = headers['x-forwarded-to']
const recipientAddress = forwardedAddress
? forwardedAddress.toString()
: parsed.to
const postHeader = headers['list-post']
? headers['list-post'].toString()
: ''
const unSubHeader = headers['list-unsubscribe']
? headers['list-unsubscribe'].toString()
: ''
const recipientAddress = forwardedAddress?.toString() || parsed.to
const postHeader = headers['list-post']?.toString()
const unSubHeader = headers['list-unsubscribe'].toString()
// check if it is a forwarding confirmation email or newsletter
const newsletterHandler = getNewsletterHandler(
postHeader,
from,
unSubHeader
)
try {
// check if it is a forwarding confirmation email or newsletter
const newsletterHandler = getNewsletterHandler(
postHeader,
from,
unSubHeader
)
if (newsletterHandler) {
console.log('handleNewsletter', from, recipientAddress)
await newsletterHandler.handleNewsletter(

View File

@ -1,6 +1,7 @@
import { PubSub } from '@google-cloud/pubsub'
import { v4 as uuidv4 } from 'uuid'
import addressparser from 'addressparser'
import rfc2047 from 'rfc2047'
interface Unsubscribe {
mailTo?: string
@ -20,9 +21,10 @@ const UNSUBSCRIBE_MAIL_TO_PATTERN = /<mailto:([^>]*)>/
export const parseUnsubscribe = (unSubHeader: string): Unsubscribe => {
// parse list-unsubscribe header
// e.g. List-Unsubscribe: <https://omnivore.com/unsub>, <mailto:unsub@omnivore.com>
const decoded = rfc2047.decode(unSubHeader)
return {
mailTo: unSubHeader.match(UNSUBSCRIBE_MAIL_TO_PATTERN)?.[1],
httpUrl: unSubHeader.match(UNSUBSCRIBE_HTTP_URL_PATTERN)?.[1],
mailTo: decoded.match(UNSUBSCRIBE_MAIL_TO_PATTERN)?.[1],
httpUrl: decoded.match(UNSUBSCRIBE_HTTP_URL_PATTERN)?.[1],
}
}

View File

@ -167,5 +167,15 @@ describe('Newsletter email test', () => {
expect(parseUnsubscribe(header).httpUrl).to.equal(httpUrl)
})
context('when unsubscribe header rfc2047 encoded', () => {
it('returns mail to address if exists', () => {
const header = `=?us-ascii?Q?=3Cmailto=3A654e9594-184c-4884-8e02-e6e58a3a6871+87e39b3d-c3ca-4be?= =?us-ascii?Q?b-ba4d-977cc2ba61e7+067a353f-f775-4f2c-?= =?us-ascii?Q?a5cc-978df38deeca=40unsub=2Ebeehiiv=2Ecom=3E=2C?= =?us-ascii?Q?_=3Chttps=3A=2F=2Fwww=2Emilkroad=2Ecom=2Fsubscribe=2F87e39b3d-c3ca-4beb-ba4d-97?= =?us-ascii?Q?7cc2ba61e7=2Fmanage=3Fpost=5Fid=3D067a353f-f775?= =?us-ascii?Q?-4f2c-a5cc-978df38deeca=3E?=',`
expect(parseUnsubscribe(header).mailTo).to.equal(
'654e9594-184c-4884-8e02-e6e58a3a6871+87e39b3d-c3ca-4beb-ba4d-977cc2ba61e7+067a353f-f775-4f2c-a5cc-978df38deeca@unsub.beehiiv.com'
)
})
})
})
})

View File

@ -7947,6 +7947,11 @@
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065"
integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==
"@types/rfc2047@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/rfc2047/-/rfc2047-2.0.1.tgz#42eb43c161a52fd404a935f032360880ec84d400"
integrity sha512-slgtykv+XXME7EperkdqfdBBUGcs28ru+a21BK0zOQY4IoxE7tEqvIcvAFAz5DJVxyOmoAUXo30Oxpm3KS+TBQ==
"@types/sanitize-html@^1.27.1":
version "1.27.2"
resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-1.27.2.tgz#f7bf16ca4b1408278f97ae737f0377a08a10b35c"
@ -15257,6 +15262,11 @@ iconv-lite@0.4.24, iconv-lite@^0.4.24:
dependencies:
safer-buffer ">= 2.1.2 < 3"
iconv-lite@0.4.5:
version "0.4.5"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.5.tgz#9c574b70c30d615859f2064d2be4335ad6b1a8d6"
integrity sha512-LQ4GtDkFagYaac8u4rE73zWu7h0OUUmR0qVBOgzLyFSoJhoDG2xV9PZJWWyVVcYha/9/RZzQHUinFMbNKiOoAA==
iconv-lite@^0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01"
@ -21872,6 +21882,13 @@ reusify@^1.0.4:
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
rfc2047@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/rfc2047/-/rfc2047-4.0.1.tgz#4e1cc217654728f09bc818b53a67d1d9c9d5067c"
integrity sha512-x5zHBAZtSSZDuBNAqGEAVpsQFV+YUluIkMWVaYRMEeGoLPxNVMmg67TxRnXwmRmCB7QaneyrkWXeKqbjfcK6RA==
dependencies:
iconv-lite "0.4.5"
rfdc@^1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b"