Rails: Bağlantıları (URL'leri) doğrulamanın iyi bir yolu nedir?


Yanıtlar:


151

Bir URL'yi doğrulamak zor bir iştir. Aynı zamanda çok geniş bir talep.

Tam olarak ne yapmak istiyorsun? URL'nin biçimini, varlığını veya ne olduğunu doğrulamak istiyor musunuz? Ne yapmak istediğinize bağlı olarak birkaç olasılık vardır.

Normal bir ifade, URL'nin biçimini doğrulayabilir. Ancak karmaşık bir düzenli ifade bile geçerli bir URL ile uğraştığınızı garanti edemez.

Örneğin, basit bir düzenli ifade alırsanız, muhtemelen aşağıdaki ana bilgisayarı reddedecektir

http://invalid##host.com

ama izin verecek

http://invalid-host.foo

bu geçerli bir ana bilgisayardır, ancak mevcut TLD'leri dikkate alırsanız geçerli bir etki alanı değildir. Aslında, çözüm, etki alanını değil ana bilgisayar adını doğrulamak istiyorsanız işe yarar çünkü aşağıdaki geçerli bir ana bilgisayar adıdır

http://host.foo

yanı sıra bir sonraki

http://localhost

Şimdi size bazı çözümler vereyim.

Bir alanı doğrulamak istiyorsanız, normal ifadeleri unutmanız gerekir. Şu anda mevcut olan en iyi çözüm, Mozilla tarafından sağlanan bir liste olan Genel Son Ek Listesi'dir. Etki alanlarını Genel Son Ek Listesi'ne göre ayrıştırmak ve doğrulamak için bir Ruby kitaplığı oluşturdum ve adı PublicSuffix .

Bir URI / URL'nin biçimini doğrulamak istiyorsanız, normal ifadeler kullanmak isteyebilirsiniz. Birini aramak yerine yerleşik Ruby URI.parseyöntemini kullanın.

require 'uri'

def valid_url?(uri)
  uri = URI.parse(uri) && !uri.host.nil?
rescue URI::InvalidURIError
  false
end

Hatta daha kısıtlayıcı hale getirmeye bile karar verebilirsiniz. Örneğin, URL'nin bir HTTP / HTTPS URL'si olmasını istiyorsanız, doğrulamayı daha doğru hale getirebilirsiniz.

require 'uri'

def valid_url?(url)
  uri = URI.parse(url)
  uri.is_a?(URI::HTTP) && !uri.host.nil?
rescue URI::InvalidURIError
  false
end

Elbette, bir yolu veya şemayı kontrol etmek de dahil olmak üzere bu yönteme uygulayabileceğiniz tonlarca iyileştirme vardır.

Son olarak, bu kodu bir doğrulayıcıya da paketleyebilirsiniz:

class HttpUrlValidator < ActiveModel::EachValidator

  def self.compliant?(value)
    uri = URI.parse(value)
    uri.is_a?(URI::HTTP) && !uri.host.nil?
  rescue URI::InvalidURIError
    false
  end

  def validate_each(record, attribute, value)
    unless value.present? && self.class.compliant?(value)
      record.errors.add(attribute, "is not a valid HTTP URL")
    end
  end

end

# in the model
validates :example_attribute, http_url: true

1
Sınıfın URI::HTTPShttps uris için olacağını unutmayın (örn:URI.parse("https://yo.com").class => URI::HTTPS
tee

12
URI::HTTPSmiras alır URI:HTTP, kullanmamın nedeni budur kind_of?.
Simone Carletti

1
Bir URL'yi güvenli bir şekilde doğrulamak için açık farkla en eksiksiz çözüm.
Fabrizio Regini

4
URI.parse('http://invalid-host.foo')bu URI geçerli bir URL olduğu için true döndürür. Ayrıca bunun .fooartık geçerli bir TLD olduğunu unutmayın . iana.org/domains/root/db/foo.html
Simone Carletti

1
@jmccartie lütfen yazının tamamını okuyun. Şemayı önemsiyorsanız, sadece o satırı değil, bir tür kontrolü de içeren son kodu kullanmalısınız. Yazının sonundan önce okumayı bıraktın.
Simone Carletti

101

Modellerimin içinde tek astar kullanıyorum:

validates :url, format: URI::regexp(%w[http https])

Bence yeterince iyi ve kullanımı basit. Dahası, dahili olarak aynı düzenli ifadeyi kullandığı için teorik olarak Simone'un yöntemine eşdeğer olmalıdır.


17
Maalesef 'http://'yukarıdaki modelle eşleşiyor. Bakınız:URI::regexp(%w(http https)) =~ 'http://'
David J.

15
Ayrıca bir url benzeri http:fakegeçerli olacaktır.
nathanvda

54

Simone'un fikrini takip ederek, kendi doğrulayıcınızı kolayca oluşturabilirsiniz.

class UrlValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    return if value.blank?
    begin
      uri = URI.parse(value)
      resp = uri.kind_of?(URI::HTTP)
    rescue URI::InvalidURIError
      resp = false
    end
    unless resp == true
      record.errors[attribute] << (options[:message] || "is not an url")
    end
  end
end

ve sonra kullan

validates :url, :presence => true, :url => true

senin modelinde.


1
bu sınıfı nereye koymalıyım? Bir başlatıcıda mı?
Deb

3
@Gbc'den alıntı yapıyorum: "Özel doğrulayıcılarınızı app / doğrulayıcılara yerleştirirseniz, config / application.rb dosyanızı değiştirmenize gerek kalmadan otomatik olarak yüklenecekler." ( stackoverflow.com/a/6610270/839847 ). Stefan Pettersson'un aşağıdaki cevabının "uygulama / doğrulayıcılara" da benzer bir dosya kaydettiğini gösterdiğini unutmayın.
bergie3000

4
bu yalnızca
url'nin

1
URL'nin isteğe bağlı olmasını karşılayabiliyorsanız sonlandırın: class OptionalUrlValidator <UrlValidator def validate_each (kayıt, öznitelik, değer) true if value.blank? dönüş süper son sonu
Dirty Henry

1
Bu iyi bir doğrulama değil:URI("http:").kind_of?(URI::HTTP) #=> true
2016

29

Ayrıca validate_url gem ( Addressable::URI.parseçözüm için güzel bir sarmalayıcı ) vardır.

Sadece ekle

gem 'validate_url'

Blogunuza Gemfileve sonra yapabilirsiniz modellerinde

validates :click_through_url, url: true

@ Zorunludur, çünkü spesifikasyona göre geçerli olabilir, ancak github.com/sporkmonger/addressable/issues adresini kontrol etmek isteyebilirsiniz . Ayrıca genel durumda, hiç kimsenin standardı takip etmediğini ve bunun yerine basit format doğrulamasını kullandığını gördük.
dolzenko

13

Bu soru zaten cevaplandı, ama ne halt, kullandığım çözümü öneriyorum.

Normal ifade, karşılaştığım tüm url'lerle iyi çalışıyor. Ayarlayıcı yöntemi, herhangi bir protokolden bahsedilmemesine dikkat etmektir (http: // varsayalım).

Ve son olarak, sayfayı getirmeye çalışıyoruz. Belki de yeniden yönlendirmeleri kabul etmeliyim ve sadece HTTP 200 OK değil.

# app/models/my_model.rb
validates :website, :allow_blank => true, :uri => { :format => /(^$)|(^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(([0-9]{1,5})?\/.*)?$)/ix }

def website= url_str
  unless url_str.blank?
    unless url_str.split(':')[0] == 'http' || url_str.split(':')[0] == 'https'
        url_str = "http://" + url_str
    end
  end  
  write_attribute :website, url_str
end

ve...

# app/validators/uri_vaidator.rb
require 'net/http'

# Thanks Ilya! http://www.igvita.com/2006/09/07/validating-url-in-ruby-on-rails/
# Original credits: http://blog.inquirylabs.com/2006/04/13/simple-uri-validation/
# HTTP Codes: http://www.ruby-doc.org/stdlib/libdoc/net/http/rdoc/classes/Net/HTTPResponse.html

class UriValidator < ActiveModel::EachValidator
  def validate_each(object, attribute, value)
    raise(ArgumentError, "A regular expression must be supplied as the :format option of the options hash") unless options[:format].nil? or options[:format].is_a?(Regexp)
    configuration = { :message => I18n.t('errors.events.invalid_url'), :format => URI::regexp(%w(http https)) }
    configuration.update(options)

    if value =~ configuration[:format]
      begin # check header response
        case Net::HTTP.get_response(URI.parse(value))
          when Net::HTTPSuccess then true
          else object.errors.add(attribute, configuration[:message]) and false
        end
      rescue # Recover on DNS failures..
        object.errors.add(attribute, configuration[:message]) and false
      end
    else
      object.errors.add(attribute, configuration[:message]) and false
    end
  end
end

gerçekten temiz! girdiniz için teşekkürler, genellikle bir soruna birçok yaklaşım vardır; insanların kendi kartlarını paylaşması harika.
jay

6
Ray güvenlik kılavuzuna göre bu regexp'de $ ^ yerine \ A ve \ z kullanmanız gerektiğini belirtmek istedim
Jared

1
Bunu sevdim. Modeller arasında tutarlı olmasını isteyeceğinizi düşündüğüm için normal ifadeyi doğrulayıcıya taşıyarak kodu biraz kurutmak için hızlı bir öneri. Bonus: validate_each'in altına ilk satırı bırakmanıza izin verir.
Paul Pettengill

Ya url uzun sürüyorsa ve zaman aşımına uğruyorsa? Zaman aşımı hata mesajını göstermek için veya sayfa açılamıyorsa en iyi seçenek ne olacaktır?
user588324

bu asla bir güvenlik denetiminden geçmez, sunucularınızın keyfi bir url'yi dürtmesini sağlıyorsunuz
Mauricio

12

Şema olmadan URL'lere izin veren, etki alanı bölgesini ve ip-ana bilgisayar adlarını kontrol eden valid_url gem'i de deneyebilirsiniz .

Gemfile'ınıza ekleyin:

gem 'valid_url'

Ve sonra modelde:

class WebSite < ActiveRecord::Base
  validates :url, :url => true
end

Bu çok güzel, özellikle de URI sınıfıyla şaşırtıcı bir şekilde ilgili olan şemasız URL'ler.
Paul Pettengill

Bu cevherin IP tabanlı URL'leri kazma ve sahte olanları tespit etme yeteneği beni şaşırttı. Teşekkürler!
Oz

10

Sadece 2 sentim:

before_validation :format_website
validate :website_validator

private

def format_website
  self.website = "http://#{self.website}" unless self.website[/^https?/]
end

def website_validator
  errors[:website] << I18n.t("activerecord.errors.messages.invalid") unless website_valid?
end

def website_valid?
  !!website.match(/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-=\?]*)*\/?$/)
