Rails: Comment valider les liens (URL)?

Je me demandais comment valider le mieux les URL dans Rails. Je pensais utiliser une expression régulière, mais je ne suis pas sûr que ce soit la meilleure pratique.

Et si je devais utiliser une regex, est-ce que quelqu’un pourrait m’en suggérer une? Je suis encore nouveau à Regex.

La validation d’une URL est un travail délicat. C’est aussi une demande très large.

Qu’est-ce que tu veux faire, exactement? Voulez-vous valider le format de l’URL, l’existence ou quoi? Il y a plusieurs possibilités, selon ce que vous voulez faire.

Une expression régulière peut valider le format de l’URL. Mais même une expression régulière complexe ne peut pas garantir que vous avez affaire à une URL valide.

Par exemple, si vous prenez une expression régulière simple, il rejettera probablement l’hôte suivant

http://invalid##host.com 

mais cela permettra

 http://invalid-host.foo 

c’est un hôte valide, mais pas un domaine valide si vous considérez les TLD existants. En effet, la solution fonctionnerait si vous souhaitez valider le nom d’hôte, pas le domaine car celui-ci est un nom d’hôte valide

 http://host.foo 

ainsi que le suivant

 http://localhost 

Maintenant, laissez-moi vous donner quelques solutions.

Si vous souhaitez valider un domaine, vous devez oublier les expressions régulières. La meilleure solution disponible actuellement est la liste de suffixes publics, une liste maintenue par Mozilla. J’ai créé une bibliothèque Ruby pour parsingr et valider les domaines par rapport à la liste des suffixes publics, appelée PublicSuffix .

Si vous souhaitez valider le format d’un URI / URL, vous pouvez utiliser des expressions régulières. Au lieu d’en chercher un, utilisez la méthode Ruby URI.parse .

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

Vous pouvez même décider de le rendre plus ressortingctif. Par exemple, si vous voulez que l’URL soit une URL HTTP / HTTPS, vous pouvez rendre la validation plus précise.

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

Bien sûr, il y a des tonnes d’améliorations que vous pouvez appliquer à cette méthode, y compris la vérification d’un chemin ou d’un schéma.

Last but not least, vous pouvez également empaqueter ce code dans un validateur:

 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 

J’utilise un seul liner à l’intérieur de mes modèles:

validates :url, :format => URI::regexp(%w(http https))

Je pense que c’est assez bon et simple à utiliser. De plus, il devrait être théoriquement équivalent à la méthode de Simone, car il utilise la même expression rationnelle en interne.

Suivant l’idée de Simone, vous pouvez facilement créer votre propre validateur.

 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 

et ensuite utiliser

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

dans votre modèle.

Il y a aussi validem_url gem (qui est juste une belle enveloppe pour la solution Addressable::URI.parse ).

Ajoutez simplement

 gem 'validate_url' 

à votre Gemfile , puis dans les modèles que vous pouvez

 validates :click_through_url, url: true 

Cette question a déjà été résolue, mais que diable, je propose la solution que j’utilise.

L’expression rationnelle fonctionne bien avec toutes les URL que j’ai rencontrées. La méthode setter doit faire attention si aucun protocole n’est mentionné (supposons http: //).

Et enfin, nous essayons de récupérer la page. Peut-être que je devrais accepter les redirections et pas seulement HTTP 200 OK.

 # app/models/my_model.rb validates :website, :allow_blank => true, :uri => { :format => /(^$)|(^(http|https):\/\/[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[az]{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_atsortingbute :website, url_str end 

et…

 # 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(atsortingbute, configuration[:message]) and false end rescue # Recover on DNS failures.. object.errors.add(atsortingbute, configuration[:message]) and false end else object.errors.add(atsortingbute, configuration[:message]) and false end end end 

Vous pouvez également essayer valid_url gem qui autorise les URL sans le schéma, vérifie la zone de domaine et les noms d’hôte ip.

Ajoutez-le à votre Gemfile:

gem 'valid_url'

Et puis dans le modèle:

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

Juste mes 2 centimes:

 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\.-]+)\.([az\.]{2,6})([\/\w \.-=\?]*)*\/?$/) end 

EDIT: modification de l'expression rationnelle pour correspondre aux parameters des URL.

La solution qui a fonctionné pour moi était:

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

