Rails için e-posta doğrulamasında son teknoloji nedir?


95

Kullanıcıların e-posta adreslerini doğrulamak için ne kullanıyorsunuz ve neden?

validates_email_veracity_ofAslında MX sunucularını sorgulayan kullanıyordum . Ancak bu, çoğunlukla ağ trafiği ve güvenilirlikle ilgili çeşitli nedenlerden dolayı başarısızlıkla doludur.

Etrafıma baktım ve pek çok insanın bir e-posta adresi üzerinde akıl sağlığı kontrolü yapmak için kullandığı açık bir şey bulamadım. Bunun için sürdürülen, makul derecede doğru bir eklenti veya mücevher var mı?

Not: Lütfen bana e-postanın çalışıp çalışmadığını görmek için bağlantı içeren bir e-posta göndermemi söylemeyin. Bir "arkadaşa gönder" özelliği geliştiriyorum, bu yüzden bu pratik değil.


İşte normal ifadeyle uğraşmadan süper kolay bir yol: geçerli bir e-posta adresi algılama
Zabba

MX sunucusunu sorgulamanın neden başarısız olduğuna dair daha ayrıntılı bir neden verebilir misiniz? Bunların düzeltilebilir olup olmadığını görmek için bilmek isterim.
lulalala

Yanıtlar:


67

Rails 3.0 ile Mail cevherini kullanarak regexp olmadan bir e-posta doğrulamasını kullanabilirsiniz .

İşte benim uygulamam ( bir mücevher olarak paketlenmiş ).


Güzel, mücevherini kullanıyorum. Teşekkürler.
jasoncrawford

###@domain.comdoğrulayacak gibi görünüyor ?
cwd

1
Beyler bu mücevheri canlandırmak istiyorum, onu korumak için zamanım olmadı. Ama görünen o ki insanlar hala kullanıyor ve iyileştirme arıyor. Eğer ilgileniyorsanız, lütfen bana github projesini yazın: hallelujah / valid_email
Hallelujah

106

Bunu olması gerekenden daha zorlaştırma. Özelliğiniz kritik değil; doğrulama, yazım hatalarını yakalamak için sadece basit bir mantıklı adımdır. Bunu basit bir normal ifadeyle yapardım ve CPU döngülerini çok karmaşık hiçbir şey için boşa harcamazdım:

/\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/

Bu, http://www.regular-expressions.info/email.html'den uyarlanmıştır - tüm değiş tokuşları gerçekten bilmek istiyorsanız okumanız gerekir. Daha doğru ve çok daha karmaşık, tamamen RFC822 uyumlu bir normal ifade istiyorsanız, o da o sayfada. Ama mesele şu ki, tamamen doğru yapmanıza gerek yok.

Adres doğrulamayı geçerse, bir e-posta göndereceksiniz. E-posta başarısız olursa, bir hata mesajı alırsınız. Hangi noktada kullanıcıya "Üzgünüm, arkadaşınız bunu almadı, tekrar denemek ister misiniz?" Diyebilirsiniz. ya da manuel inceleme için işaretleyin ya da yok sayın ya da her neyse.

Bunlar adres eğer uğraşmak zorunda istiyorum aynı seçeneklerdir yaptılar doğrulama geçmektedir. Çünkü doğrulamanız mükemmel olsa ve adresin var olduğuna dair mutlak kanıt elde etseniz bile, gönderme yine de başarısız olabilir.

Doğrulamada yanlış pozitifin maliyeti düşüktür. Daha iyi doğrulamanın yararı da düşüktür. Cömertçe doğrulayın ve hatalar olduğunda endişelenin.


36
Err, o .museum ve yeni uluslararası TLD'lerde kusmaz mı? Bu normal ifade, birçok geçerli e-posta adresini engelleyecektir .
Elijah