end

DÜZENLE: normal ifade parametre url'leri ile eşleşecek şekilde değiştirildi.


1
katkılarınız için teşekkürler, farklı çözümler görmek her zaman güzel
jay

Btw, http://test.com/fdsfsdf?a=b
regexp'iniz

2
Bu kodu üretime koyduk ve .match regex satırındaki sonsuz döngülerde zaman aşımına uğramaya devam ettik. Neden olduğundan emin değilim, sadece bazı köşe kılıfları için dikkatli olun ve bunun neden olacağına dair başkalarının düşüncelerini duymak isterim.
toobulkeh

10

Benim için işe yarayan çözüm şuydu:

validates_format_of :url, :with => /\A(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w\.-]*)*\/?\Z/i

Eklediğiniz örneklerin bir kısmını kullanmayı denedim ama url'yi şöyle destekliyorum:

A ve Z'nin kullanımına dikkat edin çünkü ^ ve $ kullanırsanız bu uyarı güvenliğini Rails doğrulayıcılarından göreceksiniz.

 Valid ones:
 'www.crowdint.com'
 'crowdint.com'
 'http://crowdint.com'
 'http://www.crowdint.com'

 Invalid ones:
  'http://www.crowdint. com'
  'http://fake'
  'http:fake'

1
Bunu ile deneyin "https://portal.example.com/portal/#". Ruby 2.1.6'da değerlendirme kilitleniyor.
Old Pro

