Module: AlEmailProtect
- Defined in:
- lib/al_email_protect.rb,
lib/al_email_protect/version.rb
Overview
Keeps published email addresses away from harvesters.
The scraper-facing half is deliberately server-side: when the feature is on,
no mailto: string and no user@host string is present in the HTML at all.
Address harvesters read markup, not JavaScript, so an approach that ships the
plaintext and merely rewrites it on DOMContentLoaded protects nobody. The
address is emitted as two halves and only rejoined in the browser.
Defined Under Namespace
Modules: Filters Classes: AssetsGenerator, LinkTag, PluginStaticFile, ScriptsTag, StylesTag
Constant Summary collapse
- PLUGIN_ROOT =
File.("..", __dir__)
- LIB_ROOT =
Jekyll writes a StaticFile to
/ / , where is relative to the base below. That base must be lib/, not the gem root, or assets land at /lib/assets/... while every tag in this file points at /assets/... . __dir__- ASSETS_ROOT =
File.join(LIB_ROOT, "assets")
- ANCHOR =
cv.email | al_email_obfuscate } Rewrites every
mailto:anchor in a fragment of already-rendered HTML.This exists because the markup that needs protecting is frequently not ours. al-folio's social links come from the third-party
jekyll-socialsgem, which owns the whole block and emitsmailto:%sfor the email entry — there is no hook to render that one link differently. Capturing its output and rewriting it afterwards is the only interception point, and it works regardless of which gem produced the markup.Deliberately narrow: it matches an whose href begins with
mailto:and replaces the whole element. It does not attempt to parse HTML generally. %r{<a\b([^>]*?)href=(["'])mailto:([^"']+)\2([^>]*)>(.*?)</a>}im.freeze
- VERSION =
"1.0.1"
Class Method Summary collapse
-
.asset_entries ⇒ Object
[relative_dir, filename] for everything this gem publishes.
-
.decode_target(value) ⇒ Object
mailto:targets are commonly percent-encoded (jekyll-email-protect'sencode_emaildoes exactly that), so decode before splitting or the local and domain halves come out as escape sequences. -
.domain_markup(domain) ⇒ Object
Builds "example [dot] com" as alternating styled spans.
- .enabled?(site) ⇒ Boolean
- .escape(value) ⇒ Object
-
.obfuscate_text(value) ⇒ Object
"someone [at] example [dot] com", for places that show an address as text rather than as a link (CV contact blocks, for instance).
- .rewrite_html(html) ⇒ Object
-
.split_address(value) ⇒ Object
Splits "someone@example.ac.uk" into ["someone", "example.ac.uk"].
Class Method Details
.asset_entries ⇒ Object
[relative_dir, filename] for everything this gem publishes. Exposed so the destination path can be asserted without booting a full Jekyll site.
73 74 75 76 77 |
# File 'lib/al_email_protect.rb', line 73 def asset_entries Dir.glob(File.join(ASSETS_ROOT, "**", "*")).sort.reject { |p| File.directory?(p) }.map do |source_path| [File.dirname(source_path).sub("#{LIB_ROOT}/", ""), File.basename(source_path)] end end |
.decode_target(value) ⇒ Object
mailto: targets are commonly percent-encoded (jekyll-email-protect's
encode_email does exactly that), so decode before splitting or the local
and domain halves come out as escape sequences.
167 168 169 170 171 |
# File 'lib/al_email_protect.rb', line 167 def decode_target(value) CGI.unescape(value.to_s.split("?").first.to_s) rescue StandardError value.to_s end |
.domain_markup(domain) ⇒ Object
Builds "example [dot] com" as alternating styled spans.
66 67 68 69 |
# File 'lib/al_email_protect.rb', line 66 def domain_markup(domain) separator = %(<span class="al-email-sep"> [dot] </span>) domain.split(".").map { |label| %(<span class="al-email-text">#{escape(label)}</span>) }.join(separator) end |
.enabled?(site) ⇒ Boolean
27 28 29 30 31 |
# File 'lib/al_email_protect.rb', line 27 def enabled?(site) return false unless site site.config["protect_email"] == true end |
.escape(value) ⇒ Object
61 62 63 |
# File 'lib/al_email_protect.rb', line 61 def escape(value) CGI.escapeHTML(value.to_s) end |
.obfuscate_text(value) ⇒ Object
"someone [at] example [dot] com", for places that show an address as text rather than as a link (CV contact blocks, for instance).
53 54 55 56 57 58 59 |
# File 'lib/al_email_protect.rb', line 53 def obfuscate_text(value) parts = split_address(value) return value.to_s unless parts local, domain = parts "#{local} [at] #{domain.gsub(".", " [dot] ")}" end |
.rewrite_html(html) ⇒ Object
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/al_email_protect.rb', line 173 def rewrite_html(html) html.to_s.gsub(ANCHOR) do before = Regexp.last_match(1) after = Regexp.last_match(4) address = decode_target(Regexp.last_match(3)) label = Regexp.last_match(5) parts = split_address(address) # Leave anything that is not a usable address exactly as it was; a # half-rewritten contact link is worse than an unprotected one. next Regexp.last_match(0) unless parts local, domain = parts attributes = "#{before}#{after}".gsub(/\s+/, " ").strip # The visible text often *is* the address. Replace it with the split # rendering, or the plaintext survives in the markup and the rewrite # achieves nothing. inner = label.to_s.include?("@") ? "#{escape(local)}<span class=\"al-email-sep\"> [at] </span>#{domain_markup(domain)}" : label %(<a href="#" class="al-email-protect #{attributes}" data-eu="#{escape(local)}" data-ed="#{escape(domain)}">#{inner}</a>) end end |
.split_address(value) ⇒ Object
Splits "someone@example.ac.uk" into ["someone", "example.ac.uk"].
Splits on the LAST "@" because the local part of an address may legally contain one when quoted (RFC 5321), and getting this backwards would emit a broken address rather than a protected one. Returns nil for anything not usable as an address, so callers can fall back to rendering it verbatim instead of silently publishing a mangled contact.
40 41 42 43 44 45 46 47 48 49 |
# File 'lib/al_email_protect.rb', line 40 def split_address(value) address = value.to_s.strip return nil if address.empty? local, _, domain = address.rpartition("@") return nil if local.empty? || domain.empty? return nil unless domain.include?(".") [local, domain] end |