3
Elijah ile anlaştık, bu kötü bir tavsiye. Ayrıca, kullanıcıya arkadaşının e-postayı almadığını nasıl söyleyeceğinizi düşündüğünüzden emin değilim çünkü e-postanın hemen başarılı olup olmadığını anlamanın bir yolu yoktur.
Jaryl

8
Müze falan üzerine iyi bir nokta - bu cevabı 2009'da ilk kez gönderdiğimde sorun değildi. Normal ifadeyi değiştirdim. Daha fazla geliştirmeniz varsa, onu da düzenleyebilir veya bunu bir topluluk wiki gönderisi yapabilirsiniz.
SFEley

5
Bilginize, bu yine de bazı geçerli e-posta adreslerini kaçıracaktır. Çok değil, birkaç tane. Örneğin, teknik olarak #|@foo.com geçerli bir e-posta adresidir, "Hey, alıntılarsa boşluk alabilirim" @ foo.com. @ İşaretinden önceki herhangi bir şeyi yok saymayı ve sadece alan kısmını doğrulamayı en kolay buluyorum.
Nerdmaster

6
Bazı yanlış adreslere izin verme konusunda endişelenmemeniz gerektiği motivasyonuna katılıyorum. Maalesef bu normal ifade bazı doğru adreslere izin vermeyecek, ki bunu kabul edilemez buluyorum. Belki böyle bir şey daha iyi olabilir mi? /.+@.+\..+/
ZoFreX

12

Rails 3'te e-posta doğrulaması için bir mücevher yarattım. Rails'in varsayılan olarak buna benzer bir şey içermemesine biraz şaşırdım.

http://github.com/balexand/email_validator


8
Bu aslında normal ifadenin etrafındaki bir sarmalayıcıdır.
Rob Dawson

Bunu bir ifveya unlessifadeyle nasıl kullanacağınıza dair bir örnek verebilir misiniz ? Belgeler seyrek görünüyor.
2013

@cwd Belgelerin tamamlandığını düşünüyorum. Rails 3+ doğrulamasına aşina değilseniz, bu Railscast'e ( railscasts.com/episodes/211-validations-in-rails-3 ) veya guides.rubyonrails.org/active_record_validations.html
balexand


7

Dan 4 docs Raylar :

class EmailValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
      record.errors[attribute] << (options[:message] || "is not an email")
    end
  end
end

class Person < ActiveRecord::Base
  validates :email, presence: true, email: true
end

5

Rails 4'te modelinize ekleyin validates :email, email:true(alanınızın çağrıldığını varsayarak email) ve ardından EmailValidatorihtiyaçlarınıza uygun basit (veya karmaşık †) yazın.

örneğin: - modeliniz:

class TestUser
  include Mongoid::Document
  field :email,     type: String
  validates :email, email: true
end

Doğrulayıcınız (girer app/validators/email_validator.rb)