haklısın gibi görünüyor bazı durumlarda bu normal ifadenin çözülmesi sonsuza kadar sürer :(
heriberto perez

1
açıkça, her senaryoyu kapsayan bir normal ifade yok, bu yüzden sadece basit bir doğrulama kullanıyorum: validates: url, format: {with: URI.regexp}, if: Proc.new {| a | a.url.present? }
heriberto perez

5

Son zamanlarda aynı problemle karşılaştım (bir Rails uygulamasında url'leri doğrulamam gerekiyordu) ancak ek unicode url gereksinimleriyle (örneğin http://кц.рф) başa çıkmak zorunda kaldım ...

Birkaç çözüm araştırdım ve şunlarla karşılaştım:


Evet, Addressable::URI.parse('http:///').scheme # => "http"veya Addressable::URI.parse('Съешь [же] ещё этих мягких французских булок да выпей чаю')Addressable'ın bakış açısından tamamen uygun :(
smileart

4

İşte David James tarafından yayınlanan doğrulayıcının güncellenmiş bir sürümü . Bu edilmiş Benjamin Fleischer tarafından yayınlanan . Bu arada, burada bulunabilecek güncellenmiş bir çatal ittim .

require 'addressable/uri'

# Source: http://gist.github.com/bf4/5320847
# Accepts options[:message] and options[:allowed_protocols]
# spec/validators/uri_validator_spec.rb
class UriValidator < ActiveModel::EachValidator

  def validate_each(record, attribute, value)
    uri = parse_uri(value)
    if !uri
      record.errors[attribute] << generic_failure_message
    elsif !allowed_protocols.include?(uri.scheme)
      record.errors[attribute] << "must begin with #{allowed_protocols_humanized}"
    end
  end

private

  def generic_failure_message
    options[:message] || "is an invalid URL"
  end

  def allowed_protocols_humanized
    allowed_protocols.to_sentence(:two_words_connector => ' or ')
  end

  def allowed_protocols
    @allowed_protocols ||= [(options[:allowed_protocols] || ['http', 'https'])].flatten
  end

  def parse_uri(value)
    uri = Addressable::URI.parse(value)
    uri.scheme && uri.host && uri
  rescue URI::InvalidURIError, Addressable::URI::InvalidURIError, TypeError
  end

end

...

require 'spec_helper'

# Source: http://gist.github.com/bf4/5320847
# spec/validators/uri_validator_spec.rb
describe UriValidator do
  subject do
    Class.new do
      include ActiveModel::Validations
      attr_accessor :url
      validates :url, uri: true
    end.new
  end

  it "should be valid for a valid http url" do
    subject.url = 'http://www.google.com'
    subject.valid?
    subject.errors.full_messages.should == []
  end

  ['http://google', 'http://.com', 'http://ftp://ftp.google.com', 'http://ssh://google.com'].each do |invalid_url|
    it "#{invalid_url.inspect} is a invalid http url" do
      subject.url = invalid_url
      subject.valid?
      subject.errors.full_messages.should == []
    end
  end

  ['http:/www.google.com','<>hi'].each do |invalid_url|
    it "#{invalid_url.inspect} is an invalid url" do
      subject.url = invalid_url
      subject.valid?
      subject.errors.should have_key(:url)
      subject.errors[:url].should include("is an invalid URL")
    end
  end

  ['www.google.com','google.com'].each do |invalid_url|
    it "#{invalid_url.inspect} is an invalid url" do
      subject.url = invalid_url
      subject.valid?
      subject.errors.should have_key(:url)
      subject.errors[:url].should include("is an invalid URL")
    end
  end

  ['ftp://ftp.google.com','ssh://google.com'].each do |invalid_url|
    it "#{invalid_url.inspect} is an invalid url" do
      subject.url = invalid_url
      subject.valid?
      subject.errors.should have_key(:url)
      subject.errors[:url].should include("must begin with http or https")
    end
  end
end

Lütfen hala geçerli adresler olarak ayrıştırılan garip HTTP URI'ları olduğuna dikkat edin.

http://google  
http://.com  
http://ftp://ftp.google.com  
http://ssh://google.com

İşte örnekleri kapsayan mücevher içinaddressable bir konu .


3

Yukarıdaki lafeber çözümünde hafif bir varyasyon kullanıyorum . Ana bilgisayar adında art arda noktalara izin vermez (örneğin, içinde www.many...dots.com):

%r"\A(https?://)?[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]{2,6}(/.*)?\Z"i

URI.parseşema ön ekini zorunlu kılıyor gibi görünüyor, ki bu bazı durumlarda isteyebileceğiniz şey değildir (örneğin, kullanıcılarınızın URL'leri aşağıdaki gibi biçimlerde hızlı bir şekilde yazmasına izin vermek istiyorsanız twitter.com/username)


2

'Activevalidators' taşını kullanıyorum ve oldukça iyi çalışıyor (sadece url doğrulaması için değil)

onu burada bulabilirsin

Hepsi belgelendi, ancak temelde mücevher eklendiğinde, aşağıdaki birkaç satırı bir başlatıcıya eklemek isteyeceksiniz: /config/environment/initializers/active_validators_activation.rb

# Activate all the validators
ActiveValidators.activate(:all)

(Not: sadece belirli değer türlerini doğrulamak istiyorsanız: tümünü: url ile veya: her neyse değiştirebilirsiniz)

Ve sonra modelinize geri döndüğünüzde bunun gibi bir şey

class Url < ActiveRecord::Base
   validates :url, :presence => true, :url => true
end

Şimdi sunucuyu yeniden başlatın ve bu olmalı


2

Basit bir doğrulama ve özel bir hata mesajı istiyorsanız:

  validates :some_field_expecting_url_value,
            format: {
              with: URI.regexp(%w[http https]),
              message: 'is not a valid URL'
            }

1

Aşağıdaki gibi bir şey kullanarak birden çok url'yi doğrulayabilirsiniz:

validates_format_of [:field1, :field2], with: URI.regexp(['http', 'https']), allow_nil: true

1
Şema olmadan URL'leri nasıl ele alırdınız (ör. Www.bar.com/foo)?
craig


1

Son zamanlarda aynı sorunu yaşadım ve geçerli URL'ler için bir çözüm buldum.

validates_format_of :url, :with => URI::regexp(%w(http https))
validate :validate_url
def validate_url

  unless self.url.blank?

    begin

      source = URI.parse(self.url)

      resp = Net::HTTP.get_response(source)

    rescue URI::InvalidURIError

      errors.add(:url,'is Invalid')

    rescue SocketError 

      errors.add(:url,'is Invalid')

    end



  end

Validate_url yönteminin ilk bölümü url biçimini doğrulamak için yeterlidir. İkinci bölüm, bir istek göndererek url'nin var olduğundan emin olacaktır.


Ya url çok büyük bir kaynağa işaret ederse (örneğin, birden çok gigabayt)?
Jon Schneider

@JonSchneider bir http head isteği ( burada olduğu gibi ) get yerine kullanabilir.
wvengen

1

Geçerli olanı eklemek için URI modülünü maymunla yüklemeyi sevdim. yöntem

içeride config/initializers/uri.rb

module URI
  def self.valid?(url)
    uri = URI.parse(url)
    uri.is_a?(URI::HTTP) && !uri.host.nil?
  rescue URI::InvalidURIError
    false
  end
end

0

Ve bir modül olarak

module UrlValidator
  extend ActiveSupport::Concern
  included do
    validates :url, presence: true, uniqueness: true
    validate :url_format
  end

  def url_format
    begin
      errors.add(:url, "Invalid url") unless URI(self.url).is_a?(URI::HTTP)
    rescue URI::InvalidURIError
      errors.add(:url, "Invalid url")
    end
  end
end

Ve sonra include UrlValidatorURL'leri doğrulamak istediğiniz herhangi bir modelde. Sadece seçenekler için dahil.


0

Web sitelerinin sayısı arttıkça ve yeni alan adlandırma şemaları ortaya çıkmaya devam ettiğinden, URL doğrulaması yalnızca Normal İfade kullanılarak gerçekleştirilemez.

Benim durumumda, başarılı bir yanıtı kontrol eden özel bir doğrulayıcı yazıyorum.

class UrlValidator < ActiveModel::Validator
  def validate(record)
    begin
      url = URI.parse(record.path)
      response = Net::HTTP.get(url)
      true if response.is_a?(Net::HTTPSuccess)   
    rescue StandardError => error
      record.errors[:path] << 'Web address is invalid'
      false
    end  
  end
end

pathModelimin özniteliğini kullanarak doğruluyorum record.path. Ayrıca kullanarak hatayı ilgili öznitelik adına itiyorum record.errors[:path].

Bunu herhangi bir öznitelik adıyla değiştirebilirsiniz.

Ardından, modelimdeki özel doğrulayıcıyı çağırırım.

class Url < ApplicationRecord

  # validations
  validates_presence_of :path
  validates_with UrlValidator

end

Ya url çok büyük bir kaynağa işaret ederse (örneğin, birden çok gigabayt)?
Jon Schneider

0

Bunun için normal ifadeyi kullanabilirsin, benim için bu iyi çalışıyor:

(^|[\s.:;?\-\]<\(])(ftp|https?:\/\/[-\w;\/?:@&=+$\|\_.!~*\|'()\[\]%#,]+[\w\/#](\(\))?)(?=$|[\s',\|\(\).:;?\-\[\]>\)])
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.