├── .rspec ├── Gemfile ├── lib ├── validates_email_format_of │ ├── version.rb │ ├── railtie.rb │ ├── rspec_matcher.rb │ └── active_model.rb └── validates_email_format_of.rb ├── rakefile.rb ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── gemfiles ├── rails_7.0.gemfile ├── rails_7.1.gemfile ├── rails_6.1.gemfile ├── rails_6.0.gemfile ├── rails_4.2.gemfile ├── rails_5.1.gemfile ├── rails_5.0.gemfile └── rails_5.2.gemfile ├── .gitignore ├── config └── locales │ ├── ja.yml │ ├── pl.yml │ ├── tr.yml │ ├── en.yml │ ├── id.yml │ ├── pt.yml │ ├── fr.yml │ ├── it.yml │ ├── pt-BR.yml │ └── de.yml ├── spec ├── rspec_matcher_spec.rb ├── spec_helper.rb └── validates_email_format_of_spec.rb ├── Appraisals ├── validates_email_format_of.gemspec ├── MIT-LICENSE ├── CHANGELOG.md └── README.md /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --order random 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /lib/validates_email_format_of/version.rb: -------------------------------------------------------------------------------- 1 | module ValidatesEmailFormatOf 2 | VERSION = "1.8.2" 3 | end 4 | -------------------------------------------------------------------------------- /rakefile.rb: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "standard/rake" 3 | 4 | task default: [:spec, "standard:fix"] 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | -------------------------------------------------------------------------------- /gemfiles/rails_7.0.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "http://rubygems.org" 4 | 5 | gem "rails", "~> 7.0" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /gemfiles/rails_7.1.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "http://rubygems.org" 4 | 5 | gem "rails", "~> 7.1" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /gemfiles/rails_6.1.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "http://rubygems.org" 4 | 5 | gem "rails", "~> 6.1.0" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pkg 2 | test/debug.log 3 | rdoc 4 | Gemfile.lock 5 | *.gem 6 | *.sqlite3 7 | *.swp 8 | .ruby-version 9 | gemfiles/.bundle 10 | gemfiles/*.gemfile.lock 11 | -------------------------------------------------------------------------------- /gemfiles/rails_6.0.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "http://rubygems.org" 4 | 5 | gem "rails", "~> 6.0.3", ">= 6.0.3.2" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /gemfiles/rails_4.2.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "http://rubygems.org" 4 | 5 | gem "rails", "~> 4.2.0" 6 | gem "i18n", "< 1" 7 | 8 | gemspec path: "../" 9 | -------------------------------------------------------------------------------- /gemfiles/rails_5.1.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "http://rubygems.org" 4 | 5 | gem "rails", "~> 5.1.7" 6 | gem "i18n", "< 1" 7 | 8 | gemspec path: "../" 9 | -------------------------------------------------------------------------------- /gemfiles/rails_5.0.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "http://rubygems.org" 4 | 5 | gem "rails", "~> 5.0.7", ">= 5.0.7.2" 6 | gem "i18n", "< 1" 7 | 8 | gemspec path: "../" 9 | -------------------------------------------------------------------------------- /gemfiles/rails_5.2.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "http://rubygems.org" 4 | 5 | gem "rails", "~> 5.2.4", ">= 5.2.4.3" 6 | gem "i18n", "< 1" 7 | 8 | gemspec path: "../" 9 | -------------------------------------------------------------------------------- /config/locales/ja.yml: -------------------------------------------------------------------------------- 1 | ja: 2 | activemodel: &errors 3 | errors: 4 | messages: 5 | invalid_email_address: 'は妥当なメールアドレスでは無いようです。' 6 | email_address_not_routable: 'は到達不能です。' 7 | activerecord: 8 | <<: *errors 9 | -------------------------------------------------------------------------------- /config/locales/pl.yml: -------------------------------------------------------------------------------- 1 | pl: 2 | activemodel: &errors 3 | errors: 4 | messages: 5 | invalid_email_address: 'nieprawidłowy adres email' 6 | email_address_not_routable: 'jest nieosiągalny' 7 | activerecord: 8 | <<: *errors 9 | -------------------------------------------------------------------------------- /config/locales/tr.yml: -------------------------------------------------------------------------------- 1 | tr: 2 | activemodel: &errors 3 | errors: 4 | messages: 5 | invalid_email_address: 'geçerli bir eposta adresi değil' 6 | email_address_not_routable: 'ulaşılabilir değil' 7 | activerecord: 8 | <<: *errors 9 | -------------------------------------------------------------------------------- /lib/validates_email_format_of/railtie.rb: -------------------------------------------------------------------------------- 1 | module ValidatesEmailFormatOf 2 | class Railtie < Rails::Railtie 3 | initializer "validates_email_format_of.load_i18n_locales" do |app| 4 | ValidatesEmailFormatOf.load_i18n_locales 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | activemodel: &errors 3 | errors: 4 | messages: 5 | invalid_email_address: 'does not appear to be a valid email address' 6 | email_address_not_routable: 'is not routable' 7 | activerecord: 8 | <<: *errors 9 | -------------------------------------------------------------------------------- /config/locales/id.yml: -------------------------------------------------------------------------------- 1 | id: 2 | activemodel: &errors 3 | errors: 4 | messages: 5 | invalid_email_address: 'tampaknya bukan alamat email yang valid' 6 | email_address_not_routable: 'tidak dapat dirutekan' 7 | activerecord: 8 | <<: *errors 9 | -------------------------------------------------------------------------------- /config/locales/pt.yml: -------------------------------------------------------------------------------- 1 | pt: 2 | activemodel: &errors 3 | errors: 4 | messages: 5 | invalid_email_address: 'não parece ser um endereço de email válido' 6 | email_address_not_routable: 'não é acessível' 7 | activerecord: 8 | <<: *errors 9 | -------------------------------------------------------------------------------- /config/locales/fr.yml: -------------------------------------------------------------------------------- 1 | fr: 2 | activemodel: &errors 3 | errors: 4 | messages: 5 | invalid_email_address: "ne semble pas être une adresse email valide" 6 | email_address_not_routable: "n'est pas routable" 7 | activerecord: 8 | <<: *errors 9 | -------------------------------------------------------------------------------- /config/locales/it.yml: -------------------------------------------------------------------------------- 1 | it: 2 | activemodel: &errors 3 | errors: 4 | messages: 5 | invalid_email_address: 'non sembra un indirizzo email valido' 6 | email_address_not_routable: 'dominio non raggiungibile' 7 | activerecord: 8 | <<: *errors 9 | -------------------------------------------------------------------------------- /config/locales/pt-BR.yml: -------------------------------------------------------------------------------- 1 | pt-BR: 2 | activemodel: &errors 3 | errors: 4 | messages: 5 | invalid_email_address: 'não parece ser um endereço de email válido' 6 | email_address_not_routable: 'não é acessível' 7 | activerecord: 8 | <<: *errors 9 | -------------------------------------------------------------------------------- /config/locales/de.yml: -------------------------------------------------------------------------------- 1 | de: 2 | activemodel: &errors 3 | errors: 4 | messages: 5 | invalid_email_address: 'ist anscheinend keine gültige E-Mail-Adresse' 6 | email_address_not_routable: 'kann nicht erreicht werden' 7 | activerecord: 8 | <<: *errors 9 | -------------------------------------------------------------------------------- /spec/rspec_matcher_spec.rb: -------------------------------------------------------------------------------- 1 | require "#{__dir__}/spec_helper" 2 | require "validates_email_format_of/rspec_matcher" 3 | 4 | class Person 5 | attr_accessor :email_address 6 | include ::ActiveModel::Validations 7 | validates_email_format_of :email_address 8 | end 9 | 10 | describe Person do 11 | it { should validate_email_format_of(:email_address) } 12 | end 13 | -------------------------------------------------------------------------------- /lib/validates_email_format_of/rspec_matcher.rb: -------------------------------------------------------------------------------- 1 | require "validates_email_format_of" 2 | 3 | RSpec::Matchers.define :validate_email_format_of do |attribute| 4 | match do 5 | actual = subject.is_a?(Class) ? subject.new : subject 6 | actual.send(:"#{attribute}=", "invalid@example.") 7 | expect(actual).to be_invalid 8 | @expected_message ||= ValidatesEmailFormatOf.default_message 9 | expect(actual.errors.added?(attribute, @expected_message)).to be_truthy 10 | end 11 | chain :with_message do |message| 12 | @expected_message = message 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | # run `bundle exec appraisal install` after making changes here 2 | appraise "rails-7.1" do 3 | gem "rails", "~> 7.1" 4 | end 5 | 6 | appraise "rails-7.0" do 7 | gem "rails", "~> 7.0" 8 | end 9 | 10 | appraise "rails-6.1" do 11 | gem "rails", "~> 6.1.0" 12 | end 13 | 14 | appraise "rails-6.0" do 15 | gem "rails", "~> 6.0.3", ">= 6.0.3.2" 16 | end 17 | 18 | appraise "rails-5.2" do 19 | gem "rails", "~> 5.2.4", ">= 5.2.4.3" 20 | gem "i18n", "< 1" 21 | end 22 | 23 | appraise "rails-5.1" do 24 | gem "rails", "~> 5.1.7" 25 | gem "i18n", "< 1" 26 | end 27 | 28 | appraise "rails-5.0" do 29 | gem "rails", "~> 5.0.7", ">= 5.0.7.2" 30 | gem "i18n", "< 1" 31 | end 32 | 33 | appraise "rails-4.2" do 34 | gem "rails", "~> 4.2.0" 35 | gem "i18n", "< 1" 36 | end 37 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require "active_model" 2 | require "active_support" 3 | 4 | RSpec::Matchers.define :have_errors_on_email do 5 | match do |user| 6 | actual = user.errors.full_messages 7 | expect(user.errors.added?(:email, ValidatesEmailFormatOf::ERROR_MESSAGE_I18N_KEY)) 8 | expect(actual).not_to be_nil, "#{actual} should not be nil" 9 | expect(actual).not_to be_empty, "#{actual} should not be empty" 10 | expect(actual.size).to eq(@reasons.size), "#{actual} should have #{@reasons.size} elements" 11 | @reasons.each do |reason| 12 | reason = "Email #{reason}" 13 | expect(actual).to include(reason), "#{actual} should contain #{reason}" 14 | end 15 | end 16 | chain :because do |reason| 17 | (@reasons ||= []) << reason 18 | end 19 | chain :and_because do |reason| 20 | (@reasons ||= []) << reason 21 | end 22 | match_when_negated do |user| 23 | expect(user.errors).to(be_empty) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/validates_email_format_of/active_model.rb: -------------------------------------------------------------------------------- 1 | require "validates_email_format_of" 2 | require "active_model" 3 | 4 | if ActiveModel::VERSION::MAJOR < 2 || (ActiveModel::VERSION::MAJOR == 2 && ActiveModel::VERSION::MINOR < 1) 5 | puts "WARNING: ActiveModel validation helper methods in validates_email_format_of gem are not compatible with ActiveModel < 2.1.0. Please use ValidatesEmailFormatOf::validate_email_format(email, options) or upgrade ActiveModel" 6 | end 7 | 8 | module ActiveModel 9 | module Validations 10 | class EmailFormatValidator < EachValidator 11 | def validate_each(record, attribute, value) 12 | (ValidatesEmailFormatOf.validate_email_format(value, options) || []).each do |error| 13 | record.errors.add(attribute, error) 14 | end 15 | end 16 | end 17 | 18 | module HelperMethods 19 | def validates_email_format_of(*attr_names) 20 | validates_with EmailFormatValidator, _merge_attributes(attr_names) 21 | end 22 | end 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /validates_email_format_of.gemspec: -------------------------------------------------------------------------------- 1 | $:.unshift File.expand_path("../lib", __FILE__) 2 | require "validates_email_format_of/version" 3 | 4 | Gem::Specification.new do |s| 5 | s.name = "validates_email_format_of" 6 | s.version = ValidatesEmailFormatOf::VERSION 7 | s.summary = "Validate email addresses against RFC 2822 and RFC 3696." 8 | s.description = s.summary 9 | s.authors = ["Alex Dunae", "Isaac Betesh"] 10 | s.email = ["code@dunae.ca", "iybetesh@gmail.com"] 11 | s.homepage = "https://github.com/validates-email-format-of/validates_email_format_of" 12 | s.license = "MIT" 13 | s.files = `git ls-files`.split($/) 14 | s.require_paths = ["lib"] 15 | 16 | if RUBY_VERSION < "1.9.3" 17 | s.add_dependency "i18n", "< 0.7.0" 18 | else 19 | s.add_dependency "i18n", ">= 0.8.0" 20 | end 21 | 22 | s.add_dependency "simpleidn" 23 | s.add_development_dependency "activemodel" 24 | s.add_development_dependency "bundler" 25 | s.add_development_dependency "rspec" 26 | s.add_development_dependency "standard" 27 | s.add_development_dependency "appraisal" 28 | end 29 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2006-11 Alex Dunae 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | - pull_request 5 | 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | test: 11 | name: "Ruby ${{ matrix.ruby }}, Rails ${{ matrix.gemfile }}" 12 | 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | ruby: ["2.6", "2.7", "3.0", "3.1", "3.2", "3.3", "jruby-head"] 18 | gemfile: ["4.2", "5.0", "5.1", "5.2", "6.0", "6.1", "7.0", "7.1"] 19 | 20 | exclude: 21 | - gemfile: "4.2" 22 | ruby: "3.0" 23 | - gemfile: "4.2" 24 | ruby: "3.1" 25 | - gemfile: "4.2" 26 | ruby: "3.2" 27 | - gemfile: "4.2" 28 | ruby: "3.3" 29 | - gemfile: "4.2" 30 | ruby: "jruby-head" 31 | - gemfile: "5.0" 32 | ruby: "3.0" 33 | - gemfile: "5.0" 34 | ruby: "3.1" 35 | - gemfile: "5.0" 36 | ruby: "3.2" 37 | - gemfile: "5.0" 38 | ruby: "3.3" 39 | - gemfile: "5.0" 40 | ruby: "jruby-head" 41 | - gemfile: "5.1" 42 | ruby: "3.0" 43 | - gemfile: "5.1" 44 | ruby: "3.1" 45 | - gemfile: "5.1" 46 | ruby: "3.2" 47 | - gemfile: "5.1" 48 | ruby: "3.3" 49 | - gemfile: "5.1" 50 | ruby: "jruby-head" 51 | - gemfile: "5.2" 52 | ruby: "3.0" 53 | - gemfile: "5.2" 54 | ruby: "3.1" 55 | - gemfile: "5.2" 56 | ruby: "3.2" 57 | - gemfile: "5.2" 58 | ruby: "3.3" 59 | - gemfile: "5.2" 60 | ruby: "jruby-head" 61 | - gemfile: "6.0" 62 | ruby: "3.2" 63 | - gemfile: "6.0" 64 | ruby: "3.3" 65 | - gemfile: "6.1" 66 | ruby: "3.2" 67 | - gemfile: "6.1" 68 | ruby: "3.3" 69 | - gemfile: "7.0" 70 | ruby: "2.5" 71 | - gemfile: "7.0" 72 | ruby: "2.6" 73 | - gemfile: "7.0" 74 | ruby: "2.7" 75 | - gemfile: "7.1" 76 | ruby: "2.5" 77 | - gemfile: "7.1" 78 | ruby: "2.6" 79 | - gemfile: "7.1" 80 | ruby: "2.7" 81 | 82 | 83 | env: 84 | BUNDLE_GEMFILE: gemfiles/rails_${{ matrix.gemfile }}.gemfile 85 | RAILS_ENV: test 86 | 87 | steps: 88 | - uses: actions/checkout@v4 89 | 90 | - name: "Install Ruby ${{ matrix.ruby }}" 91 | # To automatically get bug fixes and new Ruby versions for ruby/setup-ruby, 92 | # change this to (see https://github.com/ruby/setup-ruby#versioning): 93 | # uses: ruby/setup-ruby@v1 94 | uses: ruby/setup-ruby@v1 95 | with: 96 | bundler: 1 97 | ruby-version: ${{ matrix.ruby }} 98 | bundler-cache: true # runs 'bundle install' and caches installed gems automatically 99 | 100 | - name: Run specs 101 | run: bundle exec rspec 102 | 103 | - name: Run standard.rb 104 | run: bundle exec rake standard 105 | if: ${{ ! startsWith(matrix.ruby, '2.') }} 106 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## [Unreleased] 4 | 5 | [Unreleased]: https://github.com/validates-email-format-of/validates_email_format_of/compare/v1.8.2...master 6 | 7 | ## [1.8.2] 8 | 9 | * Improve German translations - https://github.com/validates-email-format-of/validates_email_format_of/pull/111 10 | 11 | [1.8.2]: https://github.com/validates-email-format-of/validates_email_format_of/compare/v1.8.1...1.8.2 12 | 13 | ## [1.8.1] 14 | 15 | * Fix IDN->Punycode conversion when domain names start with periods - https://github.com/validates-email-format-of/validates_email_format_of/issues/109 16 | * Add jruby to test matrix - https://github.com/validates-email-format-of/validates_email_format_of/pull/108 17 | 18 | [1.8.1]: https://github.com/validates-email-format-of/validates_email_format_of/compare/v1.8.0...1.8.1 19 | 20 | ## [1.8.0] 21 | 22 | * Add Internationalized Domain Name support - https://github.com/validates-email-format-of/validates_email_format_of/pull/103 - thanks https://github.com/sbilharz ! 23 | * Add Turkish locale - https://github.com/validates-email-format-of/validates_email_format_of/pull/101 - thanks https://github.com/@krmbzds ! 24 | * Added Indonesian locale - https://github.com/validates-email-format-of/validates_email_format_of/commit/129ebfc3a3b432b4df0334bcbdd74b1d17d765e0 - thanks https://github.com/khoerodin ! 25 | * Fix inconsistent `generate_messages` behaviour - https://github.com/validates-email-format-of/validates_email_format_of/pull/105 26 | * ⚠️ Deprecate `:with` option - https://github.com/validates-email-format-of/validates_email_format_of/issues/42 27 | * Require i18n >= 0.8.0 in modern Ruby versions - https://github.com/advisories/GHSA-34hf-g744-jw64 28 | 29 | [1.8.0]: https://github.com/validates-email-format-of/validates_email_format_of/compare/v1.7.2...1.8.0 30 | 31 | ## [1.7.2] 32 | 33 | * Fix regression that disallowed domains starting with number - https://github.com/validates-email-format-of/validates_email_format_of/issues/88 34 | 35 | [1.7.2]: https://github.com/validates-email-format-of/validates_email_format_of/compare/v1.7.1...v1.7.2 36 | 37 | ## [1.7.1] (3 Aug 2022) 38 | 39 | * Fix invalid symbols being allowed in the local part - https://github.com/validates-email-format-of/validates_email_format_of/issues/86 40 | * Fix rspec_matcher when using a custom error message - https://github.com/validates-email-format-of/validates_email_format_of/pull/85 - thanks https://github.com/luuqnhu ! 41 | 42 | [1.7.1]: https://github.com/validates-email-format-of/validates_email_format_of/compare/v1.7.0...v1.7.1 43 | 44 | ## [1.7.0] (29 July 2022) 45 | 46 | * Use Standard.rb for internal code formatting - https://github.com/validates-email-format-of/validates_email_format_of/commit/db1b0a86af58e478b7f9f2f269bf93bf48dc13c1 47 | * Add support for comments in the local part and improve quoted character handling - https://github.com/validates-email-format-of/validates_email_format_of/issues/69 48 | * Improve grammar for parsing domain part and validate domain part lengths - https://github.com/validates-email-format-of/validates_email_format_of/commit/2554b55e547c1fae6599d13b0c99296752888c91 49 | * Do not strip spaces before validating - https://github.com/validates-email-format-of/validates_email_format_of/issues/61 and https://github.com/validates-email-format-of/validates_email_format_of/issues/72 50 | * Allow setting check_mx_timeout and reduce the default timeout to 3 seconds - https://github.com/validates-email-format-of/validates_email_format_of/issues/66 51 | * Fix regex duplicate character warning - https://github.com/validates-email-format-of/validates_email_format_of/pull/71 52 | * Update CI to include Ruby 2.6 to 3.1 and Rails 4.2 to 7.0 53 | 54 | [1.7.0]: https://github.com/validates-email-format-of/validates_email_format_of/compare/v1.6.1...v1.7.0 55 | ## [1.6.1] (8 Sept 2014) 56 | 57 | * In a Rails context, this gem now uses ActiveModel's default logic for constructing I18n keys, to make it easier to override them on a model/attribute basis. 58 | 59 | [1.6.1]: https://github.com/validates-email-format-of/validates_email_format_of/compare/v1.6.0...v1.6.1 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # validates_email_format_of 2 | 3 | [![Build Status](https://github.com/validates-email-format-of/validates_email_format_of/actions/workflows/ci.yml/badge.svg)]( https://github.com/validates-email-format-of/validates_email_format_of/actions/workflows/ci.yml?query=branch%3Amaster) 4 | 5 | A Ruby gem to validate email addresses against RFC 2822 and RFC 5322, with optional domain name lookups. 6 | 7 | ## Why this email validator? 8 | 9 | This gem is the O.G. email validation gem for Rails. It was started back in 2006. 10 | 11 | Why use this validator? Instead of trying to validate email addresses with one giant regular expression, this library parses addresses character by character. This lets us handle weird cases like [nested comments](https://www.rfc-editor.org/rfc/rfc5322#appendix-A.5). Gross, but technically allowed. 12 | 13 | In reality, most email validating scripts will get you where you need to go. This library just aims to go all the way. 14 | 15 | ## Installation 16 | 17 | Add the gem to your Gemfile with: 18 | 19 | ```sh 20 | gem 'validates_email_format_of' 21 | ``` 22 | 23 | ### Usage in a Rails app 24 | 25 | ```ruby 26 | class Person < ActiveRecord::Base 27 | validates :email, :email_format => { :message => "is not looking good" } 28 | 29 | # OR 30 | 31 | validates_email_format_of :email, :message => "is not looking good" 32 | end 33 | ``` 34 | 35 | You can use the included `rspec` matcher as well: 36 | 37 | ```ruby 38 | require "validates_email_format_of/rspec_matcher" 39 | 40 | describe Person do 41 | it { should validate_email_format_of(:email).with_message("is not looking good") } 42 | end 43 | ``` 44 | 45 | ### Usage without Rails 46 | 47 | ```ruby 48 | ValidatesEmailFormatOf::validate_email_format("example@mydomain.com") # => nil 49 | ValidatesEmailFormatOf::validate_email_format("invalid@") # => ["does not appear to be a valid email address"] 50 | 51 | # Optional, if you want error messages to be in your language 52 | ValidatesEmailFormatOf::load_i18n_locales 53 | I18n.locale = :pl 54 | 55 | ValidatesEmailFormatOf::validate_email_format("invalid@") # => ["nieprawidłowy adres email"] 56 | ``` 57 | 58 | ## Internationalized Domain Names (IDN) and Punycode 59 | 60 | As of v1.8.0, this gem can validate email addresses using internationalized domain names (like `test@пример.рф`) as well as domains that have already been converted to Punycode code (like `test@xn--test@-3weu6azakd.xn--p1ai`). 61 | 62 | If you would like to forbid internationalized domains, you can pass the `idn: false` option. Punycode is always accepted. 63 | 64 | 65 | ## Options 66 | 67 | | Option | Type | Description | 68 | | --- | --- | --- | 69 | | `:message` | String | A custom error message when the email format is invalid (default is: "does not appear to be a valid email address") | 70 | | `:check_mx` | Boolean | Check domain for a valid MX record (default is false) | 71 | | `:check_mx_timeout` | Integer | Timeout in seconds for checking MX records before a `ResolvTimeout` is raised (default is 3). | 72 | | `:idn` | Boolean | Allowed internationalized domain names like `test@exämple.com` and `test@пример.рф`. Otherwise only domains that have already been converted to [Punycode](https://en.wikipedia.org/wiki/Punycode) are supported. (default is true) | 73 | | `:mx_message` | String | A custom error message when the domain does not match a valid MX record (default is: "is not routable"). Ignored unless :check_mx option is true. | 74 | | `:local_length` |Integer | Maximum number of characters allowed in the local part (everything before the '@') (default is 64) | 75 | | `:domain_length` | Integer | Maximum number of characters allowed in the domain part (everything after the '@') (default is 255) | 76 | | `:generate_message` | Boolean | Return the I18n key of the error message instead of the error message itself (default is false) | 77 | 78 | The standard ActiveModel validation options (`:on`, `:if`, `:unless`, `:allow_nil`, `:allow_blank`, etc...) all work as well when using the gem as part of a Rails application. 79 | ## Testing 80 | 81 | The gem is tested against Rails 4.2 and onward across a bunch of Ruby versions including jruby. You can see our [current Ruby and Rails test matrix here](.github/workflows/ci.yml). 82 | 83 | To execute the unit tests against [all the Rails versions we support run](gemfiles/) bundle exec appraisal rspec or run against an individual version with bundle exec appraisal rails-6.0 rspec. 84 | ## Contributing 85 | 86 | If you think we're letting some rules about valid email formats slip through the cracks, don't just update the parser. Instead, add a failing test and demonstrate that the described email address should be treated differently. A link to an appropriate RFC is the best way to do this. Then change the gem code to make the test pass. 87 | 88 | ```ruby 89 | describe "i_think_this_is_not_a_v@lid_email_addre.ss" do 90 | # According to http://..., this email address IS NOT valid. 91 | it { should have_errors_on_email.because("does not appear to be valid") } 92 | end 93 | 94 | describe "i_think_this_is_a_v@lid_email_addre.ss" do 95 | # According to http://..., this email address IS valid. 96 | it { should_not have_errors_on_email } 97 | end 98 | ``` 99 | 100 | Yes, our Rspec syntax is that simple! 101 | 102 | ## Homepage 103 | 104 | * https://github.com/validates-email-format-of/validates_email_format_of 105 | 106 | ## Credits 107 | 108 | Written by [Alex Dunae](https://dunae.ca), 2006-24. 109 | 110 | Many thanks to the plugin's recent contributors: https://github.com/alexdunae/validates_email_format_of/contributors 111 | 112 | Thanks to [Francis Hwang](http://fhwang.net/) at Diversion Media for creating the 1.1 update. 113 | 114 | Thanks to Travis Sinnott for creating the 1.3 update. 115 | 116 | Thanks to Denis Ahearn at [Riverock Technologies](http://www.riverocktech.com/) for creating the 1.4 update. 117 | 118 | Thanks to [George Anderson](http://github.com/george) and ['history'](http://github.com/history) for creating the 1.4.1 update. 119 | 120 | Thanks to [Isaac Betesh](https://github.com/betesh) for converting tests to Rspec and refactoring for version 1.6.0. 121 | -------------------------------------------------------------------------------- /lib/validates_email_format_of.rb: -------------------------------------------------------------------------------- 1 | require "validates_email_format_of/version" 2 | require "simpleidn" 3 | 4 | module ValidatesEmailFormatOf 5 | def self.load_i18n_locales 6 | require "i18n" 7 | I18n.load_path += Dir.glob(File.expand_path(File.join(File.dirname(__FILE__), "..", "config", "locales", "*.yml"))) 8 | end 9 | 10 | require "resolv" 11 | 12 | # Characters that are allowed in to appear in the local part unquoted 13 | # https://www.rfc-editor.org/rfc/rfc5322#section-3.2.3 14 | # 15 | # An addr-spec is a specific Internet identifier that contains a 16 | # locally interpreted string followed by the at-sign character ("@", 17 | # ASCII value 64) followed by an Internet domain. The locally 18 | # interpreted string is either a quoted-string or a dot-atom. If the 19 | # string can be represented as a dot-atom (that is, it contains no 20 | # characters other than atext characters or "." surrounded by atext 21 | # characters), then the dot-atom form SHOULD be used and the quoted- 22 | # string form SHOULD NOT be used. Comments and folding white space 23 | # SHOULD NOT be used around the "@" in the addr-spec. 24 | # 25 | # atext = ALPHA / DIGIT / 26 | # "!" / "#" / "$" / "%" / "&" / "'" / "*" / 27 | # "+" / "-" / "/" / "=" / "?" / "^" / "_" / 28 | # "`" / "{" / "|" / "}" / "~" 29 | # dot-atom-text = 1*atext *("." 1*atext) 30 | # dot-atom = [CFWS] dot-atom-text [CFWS] 31 | ATEXT = /\A[A-Z0-9!\#$%&'*\-\/=?+\^_`{|}~]\z/i 32 | 33 | # Characters that are allowed to appear unquoted in comments 34 | # https://www.rfc-editor.org/rfc/rfc5322#section-3.2.2 35 | # 36 | # ctext = %d33-39 / %d42-91 / %d93-126 37 | # ccontent = ctext / quoted-pair / comment 38 | # comment = "(" *([FWS] ccontent) [FWS] ")" 39 | # CFWS = (1*([FWS] comment) [FWS]) / FWS 40 | CTEXT = /\A[#{Regexp.escape([33..39, 42..91, 93..126].map { |ascii_range| ascii_range.map(&:chr) }.flatten.join)}\s]/i 41 | 42 | # https://www.rfc-editor.org/rfc/rfc5322#section-3.2.4 43 | # 44 | # Strings of characters that include characters other than those 45 | # allowed in atoms can be represented in a quoted string format, where 46 | # the characters are surrounded by quote (DQUOTE, ASCII value 34) 47 | # characters. 48 | # 49 | # qtext = %d33 / ; Printable US-ASCII 50 | # %d35-91 / ; characters not including 51 | # %d93-126 / ; "\" or the quote character 52 | # obs-qtext 53 | # 54 | # qcontent = qtext / quoted-pair 55 | # quoted-string = [CFWS] 56 | # DQUOTE *([FWS] qcontent) [FWS] DQUOTE 57 | # [CFWS] 58 | QTEXT = /\A[#{Regexp.escape([33..33, 35..91, 93..126].map { |ascii_range| ascii_range.map(&:chr) }.flatten.join)}\s]/i 59 | 60 | IP_OCTET = /\A[0-9]+\Z/ 61 | 62 | # From https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.1 63 | # 64 | # > The labels must follow the rules for ARPANET host names. They must 65 | # > start with a letter, end with a letter or digit, and have as interior 66 | # > characters only letters, digits, and hyphen. There are also some 67 | # > restrictions on the length. Labels must be 63 characters or less. 68 | # 69 | #