class EmailValidator < ActiveModel::EachValidator
  EMAIL_ADDRESS_QTEXT           = Regexp.new '[^\\x0d\\x22\\x5c\\x80-\\xff]', nil, 'n'
  EMAIL_ADDRESS_DTEXT           = Regexp.new '[^\\x0d\\x5b-\\x5d\\x80-\\xff]', nil, 'n'
  EMAIL_ADDRESS_ATOM            = Regexp.new '[^\\x00-\\x20\\x22\\x28\\x29\\x2c\\x2e\\x3a-\\x3c\\x3e\\x40\\x5b-\\x5d\\x7f-\\xff]+', nil, 'n'
  EMAIL_ADDRESS_QUOTED_PAIR     = Regexp.new '\\x5c[\\x00-\\x7f]', nil, 'n'
  EMAIL_ADDRESS_DOMAIN_LITERAL  = Regexp.new "\\x5b(?:#{EMAIL_ADDRESS_DTEXT}|#{EMAIL_ADDRESS_QUOTED_PAIR})*\\x5d", nil, 'n'
  EMAIL_ADDRESS_QUOTED_STRING   = Regexp.new "\\x22(?:#{EMAIL_ADDRESS_QTEXT}|#{EMAIL_ADDRESS_QUOTED_PAIR})*\\x22", nil, 'n'
  EMAIL_ADDRESS_DOMAIN_REF      = EMAIL_ADDRESS_ATOM
  EMAIL_ADDRESS_SUB_DOMAIN      = "(?:#{EMAIL_ADDRESS_DOMAIN_REF}|#{EMAIL_ADDRESS_DOMAIN_LITERAL})"
  EMAIL_ADDRESS_WORD            = "(?:#{EMAIL_ADDRESS_ATOM}|#{EMAIL_ADDRESS_QUOTED_STRING})"
  EMAIL_ADDRESS_DOMAIN          = "#{EMAIL_ADDRESS_SUB_DOMAIN}(?:\\x2e#{EMAIL_ADDRESS_SUB_DOMAIN})*"
  EMAIL_ADDRESS_LOCAL_PART      = "#{EMAIL_ADDRESS_WORD}(?:\\x2e#{EMAIL_ADDRESS_WORD})*"
  EMAIL_ADDRESS_SPEC            = "#{EMAIL_ADDRESS_LOCAL_PART}\\x40#{EMAIL_ADDRESS_DOMAIN}"
  EMAIL_ADDRESS_PATTERN         = Regexp.new "#{EMAIL_ADDRESS_SPEC}", nil, 'n'
  EMAIL_ADDRESS_EXACT_PATTERN   = Regexp.new "\\A#{EMAIL_ADDRESS_SPEC}\\z", nil, 'n'

  def validate_each(record, attribute, value)
    unless value =~ EMAIL_ADDRESS_EXACT_PATTERN
      record.errors[attribute] << (options[:message] || 'is not a valid email')
    end
  end
end

Bu, "test+no_really@test.tes" gibi etiketli e-postalar dahil her türlü geçerli e-postaya izin verir .

Bunu rspecsenin içinde test etmek içinspec/validators/email_validator_spec.rb

require 'spec_helper'

describe "EmailValidator" do
  let(:validator) { EmailValidator.new({attributes: [:email]}) }
  let(:model) { double('model') }

  before :each do
    model.stub("errors").and_return([])
    model.errors.stub('[]').and_return({})  
    model.errors[].stub('<<')
  end

  context "given an invalid email address" do
    let(:invalid_email) { 'test test tes' }
    it "is rejected as invalid" do
      model.errors[].should_receive('<<')
      validator.validate_each(model, "email", invalid_email)
    end  
  end

  context "given a simple valid address" do
    let(:valid_simple_email) { 'test@test.tes' }
    it "is accepted as valid" do
      model.errors[].should_not_receive('<<')    
      validator.validate_each(model, "email", valid_simple_email)
    end
  end

  context "given a valid tagged address" do
    let(:valid_tagged_email) { 'test+thingo@test.tes' }
    it "is accepted as valid" do
      model.errors[].should_not_receive('<<')    
      validator.validate_each(model, "email", valid_tagged_email)
    end
  end
end

Zaten böyle yaptım. YMMV

† Normal ifadeler şiddet gibidir; Çalışmazlarsa yeterince kullanmıyorsunuz demektir.


1
Doğrulamanızı kullanmak isterim, ama bunu nereden aldığınız veya nasıl yaptığınız hakkında hiçbir fikrim yok. Bize söyleyebilir misin?
Mauricio Moraes

Normal ifadeyi bir Google aramasından aldım ve sarmalayıcı kodunu ve özellik testlerini kendim yazdım.
Dave Sag

1
Testleri de göndermiş olmanız harika! Ama beni asıl harekete geçiren şey oradaki güç alıntıydı! :)
Mauricio Moraes

4

Hallelujah'ın önerdiği gibi Mail gemini kullanmanın iyi bir yaklaşım olduğunu düşünüyorum . Ancak, oradaki bazı çemberlerden hoşlanmıyorum.

