Module: Coercion::URI
- Defined in:
- lib/coercive/uri.rb
Constant Summary collapse
- ALLOW_PRIVATE_IP_CONNECTIONS =
Setting this ‘true` allows outbound connections to private IP addresses, bypassing the security check that the IP address is public. This is designed to be used in devlopment so that the tests can connect to local services.
This SHOULD NOT be set in PRODUCTION.
ENV.fetch("ALLOW_PRIVATE_IP_CONNECTIONS", "").downcase == "true"
- PRIVATE_IP_RANGES =
[ IPAddr.new("0.0.0.0/8"), # Broadcasting to the current network. RFC 1700. IPAddr.new("10.0.0.0/8"), # Local private network. RFC 1918. IPAddr.new("127.0.0.0/8"), # Loopback addresses to the localhost. RFC 990. IPAddr.new("169.254.0.0/16"), # link-local addresses between two hosts on a single link. RFC 3927. IPAddr.new("172.16.0.0/12"), # Local private network. RFC 1918. IPAddr.new("192.168.0.0/16"), # Local private network. RFC 1918. IPAddr.new("198.18.0.0/15"), # Testing of inter-network communications between two separate subnets. RFC 2544. IPAddr.new("198.51.100.0/24"), # Assigned as "TEST-NET-2" in RFC 5737. IPAddr.new("203.0.113.0/24"), # Assigned as "TEST-NET-3" in RFC 5737. IPAddr.new("240.0.0.0/4"), # Reserved for future use, as specified by RFC 6890 IPAddr.new("::1/128"), # Loopback addresses to the localhost. RFC 5156. IPAddr.new("2001:20::/28"), # Non-routed IPv6 addresses used for Cryptographic Hash Identifiers. RFC 7343. IPAddr.new("fc00::/7"), # Unique Local Addresses (ULAs). RFC 1918. IPAddr.new("fe80::/10"), # link-local addresses between two hosts on a single link. RFC 3927. ].freeze
Class Method Summary collapse
-
.coerce_fn(string_coerce_fn, schema_fn: nil, require_path: false, require_port: false, require_user: false, require_password: false) ⇒ Object
Public DSL: Return a coercion function to coerce input to a URI.
-
.ip_from_bytes(bytes) ⇒ Object
Internal: Return an IPAddr built from the given address bytes.
-
.resolvable_public_ip?(uri) ⇒ Boolean
Internal: Return true if the given URI is resolvable to a non-private IP.
Class Method Details
.coerce_fn(string_coerce_fn, schema_fn: nil, require_path: false, require_port: false, require_user: false, require_password: false) ⇒ Object
Public DSL: Return a coercion function to coerce input to a URI. Used when declaring an attribute. See documentation for attr_coerce_fns.
string_coerce_fn - the string coerce function used to coerce the URI schema_fn - the optional function used to coerce the schema require_path - set true to make the URI path a required element require_port - set true to make the URI port a required element require_user - set true to make the URI user a required element require_password - set true to make the URI password a required element
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/coercive/uri.rb', line 40 def self.coerce_fn(string_coerce_fn, schema_fn: nil, require_path: false, require_port: false, require_user: false, require_password: false) ->(input) do uri = begin ::URI.parse(string_coerce_fn.call(input)) rescue ::URI::InvalidURIError fail Coercion::Error.new("not_valid") end fail Coercion::Error.new("no_host") unless uri.host fail Coercion::Error.new("not_resolvable") unless resolvable_public_ip?(uri) || ALLOW_PRIVATE_IP_CONNECTIONS fail Coercion::Error.new("no_path") if require_path && uri.path.empty? fail Coercion::Error.new("no_port") if require_port && !uri.port fail Coercion::Error.new("no_user") if require_user && !uri.user fail Coercion::Error.new("no_password") if require_password && !uri.password if schema_fn begin schema_fn.call(uri.scheme) rescue Coercion::Error fail Coercion::Error.new("unsupported_schema") end end uri.to_s end end |
.ip_from_bytes(bytes) ⇒ Object
Internal: Return an IPAddr built from the given address bytes.
bytes - the binary-encoded String returned by Socket.gethostbyname.
88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/coercive/uri.rb', line 88 def self.ip_from_bytes(bytes) octets = bytes.unpack("C*") string = if octets.length == 4 # IPv4 octets.join(".") else # IPv6 octets.map { |i| "%02x" % i }.each_slice(2).map(&:join).join(":") end IPAddr.new(string) rescue IPAddr::InvalidAddressError nil end |
.resolvable_public_ip?(uri) ⇒ Boolean
Internal: Return true if the given URI is resolvable to a non-private IP.
uri - the URI to check.
71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/coercive/uri.rb', line 71 def self.resolvable_public_ip?(uri) begin _, _, _, *resolved_addresses = Socket.gethostbyname(uri.host) rescue SocketError return false end resolved_addresses.none? do |bytes| ip = ip_from_bytes(bytes) ip.nil? || PRIVATE_IP_RANGES.any? { |range| range.include?(ip) } end end |