J’ai essayé d’utiliser une partie de l’exemple que vous avez joint mais je supporte l’URL comme ceci:

Notez l’utilisation de A et Z car si vous utilisez ^ et $, vous verrez cet avertissement de sécurité provenant des validateurs 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' 

Je suis tombé sur le même problème dernièrement (j’avais besoin de valider les URL dans une application Rails) mais je devais faire face aux exigences supplémentaires des URLs Unicode (par exemple http://кц.рф ) …

J’ai recherché quelques solutions et suis tombé sur les suivants:

  • La première et la plus suggérée consiste à utiliser URI.parse . Vérifiez la réponse de Simone Carletti pour plus de détails. Cela fonctionne bien, mais pas pour les URL unicode.
  • La deuxième méthode que j’ai vue était celle d’Ilya Grigorik: http://www.igvita.com/2006/09/07/validating-url-in-ruby-on-rails/ Essentiellement, il essaie de faire une demande au url; si ça marche, c’est valide …
  • La troisième méthode que j’ai trouvée (et celle que je préfère) est une approche similaire à URI.parse mais utilisant le gem addressable au lieu de l’ URI stdlib. Cette approche est détaillée ici: http://rawsyntax.com/blog/url-validation-in-rails-3-and-ruby-in-general/

Voici une version mise à jour du validateur publié par David James . Il a été publié par Benjamin Fleischer . Pendant ce temps, j’ai poussé une fourchette mise à jour qui peut être trouvée ici .

 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 

Veuillez noter qu’il existe encore des URI HTTP étranges qui sont analysés en tant qu’adresses valides.

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

Voici un problème pour la gemme addressable qui couvre les exemples.

J’utilise une légère variation sur la solution lafeber ci-dessus . Il interdit les points consécutifs dans le nom d’hôte (comme par exemple dans www.many...dots.com ):

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

URI.parse semble imposer un préfixe de schéma, qui dans certains cas n’est pas ce que vous souhaitez (par exemple, si vous souhaitez autoriser vos utilisateurs à épeler rapidement des URL dans des formulaires tels que twitter.com/username )

J’ai utilisé la gem ‘activevalidators’ et ça marche plutôt bien (pas seulement pour la validation des URL)

vous pouvez le trouver ici

Tout est documenté, mais une fois le bijou ajouté, vous devrez append les quelques lignes suivantes dans un initialiseur: /config/environments/initializers/active_validators_activation.rb

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

(Note: vous pouvez remplacer: tous par: url ou: peu importe si vous voulez juste valider des types de valeurs spécifiques)

Et puis dans votre modèle quelque chose comme ça

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

Maintenant, redémarrez le serveur et ça devrait être ça

Vous pouvez valider plusieurs URL en utilisant quelque chose comme:

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

https://github.com/perfectline/validates_url est un joyau simple qui fera tout pour vous

Récemment, j’ai eu ce même problème et j’ai trouvé un moyen de contourner les URL valides.

 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 

La première partie de la méthode validate_url suffit à valider le format de l’url. La deuxième partie s’assurera que l’URL existe en envoyant une requête.

Et comme module

 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 

Et puis, include UrlValidator simplement include UrlValidator dans tout modèle pour lequel vous souhaitez valider les URL. Juste pour les options.

La validation d’URL ne peut pas être gérée simplement à l’aide d’une expression régulière, car le nombre de sites Web ne cesse de croître et de nouveaux schémas de nommage de domaine continuent à apparaître.

Dans mon cas, j’écris simplement un validateur personnalisé qui vérifie la réussite de la réponse.

 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 

Je valide l'atsortingbut path de mon modèle en utilisant record.path . Je pousse également l'erreur vers le nom de l'atsortingbut respectif en utilisant record.errors[:path] .

Vous pouvez simplement remplacer cela par n'importe quel nom d'atsortingbut.

Ensuite, j'appelle simplement le validateur personnalisé de mon modèle.

 class Url < ApplicationRecord # validations validates_presence_of :path validates_with UrlValidator end 

Vous pouvez utiliser regex pour cela, pour moi travaille bien celui-ci:

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

Si vous souhaitez une validation simple et un message d’erreur personnalisé:

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

J’ai aimé monkeypatch le module d’URI pour append le valide? méthode

dans 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