Kullanırım:

def self.is_valid?(email) 

  parser = Mail::RFC2822Parser.new
  parser.root = :addr_spec
  result = parser.parse(email)

  # Don't allow for a TLD by itself list (sam@localhost)
  # The Grammar is: (local_part "@" domain) / local_part ... discard latter
  result && 
     result.respond_to?(:domain) && 
     result.domain.dot_atom_text.elements.size > 1
end

Sen TLD'ler (üst düzey etki) olan talep ile sıkı olabilir bu listede ancak yeni TLD'leri (2012 ekleme gibi pop up olarak o listeyi güncellemek zorunda kalacak, .mobive .tel)

Ayrıştırıcıyı doğrudan bağlamanın avantajı, Mail dilbilgisindeki kuralların Mail geminin kullandığı kısımlar için oldukça geniş olmasıdır, user<user@example.com>SMTP için yaygın olan gibi bir adresi ayrıştırmasına izin verecek şekilde tasarlanmıştır . Onu tüketerek Mail::Addressbir sürü ekstra kontrol yapmak zorunda kalıyorsunuz.

Mail cevheriyle ilgili başka bir not, sınıf RFC2822 olarak adlandırılsa da, dilbilgisi bazı RFC5322 öğelerine sahiptir , örneğin bu test .


1
Bu pasaj için teşekkürler, Sam. Mail gem tarafından sağlanan genel bir "çoğu zaman yeterince iyi" doğrulama olmamasına biraz şaşırdım.
JD.

4

Rails 3'te , bu harika gönderinin açıkladığı gibi, yeniden kullanılabilir bir doğrulayıcı yazmak mümkündür :

http://archives.ryandaigle.com/articles/2009/8/11/what-s-new-in-edge-rails-independent-model-validators

class EmailValidator < ActiveRecord::Validator   
  def validate()
    record.errors[:email] << "is not valid" unless
    record.email =~ /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i   
  end
end

ve şununla kullanın validates_with:

class User < ActiveRecord::Base   
  validates_with EmailValidator
end

3

Diğer cevaplara dikkat çekerek, soru hala devam ediyor - neden bu konuda akıllı davranmaya çalışalım?

Birçok normal ifadenin reddedebileceği veya gözden kaçırabileceği uç durumların gerçek hacmi sorunlu görünüyor.

Sanırım soru 'neye ulaşmaya çalışıyorum?', E-posta adresini 'doğrulasanız' bile, aslında çalışan bir e-posta adresi olduğunu doğrulamıyorsunuz.

Eğer regexp için giderseniz, istemci tarafında @ varlığını kontrol edin.

Yanlış e-posta senaryosuna gelince, kodunuza bir 'mesaj gönderilemedi' şubesine sahip olun.


1

Temel olarak en yaygın 3 seçenek vardır:

  1. Regexp (tüm e-posta adresi regexp yoktur, bu yüzden kendinizinkini döndürün)
  2. MX sorgusu (kullandığınız şey budur)
  3. Bir aktivasyon jetonu oluşturma ve postalama (restful_authentication yolu)

Hem validates_email_veracity_of hem de belirteç oluşturmayı kullanmak istemiyorsanız, eski okul regexp kontrolünü tercih ederim.


1

Mail gem yerleşik bir adres ayrıştırıcıya sahiptir.

begin
  Mail::Address.new(email)
  #valid
rescue Mail::Field::ParseError => e
  #invalid
end

Rails 3.1'de benim için çalışmıyor gibi görünüyor. Mail :: Address.new ("john"), bir istisna oluşturmadan bana mutlu bir şekilde yeni bir Mail :: Address nesnesi döndürür.
jasoncrawford

Tamam, bazı durumlarda bir istisna oluşturacak ama hepsinde değil. @ Hallelujah'ın bağlantısı burada iyi bir yaklaşıma sahip görünüyor.
jasoncrawford

