url_validation 1.1.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +52 -0
- data/README.md +143 -0
- data/Rakefile +5 -35
- data/lib/url_validation/version.rb +9 -0
- data/lib/url_validation.rb +144 -115
- data/url_validation.gemspec +43 -69
- metadata +56 -61
- data/.document +0 -5
- data/.rspec +0 -2
- data/.ruby-gemset +0 -1
- data/.ruby-version +0 -1
- data/Gemfile +0 -13
- data/Gemfile.lock +0 -96
- data/README.textile +0 -36
- data/VERSION +0 -1
- data/spec/spec_helper.rb +0 -13
- data/spec/url_validator_spec.rb +0 -231
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3060391d73d4937f667c371401a15591f538001c9db4c9e60457646b31625ffb
|
|
4
|
+
data.tar.gz: 3450f357cf02db8279a324e369beeb69eeb1842242d2ff6cc59102fdd1006ad3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7111f507cf4f8f06f1e8c182846aed69633e404d0b4f5a1530ee2822ec165714de37850a243dc0b9616d262dc389c44bed9beba9c34b8e2a57f7cb84b0f0e97d
|
|
7
|
+
data.tar.gz: 5e58055eadde1913e32045b4f4f52efc838dbb6c88affafcba75c541b032dfc7c8635cbcca90c8201854f228f401c567428f65ab38c9f53b4ee6eac73f154721
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [2.0.0] - 2026-05-14
|
|
9
|
+
|
|
10
|
+
### Breaking
|
|
11
|
+
|
|
12
|
+
- Drops the runtime dependency on `activerecord`. The gem only ever needed
|
|
13
|
+
`ActiveModel::EachValidator`, so it now depends on `activemodel` (and
|
|
14
|
+
`activesupport`) directly. Apps that pulled `activerecord` in transitively
|
|
15
|
+
through this gem will need to declare it themselves if required.
|
|
16
|
+
- Bumps `required_ruby_version` to `>= 3.1`.
|
|
17
|
+
- Bumps `activemodel` / `activesupport` minimum to `>= 6.1`.
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- New `:http_method` option (default `:head`) lets callers choose the HTTP
|
|
22
|
+
verb used for accessibility checks. Useful for servers that do not handle
|
|
23
|
+
`HEAD` correctly.
|
|
24
|
+
- `webmock` is wired into the spec suite and `WebMock.disable_net_connect!`
|
|
25
|
+
is enabled, so the test suite no longer makes live network calls.
|
|
26
|
+
- GitHub Actions workflow tests Ruby 3.1-3.4 against ActiveModel 7.0-8.0.
|
|
27
|
+
- `bin/console`, `bin/setup`, modern `Rakefile`, hand-written gemspec.
|
|
28
|
+
- `CHANGELOG.md`.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- **Accessibility check now uses `HEAD` requests, as documented.** Previously
|
|
33
|
+
the code called `HTTPI.get`, contradicting the README. It now calls
|
|
34
|
+
`HTTPI.request(:head, ...)` by default.
|
|
35
|
+
- **`allow_blank: false` is now honored.** Previously the validator's
|
|
36
|
+
`return if value.blank?` short-circuit overrode an explicit
|
|
37
|
+
`allow_blank: false` option. Blank values now flow through to
|
|
38
|
+
format validation when `allow_blank` is false.
|
|
39
|
+
- **Rescue branch no longer references a re-parsed URI.** When the
|
|
40
|
+
`default_scheme` re-parse raised, the rescue branch could call
|
|
41
|
+
`url_format_valid?` against a previously-set URI in an inconsistent
|
|
42
|
+
state. The rescue branch now adds `:invalid_url` unconditionally and
|
|
43
|
+
returns, which matches the documented behavior.
|
|
44
|
+
|
|
45
|
+
### Removed
|
|
46
|
+
|
|
47
|
+
- `.travis.yml`, juwelier-generated gemspec, `VERSION` text file.
|
|
48
|
+
- Runtime `activerecord` dependency.
|
|
49
|
+
|
|
50
|
+
## [1.2.0]
|
|
51
|
+
|
|
52
|
+
- Last juwelier-managed release.
|
data/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# url_validation
|
|
2
|
+
|
|
3
|
+
[](https://github.com/RISCfuture/url_validation/actions/workflows/ci.yml)
|
|
4
|
+
[](https://rubygems.org/gems/url_validation)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
A simple, localizable `ActiveModel::EachValidator` for URL fields.
|
|
8
|
+
|
|
9
|
+
| | |
|
|
10
|
+
|:------------|:--------------------------------|
|
|
11
|
+
| **Author** | Tim Morgan |
|
|
12
|
+
| **License** | Released under the MIT license. |
|
|
13
|
+
|
|
14
|
+
## About
|
|
15
|
+
|
|
16
|
+
`url_validation` adds a URL validator usable in any `ActiveModel` (or `ActiveRecord`)
|
|
17
|
+
class. It supports localized error messages, multiple schemes, and optional
|
|
18
|
+
over-the-network reachability checks (`HEAD` by default, configurable).
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
Add to your `Gemfile`:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
gem "url_validation"
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Then `bundle install`.
|
|
29
|
+
|
|
30
|
+
This gem depends on `activemodel >= 6.1`. It does not require `activerecord`.
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
It's an `EachValidator`, so use it with `validates`:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
class User
|
|
38
|
+
include ActiveModel::Model
|
|
39
|
+
include ActiveModel::Validations
|
|
40
|
+
|
|
41
|
+
attr_accessor :terms_of_service_link
|
|
42
|
+
validates :terms_of_service_link, presence: true, url: true
|
|
43
|
+
end
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Examples
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
# Format only (no network)
|
|
50
|
+
validates :link, url: true
|
|
51
|
+
|
|
52
|
+
# Restrict to a specific scheme (or list)
|
|
53
|
+
validates :link, url: {scheme: "https"}
|
|
54
|
+
validates :link, url: {scheme: %w[http https ftp]}
|
|
55
|
+
|
|
56
|
+
# If the user types "example.com", treat it as "http://example.com"
|
|
57
|
+
validates :link, url: {default_scheme: "http"}
|
|
58
|
+
|
|
59
|
+
# Verify host is reachable (sends a HEAD request)
|
|
60
|
+
validates :link, url: {check_host: true}
|
|
61
|
+
|
|
62
|
+
# Verify the path resolves to something other than 4xx/5xx
|
|
63
|
+
validates :link, url: {check_path: true}
|
|
64
|
+
validates :link, url: {check_path: [300..399, 400..499, 500..599]}
|
|
65
|
+
|
|
66
|
+
# Use GET instead of HEAD (for servers that don't handle HEAD properly)
|
|
67
|
+
validates :link, url: {check_host: true, http_method: :get}
|
|
68
|
+
|
|
69
|
+
# Customize the HTTPI request (timeouts, headers, etc.)
|
|
70
|
+
validates :link, url: {
|
|
71
|
+
check_host: true,
|
|
72
|
+
request_callback: ->(request) { request.open_timeout = 5; request.read_timeout = 5 }
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Options
|
|
77
|
+
|
|
78
|
+
### Basic
|
|
79
|
+
|
|
80
|
+
| Option | Description |
|
|
81
|
+
|:---------------|:-------------------------------------------------------------------------|
|
|
82
|
+
| `:allow_nil` | If `true`, `nil` values are allowed. |
|
|
83
|
+
| `:allow_blank` | If `true` (the default), `nil` or empty values are allowed without running format checks. Set to `false` to flag blank values as `:invalid_url`. |
|
|
84
|
+
|
|
85
|
+
### Error messages
|
|
86
|
+
|
|
87
|
+
Override the I18n key the validator uses when adding errors:
|
|
88
|
+
|
|
89
|
+
| Option | Replaces I18n key |
|
|
90
|
+
|:--------------------------------|:------------------------|
|
|
91
|
+
| `:invalid_url_message` | `:invalid_url` |
|
|
92
|
+
| `:url_not_accessible_message` | `:url_not_accessible` |
|
|
93
|
+
| `:url_invalid_response_message` | `:url_invalid_response` |
|
|
94
|
+
|
|
95
|
+
### Format checks (no network)
|
|
96
|
+
|
|
97
|
+
| Option | Description |
|
|
98
|
+
|:------------------|:-------------------------------------------------------------------------------------------------------------------------------------|
|
|
99
|
+
| `:scheme` | A string or array of strings indicating acceptable URL schemes. Defaults to `%w[http https]`. |
|
|
100
|
+
| `:default_scheme` | If set (e.g., `"http"`), a URL without a scheme will have this scheme prepended before validation, so `"example.com"` parses fine. |
|
|
101
|
+
|
|
102
|
+
### Network checks
|
|
103
|
+
|
|
104
|
+
`url_validation` uses [HTTPI](https://rubygems.org/gems/httpi) to issue requests,
|
|
105
|
+
so you can pick your underlying HTTP client.
|
|
106
|
+
|
|
107
|
+
| Option | Description |
|
|
108
|
+
|:------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
109
|
+
| `:check_host` | If `true`, perform a request to the host to verify connectivity. Only runs for HTTP(S) URLs unless overridden. |
|
|
110
|
+
| `:check_path` | Treat specific response codes as invalid. Pass an Integer, a Symbol (e.g. `:not_found`), a Range (`400..499`), or an Array of these. `true` means "4xx or 5xx is invalid." Implies `:check_host`. |
|
|
111
|
+
| `:httpi_adapter` | The HTTPI adapter to use (default: HTTPI's default). |
|
|
112
|
+
| `:http_method` | The HTTP verb used for the accessibility check, as a Symbol. Defaults to `:head`. Use `:get` for servers that mis-handle `HEAD`. |
|
|
113
|
+
| `:request_callback` | A `Proc`/lambda invoked with the `HTTPI::Request` before it executes. Use for custom timeouts, headers, auth, etc. |
|
|
114
|
+
|
|
115
|
+
## Localization
|
|
116
|
+
|
|
117
|
+
The validator emits the standard ActiveModel error symbols (`:invalid_url`,
|
|
118
|
+
`:url_not_accessible`, `:url_invalid_response`). Provide translations under the
|
|
119
|
+
usual `activemodel.errors.messages` namespace (or use the `Model.errors.messages`
|
|
120
|
+
fallback). Example `config/locales/en.yml`:
|
|
121
|
+
|
|
122
|
+
```yaml
|
|
123
|
+
en:
|
|
124
|
+
errors:
|
|
125
|
+
messages:
|
|
126
|
+
invalid_url: "is not a valid URL"
|
|
127
|
+
url_not_accessible: "is not reachable"
|
|
128
|
+
url_invalid_response: "returned a bad response"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Development
|
|
132
|
+
|
|
133
|
+
```sh
|
|
134
|
+
bin/setup
|
|
135
|
+
bundle exec rspec
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
The spec suite uses [webmock](https://rubygems.org/gems/webmock) with
|
|
139
|
+
`disable_net_connect!`, so tests never touch the network.
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
MIT. See `LICENSE`.
|
data/Rakefile
CHANGED
|
@@ -1,38 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
require 'bundler'
|
|
3
|
-
begin
|
|
4
|
-
Bundler.setup(:default, :development)
|
|
5
|
-
rescue Bundler::BundlerError => e
|
|
6
|
-
$stderr.puts e.message
|
|
7
|
-
$stderr.puts "Run `bundle install` to install missing gems"
|
|
8
|
-
exit e.status_code
|
|
9
|
-
end
|
|
10
|
-
require 'rake'
|
|
1
|
+
# frozen_string_literal: true
|
|
11
2
|
|
|
12
|
-
require
|
|
13
|
-
|
|
14
|
-
gem.name = "url_validation"
|
|
15
|
-
gem.summary = %Q{Simple URL validation in Rails 3}
|
|
16
|
-
gem.description = %Q{A simple, localizable EachValidator for URL fields in ActiveRecord 3.0.}
|
|
17
|
-
gem.email = "git@timothymorgan.info"
|
|
18
|
-
gem.homepage = "http://github.com/riscfuture/url_validation"
|
|
19
|
-
gem.authors = [ "Tim Morgan" ]
|
|
20
|
-
gem.required_ruby_version = '>= 1.8.7'
|
|
21
|
-
end
|
|
22
|
-
Jeweler::RubygemsDotOrgTasks.new
|
|
3
|
+
require "bundler/gem_tasks"
|
|
4
|
+
require "rspec/core/rake_task"
|
|
23
5
|
|
|
24
|
-
|
|
25
|
-
YARD::Rake::YardocTask.new('doc') do |doc|
|
|
26
|
-
doc.options << "-m" << "textile"
|
|
27
|
-
doc.options << "--protected" << "--no-private"
|
|
28
|
-
doc.options << "-r" << "README.textile"
|
|
29
|
-
doc.options << "-o" << "doc"
|
|
30
|
-
doc.options << "--title" << "url_validation Documentation".inspect
|
|
31
|
-
|
|
32
|
-
doc.files = [ 'lib/*_validator.rb', 'README.textile' ]
|
|
33
|
-
end
|
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
|
34
7
|
|
|
35
|
-
|
|
36
|
-
RSpec::Core::RakeTask.new
|
|
37
|
-
|
|
38
|
-
task :default => :spec
|
|
8
|
+
task default: :spec
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# UrlValidator's version. Lives in its own module so the gemspec can
|
|
4
|
+
# `require_relative "lib/url_validation/version"` without forcing ActiveModel
|
|
5
|
+
# to load at gem-spec-evaluation time. The UrlValidator class picks the
|
|
6
|
+
# constant up and assigns it as `UrlValidator::VERSION`.
|
|
7
|
+
module UrlValidation
|
|
8
|
+
VERSION = "2.0.0"
|
|
9
|
+
end
|
data/lib/url_validation.rb
CHANGED
|
@@ -1,170 +1,197 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "addressable/uri"
|
|
4
|
+
require "httpi"
|
|
5
|
+
require "active_support/core_ext/hash/except"
|
|
6
|
+
require "active_model/validator"
|
|
7
|
+
require "active_support/core_ext/array/wrap"
|
|
8
|
+
|
|
9
|
+
require "url_validation/version"
|
|
6
10
|
|
|
7
11
|
# Validates URLs. Uses the following I18n error message keys:
|
|
8
12
|
#
|
|
9
|
-
# |
|
|
10
|
-
#
|
|
11
|
-
# |
|
|
13
|
+
# | | |
|
|
14
|
+
# |:-----------------------|:----------------------------------------------------------------|
|
|
15
|
+
# | `invalid_url` | URL is improperly formatted. |
|
|
16
|
+
# | `url_not_accessible` | Couldn't connect to the URL. |
|
|
17
|
+
# | `url_invalid_response` | Got a bad HTTP response (not of an acceptable type, e.g., 2xx). |
|
|
12
18
|
#
|
|
13
19
|
# @example Checks the syntax only
|
|
14
|
-
# validates :link, :
|
|
20
|
+
# validates :link, url: true
|
|
15
21
|
#
|
|
16
22
|
# @example Ensures the host is available but does not check the path
|
|
17
|
-
# validates :link, :
|
|
23
|
+
# validates :link, url: {check_host: true}
|
|
18
24
|
#
|
|
19
25
|
# @example Ensures that the host is available and that a request for the path does not return a 4xx or 5xx response
|
|
20
|
-
# validates :link, :
|
|
26
|
+
# validates :link, url: {check_path: true}
|
|
21
27
|
#
|
|
22
28
|
# @example Ensures that the host is available and that a request for the path does not return a 3xx, 4xx, or 5xx response
|
|
23
|
-
# validates :link, :
|
|
29
|
+
# validates :link, url: {check_path: [300..399, 400..499, 500..599]}
|
|
24
30
|
#
|
|
25
31
|
# @example Checks for host accessibility with a custom timeout
|
|
26
|
-
# validates :link, :
|
|
27
|
-
# :
|
|
28
|
-
# :
|
|
32
|
+
# validates :link, url: {
|
|
33
|
+
# check_host: true,
|
|
34
|
+
# request_callback: ->(request) { request.timeout = 30 }
|
|
29
35
|
# }
|
|
30
36
|
#
|
|
31
|
-
#
|
|
37
|
+
# @example Uses a GET request instead of the default HEAD request
|
|
38
|
+
# validates :link, url: {check_host: true, http_method: :get}
|
|
39
|
+
#
|
|
40
|
+
# ## Options
|
|
32
41
|
#
|
|
33
|
-
#
|
|
42
|
+
# ### Basic options
|
|
34
43
|
#
|
|
35
|
-
# |
|
|
36
|
-
#
|
|
44
|
+
# | | |
|
|
45
|
+
# |:---------------|:----------------------------------------------|
|
|
46
|
+
# | `:allow_nil` | If `true`, `nil` values are allowed. |
|
|
47
|
+
# | `:allow_blank` | If `true`, `nil` or empty values are allowed. |
|
|
37
48
|
#
|
|
38
|
-
#
|
|
49
|
+
# ### Error messages
|
|
39
50
|
#
|
|
40
|
-
# |
|
|
41
|
-
#
|
|
42
|
-
# |
|
|
43
|
-
# |
|
|
51
|
+
# | | |
|
|
52
|
+
# |:--------------------------------|:-------------------------------------------------------------|
|
|
53
|
+
# | `:invalid_url_message` | A custom message to use in place of `:invalid_url`. |
|
|
54
|
+
# | `:incorrect_url_type_message` | A custom message to use in place of `:incorrect_url_type`. |
|
|
55
|
+
# | `:url_not_accessible_message` | A custom message to use in place of `:url_not_accessible`. |
|
|
56
|
+
# | `:url_invalid_response_message` | A custom message to use in place of `:url_invalid_response`. |
|
|
44
57
|
#
|
|
45
|
-
#
|
|
58
|
+
# ### Networkless URL validation
|
|
46
59
|
#
|
|
47
|
-
# |
|
|
48
|
-
#
|
|
60
|
+
# | | |
|
|
61
|
+
# |:------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
62
|
+
# | `:scheme` | A string or array of strings, such as "http" or "ftp", indicating which URL schemes are valid. By default only HTTP(S) URLs are accepted. |
|
|
63
|
+
# | `:default_scheme` | A default URL scheme to try for improper URLs. If this is set to, e.g., "http", then when a URL like "whoops.com" is given (which would otherwise fail due to an improper format), "http://whoops.com" will be tried instead. |
|
|
49
64
|
#
|
|
50
|
-
#
|
|
65
|
+
# ### Over-the-network URL validation
|
|
51
66
|
#
|
|
52
67
|
# The HTTPI gem is used to provide a generic interface to whatever HTTP client
|
|
53
68
|
# you wish to use. This allows you to drop in, e.g., a Curl client if you want.
|
|
54
|
-
# You can set the HTTPI adapter with the
|
|
69
|
+
# You can set the HTTPI adapter with the `:httpi_adapter` option.
|
|
55
70
|
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
71
|
+
# By default, HEAD requests are used for accessibility checks. If a server does
|
|
72
|
+
# not support HEAD requests, you can set `:http_method` to `:get` (or any other
|
|
73
|
+
# verb supported by HTTPI).
|
|
59
74
|
#
|
|
60
|
-
#
|
|
75
|
+
# | | |
|
|
76
|
+
# |:-----------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
77
|
+
# | `:check_host` | If `true`, the validator will perform a network test to verify that it can connect to the server and access the host (at the "/" path). This check will only be performed for HTTP(S) URLs. |
|
|
78
|
+
# | `:check_path` | An integer or symbol (or array of integers or symbols), such as 301 or `:moved_permanently`, indicating what response codes are unacceptable. You can also use ranges, and include them in an array, such as `[:moved_permanently, 400..404, 500..599]`. By default, this is `nil`, and therefore only host accessibility is checked. If `true` is given, uses a default set of invalid error codes (4xx and 5xx). Implies `:check_host` is also true. |
|
|
79
|
+
# | `:httpi_adapter` | The HTTPI adapter to use for checking HTTP and HTTPS URLs (default set by the HTTPI gem). |
|
|
80
|
+
# | `:http_method` | The HTTP verb (as a Symbol) to use for the accessibility check. Defaults to `:head`. Set to `:get` for servers that do not support HEAD. |
|
|
61
81
|
#
|
|
62
|
-
#
|
|
82
|
+
# ### Other options
|
|
83
|
+
#
|
|
84
|
+
# | | |
|
|
85
|
+
# |:--------------------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
|
86
|
+
# | `:request_callback` | A proc that receives the request object (for HTTP(S) requests, the `HTTPI::Request` object) before it is executed. You can use this proc to set, e.g., custom headers or timeouts on the request. |
|
|
63
87
|
|
|
64
88
|
class UrlValidator < ActiveModel::EachValidator
|
|
89
|
+
VERSION = UrlValidation::VERSION
|
|
90
|
+
|
|
65
91
|
# @private
|
|
66
92
|
CODES = {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
93
|
+
continue: 100,
|
|
94
|
+
switching_protocols: 101,
|
|
95
|
+
processing: 102,
|
|
96
|
+
ok: 200,
|
|
97
|
+
created: 201,
|
|
98
|
+
accepted: 202,
|
|
99
|
+
non_authoritative_information: 203,
|
|
100
|
+
no_content: 204,
|
|
101
|
+
reset_content: 205,
|
|
102
|
+
partial_content: 206,
|
|
103
|
+
multi_status: 207,
|
|
104
|
+
im_used: 226,
|
|
105
|
+
multiple_choices: 300,
|
|
106
|
+
moved_permanently: 301,
|
|
107
|
+
found: 302,
|
|
108
|
+
see_other: 303,
|
|
109
|
+
not_modified: 304,
|
|
110
|
+
use_proxy: 305,
|
|
111
|
+
reserved: 306,
|
|
112
|
+
temporary_redirect: 307,
|
|
113
|
+
bad_request: 400,
|
|
114
|
+
unauthorized: 401,
|
|
115
|
+
payment_required: 402,
|
|
116
|
+
forbidden: 403,
|
|
117
|
+
not_found: 404,
|
|
118
|
+
method_not_allowed: 405,
|
|
119
|
+
not_acceptable: 406,
|
|
120
|
+
proxy_authentication_required: 407,
|
|
121
|
+
request_timeout: 408,
|
|
122
|
+
conflict: 409,
|
|
123
|
+
gone: 410,
|
|
124
|
+
length_required: 411,
|
|
125
|
+
precondition_failed: 412,
|
|
126
|
+
request_entity_too_large: 413,
|
|
127
|
+
request_uri_too_long: 414,
|
|
128
|
+
unsupported_media_type: 415,
|
|
129
|
+
requested_range_not_satisfiable: 416,
|
|
130
|
+
expectation_failed: 417,
|
|
131
|
+
unprocessable_entity: 422,
|
|
132
|
+
locked: 423,
|
|
133
|
+
failed_dependency: 424,
|
|
134
|
+
upgrade_required: 426,
|
|
135
|
+
internal_server_error: 500,
|
|
136
|
+
not_implemented: 501,
|
|
137
|
+
bad_gateway: 502,
|
|
138
|
+
service_unavailable: 503,
|
|
139
|
+
gateway_timeout: 504,
|
|
140
|
+
http_version_not_supported: 505,
|
|
141
|
+
variant_also_negotiates: 506,
|
|
142
|
+
insufficient_storage: 507,
|
|
143
|
+
not_extended: 510
|
|
144
|
+
}.freeze
|
|
145
|
+
|
|
121
146
|
# @private
|
|
122
147
|
def validate_each(record, attribute, value)
|
|
123
|
-
return if value.blank?
|
|
148
|
+
return if value.blank? && options.fetch(:allow_blank, true)
|
|
124
149
|
|
|
150
|
+
uri = nil
|
|
125
151
|
begin
|
|
126
152
|
uri = Addressable::URI.parse(value)
|
|
127
|
-
|
|
153
|
+
|
|
154
|
+
if uri.scheme.nil? && options[:default_scheme]
|
|
128
155
|
uri = Addressable::URI.parse("#{options[:default_scheme]}://#{value}")
|
|
129
156
|
end
|
|
130
157
|
rescue Addressable::URI::InvalidURIError
|
|
131
|
-
record.errors.add(attribute, options[:invalid_url_message]
|
|
158
|
+
record.errors.add(attribute, options[:invalid_url_message] || :invalid_url)
|
|
132
159
|
return
|
|
133
160
|
end
|
|
134
|
-
|
|
135
|
-
record.errors.add(attribute, options[:invalid_url_message]
|
|
136
|
-
record.errors.add(attribute, options[:url_not_accessible_message]
|
|
161
|
+
|
|
162
|
+
record.errors.add(attribute, options[:invalid_url_message] || :invalid_url) unless url_format_valid?(uri, options)
|
|
163
|
+
record.errors.add(attribute, options[:url_not_accessible_message] || :url_not_accessible) unless (response = url_accessible?(uri, options))
|
|
137
164
|
record.errors.add(attribute, options[:url_invalid_response_message] || :url_invalid_response) unless url_response_valid?(response, options)
|
|
138
165
|
end
|
|
139
|
-
|
|
166
|
+
|
|
140
167
|
private
|
|
141
|
-
|
|
168
|
+
|
|
142
169
|
def url_format_valid?(uri, options)
|
|
143
|
-
return false unless Array.wrap(options[:scheme] || %w
|
|
144
|
-
|
|
170
|
+
return false unless Array.wrap(options[:scheme] || %w[http https]).include?(uri.scheme)
|
|
171
|
+
|
|
145
172
|
case uri.scheme
|
|
146
|
-
when
|
|
173
|
+
when "http", "https"
|
|
147
174
|
return http_url_format_valid?(uri)
|
|
148
175
|
else
|
|
149
176
|
return true
|
|
150
177
|
end
|
|
151
178
|
end
|
|
152
|
-
|
|
179
|
+
|
|
153
180
|
def http_url_format_valid?(uri)
|
|
154
|
-
uri.host.present?
|
|
181
|
+
uri.host.present? && !uri.path.nil?
|
|
155
182
|
end
|
|
156
|
-
|
|
183
|
+
|
|
157
184
|
def url_accessible?(uri, options)
|
|
158
|
-
return true unless options[:check_host]
|
|
159
|
-
|
|
185
|
+
return true unless options[:check_host] || options[:check_path]
|
|
186
|
+
|
|
160
187
|
check_host = options[:check_host]
|
|
161
|
-
check_host ||= %w
|
|
162
|
-
if (schemes = Array.wrap(check_host))
|
|
163
|
-
return true
|
|
188
|
+
check_host ||= %w[http https] if options[:check_path]
|
|
189
|
+
if (schemes = Array.wrap(check_host)) && schemes.all?(String) && !schemes.include?(uri.scheme)
|
|
190
|
+
return true
|
|
164
191
|
end
|
|
165
|
-
|
|
192
|
+
|
|
166
193
|
case uri.scheme
|
|
167
|
-
when
|
|
194
|
+
when "http", "https"
|
|
168
195
|
return http_url_accessible?(uri, options)
|
|
169
196
|
else
|
|
170
197
|
return true
|
|
@@ -174,19 +201,21 @@ class UrlValidator < ActiveModel::EachValidator
|
|
|
174
201
|
def http_url_accessible?(uri, options)
|
|
175
202
|
request = HTTPI::Request.new(uri.to_s)
|
|
176
203
|
options[:request_callback].call(request) if options[:request_callback].respond_to?(:call)
|
|
177
|
-
|
|
178
|
-
|
|
204
|
+
method = options[:http_method] || :head
|
|
205
|
+
return HTTPI.request(method, request, options[:httpi_adapter])
|
|
206
|
+
rescue StandardError
|
|
179
207
|
return false
|
|
180
208
|
end
|
|
181
|
-
|
|
209
|
+
|
|
182
210
|
def url_response_valid?(response, options)
|
|
183
|
-
return true unless response.kind_of?(HTTPI::Response)
|
|
184
|
-
|
|
211
|
+
return true unless response.kind_of?(HTTPI::Response) && options[:check_path]
|
|
212
|
+
|
|
213
|
+
response_codes = (options[:check_path] == true) ? [400..499, 500..599] : Array.wrap(options[:check_path]).flatten
|
|
185
214
|
return response_codes.none? do |code| # it's good if it's not a bad response
|
|
186
215
|
case code # and it's a bad response if...
|
|
187
216
|
when Range
|
|
188
217
|
code.include? response.code
|
|
189
|
-
when
|
|
218
|
+
when Integer
|
|
190
219
|
code == response.code
|
|
191
220
|
when Symbol
|
|
192
221
|
CODES.include?(code) && CODES[code] == response.code
|