Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature add: protocol validation #1

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions init.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module ValidatesUrlFormatOf
IPv4_PART = /\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]/ # 0-255
REGEXP = %r{
\A
https?:// # http:// or https://
[a-z]+:// # http:// or https:// or xyz://
([^\s:@]+:[^\s:@]*@)? # optional username:pw@
( (([^\W_]+\.)*xn--)?[^\W_]+([-.][^\W_]+)*\.[a-z]{2,6}\.? | # domain (including Punycode/IDN)...
#{IPv4_PART}(\.#{IPv4_PART}){3} ) # or IPv4
Expand All @@ -11,18 +11,26 @@ module ValidatesUrlFormatOf
\Z
}iux

DEFAULT_MESSAGE = 'does not appear to be a valid URL'
DEFAULT_MESSAGE_URL = 'does not appear to be valid'
DEFAULT_MESSAGE = 'does not appear to be a valid URL'
DEFAULT_MESSAGE_URL = 'does not appear to be valid'
DEFAULT_PROTOCOLS = %w(http https)

def validates_url_format_of(*attr_names)
options = { :allow_nil => false,
:allow_blank => false,
:allow_protocols => DEFAULT_PROTOCOLS,
:with => REGEXP }
options = options.merge(attr_names.pop) if attr_names.last.is_a?(Hash)

attr_names.each do |attr_name|
message = attr_name.to_s.match(/(_|\b)URL(_|\b)/i) ? DEFAULT_MESSAGE_URL : DEFAULT_MESSAGE
validates_format_of(attr_name, { :message => message }.merge(options))
validates_each attr_name, options do |record, attr, value|
unless record.errors.on(attr) || value.blank? # skip if the url is already invalid or blank
protocol = value[/\A\s*([a-z]+):/i, 1].to_s.downcase
record.errors.add attr, options.fetch(:message, message) unless options[:allow_protocols].include?(protocol)
end
end
end
end

Expand Down
55 changes: 55 additions & 0 deletions test/validates_url_format_of_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@ def self.self_and_descendents_from_active_record() [self] end # Needed by Rails

attr_accessor :custom_url
validates_url_format_of :custom_url, :message => 'custom message'

attr_accessor :ftp_url
validates_url_format_of :ftp_url, :allow_protocols => %w(ftp ftps), :allow_nil => true

attr_accessor :if_true_url
validates_url_format_of :if_true_url, :if => lambda { true }

attr_accessor :if_false_url
validates_url_format_of :if_false_url, :if => lambda { false }
end

class ValidatesUrlFormatOfTest < Test::Unit::TestCase
Expand Down Expand Up @@ -86,6 +95,7 @@ def test_should_reject_invalid_urls
'http://256.0.0.1',
'http://u:u:[email protected]',
'http://r?ksmorgas.com',
'blork://example.com',

# These can all be valid local URLs, but should not be considered valid
# for public consumption.
Expand All @@ -99,6 +109,31 @@ def test_should_reject_invalid_urls
end
end

def test_allowed_protocol_list
%w(ftp ftps).each do |protocol|
@model.ftp_url = "#{protocol}://example.com"
@model.valid?
assert @model.errors[:ftp_url].empty?, "#{protocol.inspect} should have been accepted"
end

@model.ftp_url = "http://example.com"
@model.valid?
assert [email protected][:ftp_url].empty?, '"http" should have been rejected'
end

def test_allowed_protocol_list_with_custom_message
@model.custom_url = "ftp://example.com"
@model.valid?
assert [email protected][:custom_url].empty?, "non-http protocol should have been rejected"
assert @model.errors[:custom_url].include?('custom message'), "expected #{@model.errors[:custom_url].inspect} to contain 'custom message'"
end

def test_allows_nil
@model.ftp_url = nil
@model.valid?
assert @model.errors[:ftp_url].empty?, "nil should have been accepted"
end

def test_different_defaults_based_on_attribute_name
@model.homepage = 'x'
@model.my_UrL_hooray = 'x'
Expand All @@ -114,4 +149,24 @@ def test_can_override_defaults
assert_equal ['custom message'], @model.errors[:custom_url]
end

def test_if_passed_through_true
@model.if_true_url = 'x'
@model.valid?
assert [email protected][:if_true_url].empty?, "Bad url should have been rejected"

@model.if_true_url = 'http://valid.url.com/hey/index.html'
@model.valid?
assert @model.errors[:if_true_url].empty?, "good url should pass"
end

def test_if_passed_through_false
@model.if_false_url = 'x'
@model.valid?
assert @model.errors[:if_false_url].empty?, ":if was not passed through to underlying validation routines"

@model.if_false_url = 'http://valid.url.com/hey/index.html'
@model.valid?
assert @model.errors[:if_false_url].empty?, "Somehow a good url was rejected in addition to :if not passing through properly"
end

end