coercive 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: