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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 867918b9a3ee09bdfc89a10e802032e229fdf4ab250f80727a68197f8144d615
4
- data.tar.gz: 9d1dcfcc05db65d1669049c87f36bf9a252fe91ce68b9298a7578ce630aeae50
3
+ metadata.gz: 3eee84a8970b92e6b5d1b0e01e51a40c66cc0c7298b0fe37a6ea44aecd7b9064
4
+ data.tar.gz: 9504d35e8ce6376fd821110a15d079783f723af1ae78606fa7fd2e565c053487
5
5
  SHA512:
6
- metadata.gz: 2dc34142c2553b747290451db7ca631dd12b31e9e7488502e4a54b3fc1cf372a7dd498c951498f8c3b86d519172bb879fb7f650e97448260dd8fad2c5c5c8662
7
- data.tar.gz: 513cb240285c58fbf784d3a61fae066bf7c768836cdc06281357b92f8c7a62ad72ea2c0114f3ba6645da29b4ed01fbabff616f10db91f93b8194c587d2f9c623
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 validates
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"`
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "coercive"
3
- s.version = "1.0.1"
3
+ s.version = "1.1.0"
4
4
  s.summary = "Coercive is a library to validate and coerce user input"
5
5
  s.description = s.summary
6
6
  s.authors = ["Joe McIlvain", "Lucas Tolchinsky"]
@@ -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
@@ -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
- Coercive::URI::PRIVATE_IP_RANGES.each do |range|
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.1
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-05 00:00:00.000000000 Z
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: