coercive 1.0.1 → 1.1.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 +4 -4
- data/README.md +42 -1
- data/coercive.gemspec +1 -1
- data/lib/coercive/uri.rb +1 -59
- data/test/uri.rb +1 -54
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3eee84a8970b92e6b5d1b0e01e51a40c66cc0c7298b0fe37a6ea44aecd7b9064
|
4
|
+
data.tar.gz: 9504d35e8ce6376fd821110a15d079783f723af1ae78606fa7fd2e565c053487
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 41fef05cb7a8c6a1d2f9b7f0f3737d76c5756a36903d6e2c45d028fb539d4de50f9ac0720b07e4262b97df2ff187638e0cf6d5fef4692d470ddc9484e00a9b57
|
7
|
+
data.tar.gz: 94c3e1e38e740c7089d8913977fbd1feea04d1df592d918c76b46be18c53787c95d36fed451a39191781db63718a941f31d53b4ac56d960384e1e854ed4d6066
|
data/README.md
CHANGED
@@ -212,4 +212,45 @@ CoerceFoo.call("foo" => {"barrrr" => "0.1"})
|
|
212
212
|
|
213
213
|
### `uri`
|
214
214
|
|
215
|
-
The `uri` coercion
|
215
|
+
The `uri` coercion function really showcases how it's very easy to build custom logic to validate and coerce any kind of input. `uri` is meant to verify IP and URLs and has a variety of options.
|
216
|
+
|
217
|
+
```ruby
|
218
|
+
module CoerceFoo
|
219
|
+
extend Coercive
|
220
|
+
|
221
|
+
attribute :foo, uri(string), optional
|
222
|
+
end
|
223
|
+
|
224
|
+
CoerceFoo.call("foo" => "http://github.com")
|
225
|
+
# => {"foo"=>"http://github.com"}
|
226
|
+
|
227
|
+
CoerceFoo.call("foo" => "not a url")
|
228
|
+
# => Coercive::Error: {"foo"=>"not_valid"}
|
229
|
+
```
|
230
|
+
|
231
|
+
#### Requiring a specific URI schema
|
232
|
+
|
233
|
+
The `schema_fn` option allows you to compose additional coercion functions to verify the schema.
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
module CoerceFoo
|
237
|
+
extend Coercive
|
238
|
+
|
239
|
+
attribute :foo, uri(string, schema_fn: member(%w{http https})), optional
|
240
|
+
end
|
241
|
+
|
242
|
+
CoerceFoo.call("foo" => "https://github.com")
|
243
|
+
# => {"foo"=>"https://github.com"}
|
244
|
+
|
245
|
+
CoerceFoo.call("foo" => "ftp://github.com")
|
246
|
+
# => Coercive::Error: {"foo"=>"unsupported_schema"}
|
247
|
+
```
|
248
|
+
|
249
|
+
#### Requiring URI elements
|
250
|
+
|
251
|
+
There's a number of boolean options to enforce the presence of parts of a URI to be present. By default they're all false.
|
252
|
+
|
253
|
+
* `require_path`: for example, `"https://github.com/Theorem"`
|
254
|
+
* `require_port`: for example, `"https://github.com:433"`
|
255
|
+
* `require_user`: for example, `"https://user@github.com"`
|
256
|
+
* `require_password`: for example, `"https://:password@github.com"`
|
data/coercive.gemspec
CHANGED
data/lib/coercive/uri.rb
CHANGED
@@ -3,27 +3,6 @@ require "uri"
|
|
3
3
|
|
4
4
|
module Coercive
|
5
5
|
module URI
|
6
|
-
# The IP ranges below are considered private and by default not permitted by the `uri`
|
7
|
-
# coercion function. To allow connecting to local services (in development, for example)
|
8
|
-
# users can set the `allow_private_ip` option, which ignores if the URI resolves to a public
|
9
|
-
# address or not.
|
10
|
-
PRIVATE_IP_RANGES = [
|
11
|
-
IPAddr.new("0.0.0.0/8"), # Broadcasting to the current network. RFC 1700.
|
12
|
-
IPAddr.new("10.0.0.0/8"), # Local private network. RFC 1918.
|
13
|
-
IPAddr.new("127.0.0.0/8"), # Loopback addresses to the localhost. RFC 990.
|
14
|
-
IPAddr.new("169.254.0.0/16"), # link-local addresses between two hosts on a single link. RFC 3927.
|
15
|
-
IPAddr.new("172.16.0.0/12"), # Local private network. RFC 1918.
|
16
|
-
IPAddr.new("192.168.0.0/16"), # Local private network. RFC 1918.
|
17
|
-
IPAddr.new("198.18.0.0/15"), # Testing of inter-network communications between two separate subnets. RFC 2544.
|
18
|
-
IPAddr.new("198.51.100.0/24"), # Assigned as "TEST-NET-2" in RFC 5737.
|
19
|
-
IPAddr.new("203.0.113.0/24"), # Assigned as "TEST-NET-3" in RFC 5737.
|
20
|
-
IPAddr.new("240.0.0.0/4"), # Reserved for future use, as specified by RFC 6890
|
21
|
-
IPAddr.new("::1/128"), # Loopback addresses to the localhost. RFC 5156.
|
22
|
-
IPAddr.new("2001:20::/28"), # Non-routed IPv6 addresses used for Cryptographic Hash Identifiers. RFC 7343.
|
23
|
-
IPAddr.new("fc00::/7"), # Unique Local Addresses (ULAs). RFC 1918.
|
24
|
-
IPAddr.new("fe80::/10"), # link-local addresses between two hosts on a single link. RFC 3927.
|
25
|
-
].freeze
|
26
|
-
|
27
6
|
# Public DSL: Return a coercion function to coerce input to a URI.
|
28
7
|
# Used when declaring an attribute. See documentation for attr_coerce_fns.
|
29
8
|
#
|
@@ -34,8 +13,7 @@ module Coercive
|
|
34
13
|
# require_user - set true to make the URI user a required element
|
35
14
|
# require_password - set true to make the URI password a required element
|
36
15
|
def self.coerce_fn(string_coerce_fn, schema_fn: nil, require_path: false,
|
37
|
-
require_port: false, require_user: false, require_password: false
|
38
|
-
allow_private_ip: false)
|
16
|
+
require_port: false, require_user: false, require_password: false)
|
39
17
|
->(input) do
|
40
18
|
uri = begin
|
41
19
|
::URI.parse(string_coerce_fn.call(input))
|
@@ -44,7 +22,6 @@ module Coercive
|
|
44
22
|
end
|
45
23
|
|
46
24
|
fail Coercive::Error.new("no_host") unless uri.host
|
47
|
-
fail Coercive::Error.new("not_resolvable") unless allow_private_ip || resolvable_public_ip?(uri)
|
48
25
|
fail Coercive::Error.new("no_path") if require_path && uri.path.empty?
|
49
26
|
fail Coercive::Error.new("no_port") if require_port && !uri.port
|
50
27
|
fail Coercive::Error.new("no_user") if require_user && !uri.user
|
@@ -61,40 +38,5 @@ module Coercive
|
|
61
38
|
uri.to_s
|
62
39
|
end
|
63
40
|
end
|
64
|
-
|
65
|
-
# Internal: Return true if the given URI is resolvable to a non-private IP.
|
66
|
-
#
|
67
|
-
# uri - the URI to check.
|
68
|
-
def self.resolvable_public_ip?(uri)
|
69
|
-
begin
|
70
|
-
_, _, _, *resolved_addresses = Socket.gethostbyname(uri.host)
|
71
|
-
rescue SocketError
|
72
|
-
return false
|
73
|
-
end
|
74
|
-
|
75
|
-
resolved_addresses.none? do |bytes|
|
76
|
-
ip = ip_from_bytes(bytes)
|
77
|
-
|
78
|
-
ip.nil? || PRIVATE_IP_RANGES.any? { |range| range.include?(ip) }
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
# Internal: Return an IPAddr built from the given address bytes.
|
83
|
-
#
|
84
|
-
# bytes - the binary-encoded String returned by Socket.gethostbyname.
|
85
|
-
def self.ip_from_bytes(bytes)
|
86
|
-
octets = bytes.unpack("C*")
|
87
|
-
|
88
|
-
string =
|
89
|
-
if octets.length == 4 # IPv4
|
90
|
-
octets.join(".")
|
91
|
-
else # IPv6
|
92
|
-
octets.map { |i| "%02x" % i }.each_slice(2).map(&:join).join(":")
|
93
|
-
end
|
94
|
-
|
95
|
-
IPAddr.new(string)
|
96
|
-
rescue IPAddr::InvalidAddressError
|
97
|
-
nil
|
98
|
-
end
|
99
41
|
end
|
100
42
|
end
|
data/test/uri.rb
CHANGED
@@ -39,10 +39,6 @@ describe "Coercive::URI" do
|
|
39
39
|
attribute :require_password,
|
40
40
|
uri(string(min: 1, max: 255), require_password: true),
|
41
41
|
optional
|
42
|
-
|
43
|
-
attribute :allow_private_ip,
|
44
|
-
uri(string(min: 1, max: 255), allow_private_ip: true),
|
45
|
-
optional
|
46
42
|
end
|
47
43
|
end
|
48
44
|
|
@@ -127,61 +123,12 @@ describe "Coercive::URI" do
|
|
127
123
|
assert_coercion_error(expected_errors) { @coercion.call(attributes) }
|
128
124
|
end
|
129
125
|
|
130
|
-
|
131
|
-
range = range.to_range
|
132
|
-
first = range.first
|
133
|
-
last = range.last
|
134
|
-
first = first.ipv6? ? "[#{first}]" : first.to_s
|
135
|
-
last = last.ipv6? ? "[#{last}]" : last.to_s
|
136
|
-
|
137
|
-
it "errors when the URI host is an IP in the range #{first}..#{last}" do
|
138
|
-
attributes_first = { "schema" => "http://#{first}/path" }
|
139
|
-
attributes_last = { "schema" => "http://#{last}/path" }
|
140
|
-
expected_errors = { "schema" => "not_resolvable" }
|
141
|
-
|
142
|
-
assert_coercion_error(expected_errors) { @coercion.call(attributes_first) }
|
143
|
-
assert_coercion_error(expected_errors) { @coercion.call(attributes_last) }
|
144
|
-
end
|
145
|
-
|
146
|
-
it "allows overriding private IP address checks" do
|
147
|
-
attributes_first = { "allow_private_ip" => "http://#{first}/path" }
|
148
|
-
attributes_last = { "allow_private_ip" => "http://#{last}/path" }
|
149
|
-
|
150
|
-
assert_equal attributes_first, @coercion.call(attributes_first)
|
151
|
-
assert_equal attributes_last, @coercion.call(attributes_last)
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
it "errors when the URI host is not resolvable" do
|
156
|
-
attributes = {
|
157
|
-
"schema" => "http://bogus-host-that-cant-possibly-exist-here/path"
|
158
|
-
}
|
159
|
-
|
160
|
-
expected_errors = { "schema" => "not_resolvable" }
|
161
|
-
|
162
|
-
assert_coercion_error(expected_errors) { @coercion.call(attributes) }
|
163
|
-
end
|
164
|
-
|
165
|
-
it "errors when the URI host resolves to an IP in a private range" do
|
166
|
-
attributes = { "schema" => "http://localhost/path" }
|
167
|
-
|
168
|
-
expected_errors = { "schema" => "not_resolvable" }
|
169
|
-
|
170
|
-
assert_coercion_error(expected_errors) { @coercion.call(attributes) }
|
171
|
-
end
|
172
|
-
|
173
|
-
it "allows a URI host to be IP that isn't in a private range" do
|
126
|
+
it "allows a URI host to be an IP" do
|
174
127
|
attributes = { "schema" => "http://8.8.8.8/path" }
|
175
128
|
|
176
129
|
assert_equal attributes, @coercion.call(attributes)
|
177
130
|
end
|
178
131
|
|
179
|
-
it "allows a URI host that resolves to an IP not in a private range" do
|
180
|
-
attributes = { "schema" => "http://www.example.com/path" }
|
181
|
-
|
182
|
-
assert_equal attributes, @coercion.call(attributes)
|
183
|
-
end
|
184
|
-
|
185
132
|
it "allows a URI with no explicit path component" do
|
186
133
|
attributes = { "schema" => "http://www.example.com" }
|
187
134
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: coercive
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Joe McIlvain
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2020-08-
|
12
|
+
date: 2020-08-13 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description: Coercive is a library to validate and coerce user input
|
15
15
|
email:
|