├── fixtures └── test-app │ ├── config.rb │ └── source │ ├── index.html │ ├── with_no_email.html │ ├── with_body.html │ ├── with_url_params.html │ ├── with_multiple_emails.html │ └── with_complex_url_params.html ├── lib ├── middleman-protect-emails │ ├── version.rb │ ├── rot13_script.html │ └── extension.rb └── middleman-protect-emails.rb ├── .travis.yml ├── .gitignore ├── Gemfile ├── Rakefile ├── features ├── support │ └── env.rb └── extension.feature ├── middleman-protect-emails.gemspec ├── LICENSE.txt └── README.md /fixtures/test-app/config.rb: -------------------------------------------------------------------------------- 1 | activate :protect_emails 2 | -------------------------------------------------------------------------------- /fixtures/test-app/source/index.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fixtures/test-app/source/with_no_email.html: -------------------------------------------------------------------------------- 1 | Hello! 2 | -------------------------------------------------------------------------------- /fixtures/test-app/source/with_body.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lib/middleman-protect-emails/version.rb: -------------------------------------------------------------------------------- 1 | module Middleman 2 | module ProtectEmails 3 | VERSION = '0.4.0' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.2.0 4 | - 2.1.5 5 | - 2.1.0 6 | - 2.0.0 7 | before_install: 8 | - gem install bundler 9 | -------------------------------------------------------------------------------- /fixtures/test-app/source/with_url_params.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fixtures/test-app/source/with_multiple_emails.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.bundle/ 2 | /.yardoc 3 | /Gemfile.lock 4 | /_yardoc/ 5 | /coverage/ 6 | /doc/ 7 | /pkg/ 8 | /spec/reports/ 9 | /tmp/ 10 | *.bundle 11 | *.so 12 | *.o 13 | *.a 14 | mkmf.log 15 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :test do 6 | gem 'cucumber', '~> 2.0' 7 | gem 'aruba', '~> 0.7.4' 8 | gem 'codeclimate-test-reporter' 9 | end 10 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'cucumber/rake/task' 3 | 4 | Cucumber::Rake::Task.new(:cucumber, 'Run features that should pass') do |t| 5 | t.cucumber_opts = "--color --format progress" 6 | end 7 | 8 | task default: :cucumber 9 | -------------------------------------------------------------------------------- /lib/middleman-protect-emails.rb: -------------------------------------------------------------------------------- 1 | require 'middleman-core' 2 | require 'middleman-protect-emails/version' 3 | 4 | ::Middleman::Extensions.register(:protect_emails) do 5 | require 'middleman-protect-emails/extension' 6 | ::Middleman::ProtectEmailsExtension 7 | end 8 | -------------------------------------------------------------------------------- /features/support/env.rb: -------------------------------------------------------------------------------- 1 | PROJECT_ROOT_PATH = File.dirname(File.dirname(File.dirname(__FILE__))) 2 | require 'middleman-core' 3 | require 'middleman-core/step_definitions' 4 | 5 | require 'codeclimate-test-reporter' 6 | CodeClimate::TestReporter.start 7 | 8 | require File.join(PROJECT_ROOT_PATH, 'lib', 'middleman-protect-emails') 9 | -------------------------------------------------------------------------------- /lib/middleman-protect-emails/rot13_script.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /fixtures/test-app/source/with_complex_url_params.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | localhost domain 5 | plus sign in subject 6 | spaces in subject 7 | cc param 8 | crazy case 9 | @example.com hacker 10 | 11 | 12 | -------------------------------------------------------------------------------- /middleman-protect-emails.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'middleman-protect-emails/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = 'middleman-protect-emails' 8 | spec.version = Middleman::ProtectEmails::VERSION 9 | spec.authors = ['Ankit Sardesai'] 10 | spec.email = ['amsardesai@gmail.com'] 11 | spec.summary = %q{Middleman extension for email link protection and obfuscation} 12 | spec.description = %q{Middleman extension for email link protection and obfuscation.} 13 | spec.homepage = '' 14 | spec.license = 'MIT' 15 | spec.files = `git ls-files -z`.split("\x0") 16 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 17 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 18 | spec.require_paths = ['lib'] 19 | spec.required_ruby_version = '>= 2.0' 20 | 21 | spec.add_dependency 'middleman-core', '>= 3.4' 22 | 23 | spec.add_development_dependency 'rake', '~> 10.3' 24 | end 25 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Ankit Sardesai 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /features/extension.feature: -------------------------------------------------------------------------------- 1 | Feature: Email Protection 2 | 3 | Scenario: Inserts script into the page 4 | Given the Server is running at "test-app" 5 | When I go to "/" 6 | Then I should see "" 7 | 8 | Scenario: Shows obfuscated email on page 9 | Given the Server is running at "test-app" 10 | When I go to "/" 11 | Then I should see "" 12 | 13 | Scenario: Inserts script into end of body if a body tag exists 14 | Given the Server is running at "test-app" 15 | When I go to "/with_body.html" 16 | Then I should see: 17 | """ 18 | 19 | 20 | """ 21 | 22 | Scenario: Does not insert script if there is no email on the page 23 | Given the Server is running at "test-app" 24 | When I go to "/with_no_email.html" 25 | Then I should not see "" 26 | 27 | Scenario: Encrypts multiple emails 28 | Given the Server is running at "test-app" 29 | When I go to "/with_multiple_emails.html" 30 | Then I should see "" 31 | Then I should see "" 32 | 33 | Scenario: Encrypts mailto link parameters 34 | Given the Server is running at "test-app" 35 | When I go to "/with_url_params.html" 36 | Then I should see "" 37 | 38 | Scenario: Encrypts mailto link complex parameters 39 | Given the Server is running at "test-app" 40 | When I go to "/with_complex_url_params.html" 41 | Then I should see "localhost domain" 42 | Then I should see "plus sign in subject" 43 | Then I should see "spaces in subject" 44 | Then I should see "cc param" 45 | Then I should see "crazy case" 46 | Then I should see "@example.com hacker" 47 | -------------------------------------------------------------------------------- /lib/middleman-protect-emails/extension.rb: -------------------------------------------------------------------------------- 1 | require 'middleman-core/util' 2 | 3 | class Middleman::ProtectEmailsExtension < ::Middleman::Extension 4 | 5 | def initialize(app, options_hash={}, &block) 6 | super 7 | end 8 | 9 | def after_configuration 10 | app.use Middleware, middleman_app: app 11 | end 12 | 13 | class Middleware 14 | def initialize(app, options = {}) 15 | @rack_app = app 16 | @middleman_app = options[:middleman_app] 17 | end 18 | 19 | def call(env) 20 | status, headers, response = @rack_app.call(env) 21 | 22 | # Get path 23 | path = ::Middleman::Util.full_path(env['PATH_INFO'], @middleman_app) 24 | 25 | # Match only HTML documents 26 | if path =~ /(^\/$)|(\.(htm|html)$)/ 27 | body = ::Middleman::Util.extract_response_text(response) 28 | if body 29 | status, headers, response = Rack::Response.new(rewrite_response(body), status, headers).finish 30 | end 31 | end 32 | 33 | [status, headers, response] 34 | end 35 | 36 | private 37 | 38 | def rewrite_response(body) 39 | # Keeps track of email replaces 40 | replaced_email = false 41 | 42 | # Replaces mailto links with ROT13 equivalent 43 | # TODO: Don't replace plaintext mailto links 44 | invalid_character = '\s"\'>' 45 | email_username = "[^@#{invalid_character}]+" 46 | email_domain = "[^?#{invalid_character}]+" 47 | email_param = "[^&#{invalid_character}]+" 48 | new_content = body.gsub /mailto:(#{email_username}@#{email_domain}(\?#{email_param}(\&#{email_param})*)?)/i do 49 | replaced_email = true 50 | email = $1.tr 'A-Za-z','N-ZA-Mn-za-m' 51 | "#email-protection-#{email}" 52 | end 53 | 54 | # Don't do anything else if there are no emails on the page 55 | return body unless replaced_email 56 | 57 | # Reads decoding script 58 | file = File.join(File.dirname(__FILE__), 'rot13_script.html') 59 | script_content = File.read file 60 | 61 | # Appends decoding script at end of body or end of page 62 | if new_content =~ /<\/body>/i 63 | new_content.gsub(/(<\/body>)/i) do 64 | script_content + $1 65 | end 66 | else 67 | new_content + script_content 68 | end 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # middleman-protect-emails 2 | 3 | [![Build Status](https://travis-ci.org/amsardesai/middleman-protect-emails.svg)](https://travis-ci.org/amsardesai/middleman-protect-emails) 4 | [![Code Climate](https://codeclimate.com/github/amsardesai/middleman-protect-emails/badges/gpa.svg)](https://codeclimate.com/github/amsardesai/middleman-protect-emails) 5 | [![Test Coverage](https://codeclimate.com/github/amsardesai/middleman-protect-emails/badges/coverage.svg)](https://codeclimate.com/github/amsardesai/middleman-protect-emails) 6 | 7 | **middleman-protect-emails** is a [Middleman](http://middlemanapp.com) extension that encrypts email links on your page on the server-side and decodes them on the client-side, avoiding spam bots with no visible impacts to your users. 8 | 9 | This gem makes use of the [ROT13](http://en.wikipedia.org/wiki/ROT13) encryption algorithm to encrypt email links. Users must have Javascript enabled on their computers for the decoding stage to work. 10 | 11 | ## Installation 12 | 13 | Add this line to your Middleman application's Gemfile: 14 | 15 | ```ruby 16 | gem 'middleman-protect-emails' 17 | ``` 18 | 19 | And then run: 20 | 21 | $ bundle 22 | 23 | ## Usage 24 | 25 | Using this gem is as simple as adding the following line to your project's `config.rb` file: 26 | 27 | ```ruby 28 | activate :protect_emails 29 | ``` 30 | 31 | And that's it! This will now protect all `mailto` links in your Middleman project. 32 | 33 | ### How it Works 34 | 35 | If the middleware detects a `mailto` link on your page, it will automatically replace the link with an encrypted hash and insert a small script at the end of the page for the browser to decode it on page load. For example, if the following code was on one of your pages: 36 | 37 | ```html 38 | Link 39 | ``` 40 | 41 | It would automatically be replaced with: 42 | 43 | ```html 44 | Link 45 | ``` 46 | 47 | This extension also encrypts link parameters (ex. `mailto:hello@example.com?subject=Some%20Subject`). 48 | 49 | ## Contributing 50 | 51 | 1. Fork it 52 | 2. Create your feature branch (`git checkout -b my-new-feature`) 53 | 3. Commit your changes (`git commit -am 'Add some feature'`) 54 | 4. Push to the branch (`git push origin my-new-feature`) 55 | 5. Create a [new pull request](../../pull/new/master) 56 | --------------------------------------------------------------------------------