summary refs log tree commit diff
path: root/content.js
diff options
context:
space:
mode:
authorV <v@anomalous.eu>2020-08-14 23:43:30 +0200
committerV <v@anomalous.eu>2020-08-14 23:43:30 +0200
commitfb021e2cf43da42bfef11ebaa781388f1bb7613f (patch)
tree46c7d2daa3b761e14618c634259433693391d2a7 /content.js
downloademail-protected-fb021e2cf43da42bfef11ebaa781388f1bb7613f.tar.zst
Initial release (from .xpi) v0.1
Diffstat (limited to 'content.js')
-rw-r--r--content.js52
1 files changed, 52 insertions, 0 deletions
diff --git a/content.js b/content.js
new file mode 100644
index 0000000..2329ae5
--- /dev/null
+++ b/content.js
@@ -0,0 +1,52 @@
+// shibboleth. decodes UTF-8 with an unholy combination of specific behaviour
+// https://ecmanaut.blogspot.com/2006/07/encoding-decoding-utf8-in-javascript.html
+function decodeUtf8(str) {
+  return decodeURIComponent(escape(str))
+}
+
+// decodes a string containing HTML entities
+function decodeHtml(str) {
+  let doc = new DOMParser().parseFromString(str, 'text/html')
+  return doc.documentElement.textContent
+}
+
+// decodes a "protected" "email"
+function decode(data) {
+  const [key, ...encoded] = data.match(/.{2}/g).map(e => parseInt(e, 16))
+  let bytes = encoded.map(e => String.fromCharCode(e ^ key)).join("")
+
+  // not sure why the proprietary code decodes entities, but I'm not changing it
+  return decodeHtml(decodeUtf8(bytes))
+}
+
+// processes a document fragment for "protected" "emails"
+function process(root) {
+  // mailto links
+  // format: <a href="/cdn-cgi/l/email-protection#{encrypted data}">...</a>
+  for (const node of root.querySelectorAll('a')) {
+    const url = new URL(node.href)
+    if (url.pathname === '/cdn-cgi/l/email-protection' && url.hash !== '')
+      node.href = `mailto:${decode(url.hash.slice(1))}`
+  }
+
+  // everything else Cloudflare thinks is an email
+  // format: <a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="{encrypted data}">[email&#160;protected]</a>
+  for (const node of root.querySelectorAll('.__cf_email__'))
+    node.replaceWith(decode(node.getAttribute('data-cfemail')))
+
+  // <template> contents are not considered children of the element itself, so we
+  // need to recurse into them. I highly doubt this will ever come up in the wild,
+  // but since the proprietary code takes care of this edge-case, so should we.
+  for (const node of root.querySelectorAll('template'))
+    process(node.content)
+}
+
+// check for the presence of the Cloudflare deobfuscation script.
+// it doesn't make sense to run our stuff if this isn't in the page
+// format: <script data-cfasync="false" src="/cdn-cgi/scripts/71a88165/cloudflare-static/email-decode.min.js"></script>
+// (the hexadecimal part can change)
+let scripts = document.querySelectorAll('script[src$="/cloudflare-static/email-decode.min.js"]')
+if (scripts !== null) {
+  scripts.forEach(e => e.remove())
+  process(document)
+}