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


125

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

そして、もし私が正規表現を使うとしたら、誰かが私にそれを提案することができますか?私はまだRegexを初めて使用します。


回答:


151

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

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

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

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

http://invalid##host.com

しかし、それは可能になります

http://invalid-host.foo

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

http://host.foo

同様に次のもの

http://localhost

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

ドメインを検証する場合は、正規表現を忘れる必要があります。現時点で利用可能な最良のソリューションは、Mozillaが管理するリストであるパブリックサフィックスリストです。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

1
クラスがされることに注意してくださいURI::HTTPS:httpsのURIの(元のためにURI.parse("https://yo.com").class => URI::HTTPS
ティー

12
URI::HTTPSから継承しているURI:HTTP、それが私がを使用する理由ですkind_of?
Simone Carletti、2013年

1
安全にURLを検証するための最も完全なソリューション。
Fabrizio

4
URI.parse('http://invalid-host.foo')そのURIは有効なURLであるため、trueを返します。また、これ.fooは有効なTLDになりました。iana.org/domains/root/db/foo.html
Simone Carletti、2015年

1
@jmccartie投稿全体をお読みください。スキームに関心がある場合は、その行だけでなく、型チェックも含む最終的なコードを使用する必要があります。投稿が終わる前に読書をやめました。
Simone Carletti、2015

101

私はモデル内でワンライナーを使用しています:

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

シンプルで使い勝手も良いと思います。さらに、内部的には非常に同じ正規表現を使用するため、理論的にはSimoneの方法と同等でなければなりません。


17
残念ながら'http://'、上記のパターンと一致します。参照:URI::regexp(%w(http https)) =~ 'http://'
David J.

15
のようなURL http:fakeも有効になります。
nathanvda

54

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

モデルで。


1
このクラスはどこに置くべきですか?初期化子で?
deb

3
@gbcから引用:「カスタムバリデーターをapp / validatorsに配置すると、config / application.rbファイルを変更する必要なく、自動的にロードされます。」(stackoverflow.com/a/6610270/839847)。Stefan Petterssonからの以下の回答は、「app / validators」にも同様のファイルを保存したことを示していることに注意してください。
bergie3000 2012年

4
これは、URLがhttp://またはhttps://で始まるかどうかのみをチェックします。これは適切なURL検証ではありません
maggix

1
URLをオプションにする余裕がある場合は終了します。クラスOptionalUrlValidator <UrlValidator def validate_each(record、attribute、value)value.blank?スーパーエンドを返す
ダーティヘンリー

1
これは適切な検証ではありませんURI("http:").kind_of?(URI::HTTP) #=> true
。– smathy

29

ありvalidate_url宝石(のためだけの素敵なラッパーであるAddressable::URI.parse溶液)。

追加するだけ

gem 'validate_url'

Gemfile、そしてモデルでは次のことができます

validates :click_through_url, url: true

ЕвгенийМасленков@それは、その有効応じスペックにも全く同様であるかもしれないが、あなたは確認することをお勧めしますgithub.com/sporkmonger/addressable/issuesを。また、一般的なケースでは、誰も標準に従っておらず、代わりに単純なフォーマット検証を使用していることがわかりました。
dolzenko 14年

13

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

正規表現は、出会ったすべての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

本当にすっきり!ご協力ありがとうございます。問題には多くのアプローチがあります。人々が自分のものを共有するのは素晴らしいことです。
ジェイ

6
Railsセキュリティガイドによると、正規表現では$ ^ではなく\ Aと\ zを使用する必要があることを指摘したいと思います
Jared

1
私はそれが好きです。正規表現をバリデーターに移動することで、コードを少し乾かすための簡単な提案です。モデル全体でコードを一貫させる必要があると思います。おまけ:最初の行をvalidate_eachの下にドロップできます。
ポールペッテンギル2013

URLに時間がかかり、タイムアウトになるとどうなりますか?タイムアウトエラーメッセージを表示したり、ページを開けない場合に最適なオプションは何ですか?
user588324 14

これはセキュリティ監査に合格することはなく、サーバーに任意のURLを突き刺すことになります
Mauricio

12

スキームなしでURLを許可し、ドメインゾーンとip-hostnamesをチェックするvalid_url gemを試すこともできます。

それをGemfileに追加します。

gem 'valid_url'

そしてモデルでは:

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

これは特に、スキーマなしのURLはとても素晴らしいです。これは驚くほどURIクラスに関係しています。
Paul Pettengill、2015

この宝石がIPベースのURLを掘り下げて偽のURLを検出する機能に驚いた。ありがとう!
オズのウィズ2017

10

ちょうど私の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に一致するように正規表現を変更しました。


1
あなたの入力に感謝し、常にさまざまな解決策を見るのが良い
ジェイ

ところで、正規表現は次のようなクエリ文字列で有効なURLを拒否しますhttp://test.com/fdsfsdf?a=b
MikDiet

2
このコードを本番環境に導入し、.match regex行の無限ループでタイムアウトを取得し続けました。理由はわからないが、いくつかのコーナーケースに注意し、なぜこれが発生するのかについて他の人の考えを聞きたい。
toobulkeh

10

私のために働いた解決策は:

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'

1
でこれを試してください"https://portal.example.com/portal/#"。Ruby 2.1.6では評価がハングします。
Old Pro、

あなたの言う通り、場合によっては、この正規表現を解決するのに永遠にかかる:(
heriberto perez

1
明らかに、すべてのシナリオをカバーする正規表現はありません。そのため、単純な検証だけを使用することになります:validates:url、format:{with:URI.regexp}、if:Proc.new {| a | a.url.present?}
heriberto perez

5

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

私はいくつかの解決策を調査し、以下に出くわしました:

  • 最初に推奨されるのは、を使用することですURI.parse。詳細はSimone Carlettiの回答を確認してください。これは問題なく動作しますが、UnicodeのURLでは動作しません。
  • 私が見た2番目の方法は、Ilya Grigorikによるものでした:http ://www.igvita.com/2006/09/07/validating-url-in-ruby-on-rails/ 基本的に、彼はurl; 動作する場合、それは有効です...
  • 私が見つけた3番目の方法(および私が好む方法)は、stdlibの代わりにgem URI.parseを使用するのと同様の方法です。このアプローチの詳細は次のとおりです。http//rawsyntax.com/blog/url-validation-in-rails-3-and-ruby-in-general/addressableURI

ええ、でも、Addressable::URI.parse('http:///').scheme # => "http"またはAddressable::URI.parse('Съешь [же] ещё этих мягких французских булок да выпей чаю')Addressableの観点から完全に大丈夫です:(
smileart

4

これは、David Jamesによって投稿されバリデーターの更新バージョンです。ベンジャミン・フライシャーから出版されています。その間、私はここにある更新されたフォークをプッシュしまし

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

以下は、例をカバーするgemの問題ですaddressable


3

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

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

URI.parseスキームのプレフィックスを義務付けているようですが、場合によってはこれが適切でない場合があります(たとえば、ユーザーがのような形式のURLをすばやく入力できるようにする場合twitter.com/username


2

私は'activevalidators' gemを使用しており、(url検証だけでなく)非常にうまく機能します

ここで見つけることができます

これはすべてドキュメント化されていますが、基本的にはgemが追加されたら、次の数行をイニシャライザのsayに追加する必要があります。/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

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

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

1

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

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

1
スキームなしでURLをどのように処理しますか(例:www.bar.com/foo)?
クレイグ


1

最近同じ問題が発生し、有効な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が存在することを確認します。


URLが非常に大きいリソース(たとえば、数ギガバイト)を指している場合はどうなりますか?
Jon Schneider

@JonSchneider getの代わりにhttp headリクエスト(ここのような)を使用できます。
wvengen

1

有効なURIモジュールを追加するために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

そしてモジュールとして

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

そして、include UrlValidatorあなたがURLを検証したい任意のモデルで。オプションのためだけを含みます。


0

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

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

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

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

これを任意の属性名に置き換えるだけです。

次に、モデルでカスタムバリデーターを呼び出すだけです。

class Url < ApplicationRecord

  # validations
  validates_presence_of :path
  validates_with UrlValidator

end

URLが非常に大きいリソース(たとえば、数ギガバイト)を指している場合はどうなりますか?
Jon Schneider

0

あなたはこれに正規表現を使うことができます、私にとってこれはうまくいきます:

(^|[\s.:;?\-\]<\(])(ftp|https?:\/\/[-\w;\/?:@&=+$\|\_.!~*\|'()\[\]%#,]+[\w\/#](\(\))?)(?=$|[\s',\|\(\).:;?\-\[\]>\)])
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.