web-dev-qa-db-ja.com

Rails:リンク(URL)を検証する良い方法は何ですか?

RailsでURLをどのように検証するのが最善か疑問に思いました。正規表現を使用することを考えていましたが、これがベストプラクティスであるかどうかはわかりません。

また、正規表現を使用する場合、誰かが私に正規表現を提案できますか?私はまだ正規表現が初めてです。

119
jay

URLの検証は難しい作業です。また、非常に広範な要求です。

正確に何をしたいですか? URLの形式、存在、または何を検証しますか?何をしたいかによって、いくつかの可能性があります。

正規表現は、URLの形式を検証できます。しかし、複雑な正規表現であっても、有効なURLを処理していることを保証できません。

たとえば、単純な正規表現を使用する場合、おそらく次のホストを拒否します

http://invalid##Host.com

しかし、それは許可します

http://invalid-Host.foo

これは有効なホストですが、既存のTLDを考慮すると有効なドメインではありません。実際、次のドメインは有効なホスト名であるため、ドメインではなくホスト名を検証する場合、ソリューションは機能します。

http://Host.foo

また、次のもの

http://localhost

それでは、いくつかの解決策をご紹介しましょう。

ドメインを検証する場合は、正規表現を忘れる必要があります。現時点で利用可能な最良のソリューションは、Mozillaが管理しているPublic Suffix Listです。 Public Suffix Listに対してドメインを解析および検証するRubyライブラリを作成しました。これは PublicSuffix と呼ばれます。

URI/URLの形式を検証する場合は、正規表現を使用できます。検索する代わりに、組み込みのRuby URI.parseメソッドを使用します。

require 'uri'

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

さらに制限することもできます。たとえば、URLをHTTP/HTTPS URLにする場合、検証をより正確にすることができます。

require 'uri'

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

もちろん、パスやスキームのチェックなど、この方法に適用できる改善点はたくさんあります。

最後になりましたが、このコードをバリデーターにパッケージ化することもできます:

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
135
Simone Carletti

モデル内で1つのライナーを使用します。

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

使いやすくて十分だと思います。さらに、内部的にはまったく同じ正規表現を使用するため、理論的にはSimoneのメソッドと同等である必要があります。

98
Matteo Collina

Simoneのアイデアに従って、独自のバリデータを簡単に作成できます。

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

そして使用する

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

あなたのモデルで。

52
jlfenaux

validate_url gem もあります(これはAddressable::URI.parseソリューションの単なる素敵なラッパーです)。

追加するだけ

gem 'validate_url'

Gemfileに追加してから、モデルで次のことができます

validates :click_through_url, url: true
26
dolzenko

この質問はすでに回答されていますが、一体何なのか、私が使用しているソリューションを提案します。

正規表現は、私が出会ったすべてのURLで正常に機能します。 setterメソッドは、プロトコルが記載されていない場合は注意します(http://と仮定します)。

そして最後に、ページを取得しようとします。たぶん、HTTP 200 OKだけでなく、リダイレクトを受け入れるべきかもしれません。

# 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

そして...

# 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
14

valid_url gemを試すこともできます。これはスキームなしのURLを許可し、ドメインゾーンとIPホスト名をチェックします。

Gemfileに追加します。

gem 'valid_url'

そして、モデルで:

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

ちょうど私の2セント:

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

編集:パラメータのURLに一致するように正規表現を変更しました。

10
lafeber

私のために働いた解決策は次のとおりでした:

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

私はあなたが添付したいくつかの例を使用しようとしましたが、次のようにURLをサポートしています:

AとZの使用に注意してください。^と$を使用すると、Railsバリデータからこの警告セキュリティが表示されます。

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

 Invalid ones:
  'http://www.crowdint. com'
  'http://fake'
  'http:fake'
10
heriberto perez

最近同じ問題に遭遇しました(RailsアプリでURLを検証する必要がありました)が、Unicode URLの追加要件(例:http://кц.рф)に対処しなければなりませんでした...

私はいくつかのソリューションを調査し、次のことに気付きました。

  • 最初の最も推奨されることは、URI.parseを使用することです。詳細については、Simone Carlettiの回答を確認してください。これは問題なく動作しますが、Unicode URLでは動作しません。
  • 私が見た2番目の方法は、イリヤ・グリゴリックによるものでした: http://www.igvita.com/2006/09/07/validating-url-in-Ruby-on-Rails/ 基本的に、彼はURLへのリクエストを試みます。それが機能する場合、それは有効です...
  • 私が見つけた3番目の方法(および私が好む方法)は、URI.parseに似たアプローチですが、addressable stdlibの代わりにURI gemを使用します。このアプローチの詳細は次のとおりです。 http://rawsyntax.com/blog/url-validation-in-Rails-3-and-Ruby-in-general/
5
severin

David Jamesによって投稿された検証者 の更新バージョンです。 ベンジャミン・フライシャー発行 。その間、 here にある更新されたフォークをプッシュしました。

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

有効なアドレスとして解析される奇妙なHTTP URIがまだあることに注意してください。

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

addressable gemの問題 は、例をカバーしています。

4
JJD

上記のラバーソリューション にわずかなバリエーションを使用します。ホスト名に連続するドットを許可しません(たとえば、www.many...dots.comなど)。

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

URI.parseはスキームの接頭辞を義務付けているようです。これは場合によっては望んでいない場合があります(たとえば、ユーザーがTwitter.com/usernameなどの形式でURLをすばやくスペルできるようにする場合)

3
Franco

私は 'activevalidators' gemを使用しており、それはかなりうまく機能しています(URLの検証だけでなく)

あなたはそれを見つけることができます ここ

それはすべて文書化されていますが、基本的にgemが追加されたら、次の数行をイニシャライザに追加する必要があります:/config/environments/initializers/active_validators_activation.rb

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

(注:特定のタイプの値を検証する場合は、:allを:urlまたは:whateverに置き換えることができます)

そして、あなたのモデルにこのような何かを戻します

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

サーバーを再起動します

2
Arnaud Bouchot

次のようなものを使用して、複数のURLを検証できます。

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

最近、この同じ問題が発生し、有効なURLの回避策が見つかりました。

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メソッドの最初の部分は、URL形式を検証するのに十分です。 2番目の部分では、リクエストを送信してURLが存在することを確認します。

1
Dilnavaz

https://github.com/perfectline/validates_url は、あなたのためにほとんどすべてを行う素敵でシンプルな宝石です

1
stuartchaney

簡単な検証とカスタムエラーメッセージが必要な場合:

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

これには正規表現を使用できますが、私にとってはこれがうまく機能しています:

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

URL検証は、正規表現を使用するだけでは処理できません。Webサイトの数が増え続け、新しいドメイン命名スキームが登場し続けるためです。

私の場合、成功した応答をチェックするカスタムバリデータを作成するだけです。

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

record.pathを使用して、モデルのpath属性を検証しています。また、record.errors[:path]を使用して、エラーをそれぞれの属性名にプッシュしています。

これを単純に任意の属性名に置き換えることができます。

次に、モデル内のカスタムバリデーターを呼び出します。

class Url < ApplicationRecord

  # validations
  validates_presence_of :path
  validates_with UrlValidator

end
0
Noman Ur Rehman

有効を追加するためにURIモジュールをmonkeypatchするのが好きですか?方法

内部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
Blair Anderson

そして、モジュールとして

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

そして、URLを検証したいモデルのinclude UrlValidatorだけです。オプションを含めるだけです。

0
MCB