1

Bu çözüm, bir refactor ile @SFEley ve @Alessandro DS tarafından verilen yanıtlara ve kullanım açıklamasına dayanmaktadır.

Bu doğrulayıcı sınıfını modelinizde şu şekilde kullanabilirsiniz:

class MyModel < ActiveRecord::Base
  # ...
  validates :colum, :email => { :allow_nil => true, :message => 'O hai Mark!' }
  # ...
end

app/validatorsKlasörünüzde aşağıdakilere sahip olduğunuz varsayılırsa (Rails 3):

class EmailValidator < ActiveModel::EachValidator

  def validate_each(record, attribute, value)
    return options[:allow_nil] == true if value.nil?

    unless matches?(value)
      record.errors[attribute] << (options[:message] || 'must be a valid email address')
    end
  end

  def matches?(value)
    return false unless value

    if /\A[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]+\z/.match(value).nil?
      false
    else
      true
    end

  end
end

1

For Posta Listeleri Doğrulama . (Rails 4.1.6 kullanıyorum)

Normal ifademi buradan aldım . Çok eksiksiz gibi görünüyor ve çok sayıda kombinasyona karşı test edildi. Sonuçları o sayfada görebilirsiniz.

Bunu biraz Ruby regexp olarak değiştirdim ve lib/validators/email_list_validator.rb

İşte kod:

require 'mail'

class EmailListValidator < ActiveModel::EachValidator

  # Regexp source: https://fightingforalostcause.net/content/misc/2006/compare-email-regex.php
  EMAIL_VALIDATION_REGEXP   = Regexp.new('\A(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){255,})(?!(?:(?:\x22?\x5C[\x00-\x7E]\x22?)|(?:\x22?[^\x5C\x22]\x22?)){65,}@)(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22))(?:\.(?:(?:[\x21\x23-\x27\x2A\x2B\x2D\x2F-\x39\x3D\x3F\x5E-\x7E]+)|(?:\x22(?:[\x01-\x08\x0B\x0C\x0E-\x1F\x21\x23-\x5B\x5D-\x7F]|(?:\x5C[\x00-\x7F]))*\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\]))\z', true)

  def validate_each(record, attribute, value)
    begin
      invalid_emails = Mail::AddressList.new(value).addresses.map do |mail_address|
        # check if domain is present and if it passes validation through the regex
        (mail_address.domain.present? && mail_address.address =~ EMAIL_VALIDATION_REGEXP) ? nil : mail_address.address
      end

      invalid_emails.uniq!
      invalid_emails.compact!
      record.errors.add(attribute, :invalid_emails, :emails => invalid_emails.to_sentence) if invalid_emails.present?
    rescue Mail::Field::ParseError => e

      # Parse error on email field.
      # exception attributes are:
      #   e.element : Kind of element that was wrong (in case of invalid addres it is Mail::AddressListParser)
      #   e.value: mail adresses passed to parser (string)
      #   e.reason: Description of the problem. A message that is not very user friendly
      if e.reason.include?('Expected one of')
        record.errors.add(attribute, :invalid_email_list_characters)
      else
        record.errors.add(attribute, :invalid_emails_generic)
      end
    end
  end

end

Ve bunu modelde şöyle kullanıyorum:

validates :emails, :presence => true, :email_list => true

Bunun gibi posta listelerini farklı ayırıcılar ve synthax ile doğrular:

mail_list = 'John Doe <john@doe.com>, chuck@schuld.dea.th; David G. <david@pink.floyd.division.bell>'

Bu regexp'i kullanmadan önce kullandım Devise.email_regexp, ancak bu çok basit bir regexp ve ihtiyacım olan tüm vakaları alamadım. Bazı e-postalar dokundu.

Web'deki diğer normal ifadeleri denedim, ancak bu şimdiye kadarki en iyi sonuçları aldı. Umarım senin durumunda yardımcı olur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.