Eu queria saber como validar melhor os URLs no Rails. Eu estava pensando em usar uma expressão regular, mas não tenho certeza se essa é a melhor prática.
E, se eu usasse uma regex, alguém poderia me sugerir uma? Eu ainda sou novo no Regex.
Eu queria saber como validar melhor os URLs no Rails. Eu estava pensando em usar uma expressão regular, mas não tenho certeza se essa é a melhor prática.
E, se eu usasse uma regex, alguém poderia me sugerir uma? Eu ainda sou novo no Regex.
Respostas:
Validar um URL é um trabalho complicado. Também é uma solicitação muito ampla.
O que você quer fazer exatamente? Deseja validar o formato da URL, a existência ou o quê? Existem várias possibilidades, dependendo do que você deseja fazer.
Uma expressão regular pode validar o formato do URL. Mas mesmo uma expressão regular complexa não pode garantir que você esteja lidando com um URL válido.
Por exemplo, se você usar uma expressão regular simples, ela provavelmente rejeitará o seguinte host
http://invalid##host.com
mas permitirá
http://invalid-host.foo
esse é um host válido, mas não um domínio válido se você considerar os TLDs existentes. De fato, a solução funcionaria se você deseja validar o nome do host, não o domínio, porque o seguinte é um nome de host válido
http://host.foo
bem o seguinte
http://localhost
Agora, deixe-me dar algumas soluções.
Se você deseja validar um domínio, precisa esquecer as expressões regulares. A melhor solução disponível no momento é a Public Suffix List, uma lista mantida pela Mozilla. Criei uma biblioteca Ruby para analisar e validar domínios com relação à lista pública de sufixos, chamada PublicSuffix .
Se você deseja validar o formato de um URI / URL, convém usar expressões regulares. Em vez de procurar um, use o URI.parse
método Ruby interno.
require 'uri'
def valid_url?(uri)
uri = URI.parse(uri) && !uri.host.nil?
rescue URI::InvalidURIError
false
end
Você pode até decidir torná-lo mais restritivo. Por exemplo, se você quiser que o URL seja um URL HTTP / HTTPS, poderá tornar a validação mais precisa.
require 'uri'
def valid_url?(url)
uri = URI.parse(url)
uri.is_a?(URI::HTTP) && !uri.host.nil?
rescue URI::InvalidURIError
false
end
Obviamente, existem inúmeras melhorias que você pode aplicar a esse método, incluindo a verificação de um caminho ou esquema.
Por último, mas não menos importante, você também pode empacotar esse código em um validador:
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
URI::HTTPS
para https uris (ex:URI.parse("https://yo.com").class => URI::HTTPS
URI::HTTPS
herda URI:HTTP
, é por isso que eu uso kind_of?
.
URI.parse('http://invalid-host.foo')
retorna true porque esse URI é um URL válido. Observe também que .foo
agora é um TLD válido. iana.org/domains/root/db/foo.html
Eu uso um liner dentro dos meus modelos:
validates :url, format: URI::regexp(%w[http https])
Eu acho que é bom o suficiente e simples de usar. Além disso, deveria ser teoricamente equivalente ao método de Simone, pois usa o mesmo regexp internamente.
'http://'
corresponde ao padrão acima. Veja:URI::regexp(%w(http https)) =~ 'http://'
http:fake
será válido.
Seguindo a ideia de Simone, você pode criar facilmente seu próprio validador.
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
e depois use
validates :url, :presence => true, :url => true
no seu modelo.
URI("http:").kind_of?(URI::HTTP) #=> true
Também há uma validate_url gem (que é apenas um bom invólucro para a Addressable::URI.parse
solução).
Basta adicionar
gem 'validate_url'
ao seu Gemfile
e, em modelos, você pode
validates :click_through_url, url: true
Esta pergunta já está respondida, mas que diabos, proponho a solução que estou usando.
O regexp funciona bem com todos os URLs que conheci. O método setter é tomar cuidado se nenhum protocolo for mencionado (vamos assumir http: //).
E, finalmente, tentamos buscar a página. Talvez eu deva aceitar redirecionamentos e não apenas 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
e...
# 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
Você também pode tentar a gem valid_url, que permite URLs sem o esquema, verifica a zona do domínio e os nomes de host IP.
Adicione-o ao seu Gemfile:
gem 'valid_url'
E então no modelo:
class WebSite < ActiveRecord::Base
validates :url, :url => true
end
Apenas meus 2 centavos:
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
EDIT: mudou a regex para corresponder aos URLs dos parâmetros.
http://test.com/fdsfsdf?a=b
A solução que funcionou para mim foi:
validates_format_of :url, :with => /\A(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w\.-]*)*\/?\Z/i
Eu tentei usar alguns dos exemplos anexados, mas estou suportando o URL da seguinte forma:
Observe o uso de A e Z porque se você usar ^ e $, verá este aviso de segurança dos validadores do 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'
"https://portal.example.com/portal/#"
. No Ruby 2.1.6, a avaliação trava.
Ultimamente, encontrei o mesmo problema (eu precisava validar URLs em um aplicativo Rails), mas precisava atender aos requisitos adicionais de URLs unicode (por exemplo http://кц.рф
) ...
Eu pesquisei algumas soluções e me deparei com o seguinte:
URI.parse
. Verifique a resposta de Simone Carletti para obter detalhes. Isso funciona bem, mas não para URLs unicode.URI.parse
mas usando a addressable
gem em vez do URI
stdlib. Essa abordagem é detalhada aqui: http://rawsyntax.com/blog/url-validation-in-rails-3-and-ruby-in-general/Addressable::URI.parse('http:///').scheme # => "http"
ou Addressable::URI.parse('Съешь [же] ещё этих мягких французских булок да выпей чаю')
são perfeitamente ok do ponto de vista :( do endereçável
Aqui está uma versão atualizada do validador postada por David James . Foi publicado por Benjamin Fleischer . Enquanto isso, empurrei um fork atualizado, que pode ser encontrado aqui .
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
Observe que ainda existem URIs HTTP estranhos que são analisados como endereços válidos.
http://google
http://.com
http://ftp://ftp.google.com
http://ssh://google.com
Aqui está um problema para a addressable
gema que cobre os exemplos.
Eu uso uma ligeira variação na solução Lafeber acima . Não permite pontos consecutivos no nome do host (como por exemplo em www.many...dots.com
):
%r"\A(https?://)?[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]{2,6}(/.*)?\Z"i
URI.parse
parece exigir a prefixação do esquema, que em alguns casos não é o que você deseja (por exemplo, se você deseja permitir que seus usuários escrevam rapidamente URLs em formas como twitter.com/username
)
Eu tenho usado a gema 'activevalidators' e funciona muito bem (não apenas para validação de URLs)
você pode encontrá-lo aqui
Está tudo documentado, mas basicamente, uma vez adicionada a gema, você adicionará as seguintes linhas em um inicializador: /config/environments/initializers/active_validators_activation.rb
# Activate all the validators
ActiveValidators.activate(:all)
(Nota: você pode substituir: all por: url ou: what, se você quiser apenas validar tipos específicos de valores)
E então, de volta ao seu modelo, algo como isto
class Url < ActiveRecord::Base
validates :url, :presence => true, :url => true
end
Agora reinicie o servidor e deve ser
Você pode validar vários URLs usando algo como:
validates_format_of [:field1, :field2], with: URI.regexp(['http', 'https']), allow_nil: true
https://github.com/perfectline/validates_url é uma jóia agradável e simples que fará praticamente tudo por você
Recentemente, tive esse mesmo problema e encontrei uma solução alternativa para URLs válidos.
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
A primeira parte do método validate_url é suficiente para validar o formato da URL. A segunda parte garantirá que o URL exista enviando uma solicitação.
Eu gostava de monkeypatch o módulo URI para adicionar o válido? método
dentro 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
E como um módulo
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
E, em seguida, apenas include UrlValidator
em qualquer modelo para o qual você deseja validar os URLs. Apenas incluindo opções.
A validação de URL não pode ser gerenciada simplesmente usando uma Expressão regular, pois o número de sites continua crescendo e novos esquemas de nomeação de domínio continuam aparecendo.
No meu caso, simplesmente escrevo um validador personalizado que verifica se há uma resposta bem-sucedida.
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
Estou validando o path
atributo do meu modelo usando record.path
. Também estou enviando o erro para o respectivo nome de atributo usando record.errors[:path]
.
Você pode simplesmente substituir isso por qualquer nome de atributo.
Depois, simplesmente chamo o validador personalizado no meu modelo.
class Url < ApplicationRecord
# validations
validates_presence_of :path
validates_with UrlValidator
end
Você pode usar regex para isso, para mim funciona bem este:
(^|[\s.:;?\-\]<\(])(ftp|https?:\/\/[-\w;\/?:@&=+$\|\_.!~*\|'()\[\]%#,]+[\w\/#](\(\))?)(?=$|[\s',\|\(\).:;?\-\[\]>\)])