Module: Siwe::Util
- Defined in:
- lib/siwe/util.rb
Constant Summary collapse
- NONCE_LENGTH =
17- ISO8601_REGEX =
/ \A (?<date>\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\d|3[01])) [Tt] (?:[01]\d|2[0-3]): [0-5]\d: (?:[0-5]\d|60) (?:\.\d+)? (?:[Zz]|[+-](?:[01]\d|2[0-3]):[0-5]\d) \z /x- HOST_CHAR_REGEX =
Character classes per RFC 3986. Anything outside these is rejected. unreserved + sub-delims + pct-encoded marker, used in reg-name.
/\A(?:[A-Za-z0-9\-._~!$&'()*+,;=]|%[0-9A-Fa-f]{2})*\z/- USERINFO_CHAR_REGEX =
/\A(?:[A-Za-z0-9\-._~!$&'()*+,;=:]|%[0-9A-Fa-f]{2})*\z/- PCHAR_REGEX =
pchar = unreserved / pct-encoded / sub-delims / “:” / “@”. Used for request-id (ABNF “request-id = *pchar”) and segments within a path.
/\A(?:[A-Za-z0-9\-._~!$&'()*+,;=:@]|%[0-9A-Fa-f]{2})*\z/- PATH_REGEX =
path = *( pchar / “/” ). No “?” or “#”.
%r{\A(?:[A-Za-z0-9\-._~!$&'()*+,;=:@/]|%[0-9A-Fa-f]{2})*\z}- QUERY_FRAGMENT_REGEX =
query / fragment = *( pchar / “/” / “?” ).
%r{\A(?:[A-Za-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9A-Fa-f]{2})*\z}- PORT_REGEX =
/\A\d*\z/- SCHEME_REGEX =
/\A[A-Za-z][A-Za-z0-9+\-.]*\z/
Class Method Summary collapse
- .address_case(addr) ⇒ Object
- .checksum_address(addr) ⇒ Object
- .generate_nonce ⇒ Object
- .split_host_port(str) ⇒ Object
- .valid_address?(addr) ⇒ Boolean
-
.valid_authority?(str) ⇒ Boolean
Validate an RFC 3986 authority: [userinfo “@”] host [“:” port].
- .valid_dotted_ipv4?(addr) ⇒ Boolean
- .valid_hier_part?(hier) ⇒ Boolean
-
.valid_host?(host) ⇒ Boolean
host = IP-literal / IPv4address / reg-name.
-
.valid_ip_literal?(content) ⇒ Boolean
IP-literal = IPv6address / IPvFuture (latter is rare and unused in SIWE — reject).
- .valid_iso8601?(str) ⇒ Boolean
-
.valid_uri?(str) ⇒ Boolean
Validate an absolute RFC 3986 URI: scheme “:” hier-part [ “?” query ] [ “#” fragment ].
Class Method Details
.address_case(addr) ⇒ Object
69 70 71 72 73 74 75 76 77 78 |
# File 'lib/siwe/util.rb', line 69 def address_case(addr) return :invalid unless addr.is_a?(String) && addr.match?(/\A0x[0-9a-fA-F]{40}\z/) hex = addr[2..] return :lower if hex == hex.downcase return :upper if hex == hex.upcase eip55 = checksum_address(addr) eip55 == addr ? :checksum : :invalid_checksum end |
.checksum_address(addr) ⇒ Object
80 81 82 83 84 |
# File 'lib/siwe/util.rb', line 80 def checksum_address(addr) Eth::Address.new(addr).to_s rescue StandardError nil end |
.generate_nonce ⇒ Object
41 42 43 |
# File 'lib/siwe/util.rb', line 41 def generate_nonce SecureRandom.alphanumeric(NONCE_LENGTH) end |
.split_host_port(str) ⇒ Object
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 |
# File 'lib/siwe/util.rb', line 162 def split_host_port(str) if str.start_with?("[") close = str.index("]") return [nil, nil] if close.nil? host = str[0..close] rest = str[(close + 1)..] if rest.empty? [host, nil] elsif rest.start_with?(":") [host, rest[1..]] else [nil, nil] end elsif (colon = str.rindex(":")) [str[0...colon], str[(colon + 1)..]] else [str, nil] end end |
.valid_address?(addr) ⇒ Boolean
60 61 62 63 64 65 66 67 |
# File 'lib/siwe/util.rb', line 60 def valid_address?(addr) return false if addr.nil? || addr.empty? Eth::Address.new(addr) true rescue StandardError false end |
.valid_authority?(str) ⇒ Boolean
Validate an RFC 3986 authority: [userinfo “@”] host [“:” port]. Used for both the SIWE ‘domain` field and any URI’s authority component. Empty authority is valid (empty reg-name).
89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/siwe/util.rb', line 89 def (str) return false if str.nil? return true if str.empty? userinfo, host_port = str.include?("@") ? str.split("@", 2) : [nil, str] return false if userinfo && !USERINFO_CHAR_REGEX.match?(userinfo) host, port = split_host_port(host_port) return false if host.nil? return false if port && !PORT_REGEX.match?(port) valid_host?(host) end |
.valid_dotted_ipv4?(addr) ⇒ Boolean
155 156 157 158 159 160 |
# File 'lib/siwe/util.rb', line 155 def valid_dotted_ipv4?(addr) octets = addr.split(".", -1) return false unless octets.length == 4 octets.all? { |o| o.match?(/\A\d{1,3}\z/) && o.to_i <= 255 } end |
.valid_hier_part?(hier) ⇒ Boolean
183 184 185 186 187 188 189 190 191 |
# File 'lib/siwe/util.rb', line 183 def valid_hier_part?(hier) if hier.start_with?("//") rest = hier[2..] = rest.index("/") || rest.length (rest[0...]) && PATH_REGEX.match?(rest[..]) else PATH_REGEX.match?(hier) end end |
.valid_host?(host) ⇒ Boolean
host = IP-literal / IPv4address / reg-name. Distinguishes by leading “[”.
121 122 123 124 125 126 127 |
# File 'lib/siwe/util.rb', line 121 def valid_host?(host) if host.start_with?("[") && host.end_with?("]") valid_ip_literal?(host[1..-2]) else HOST_CHAR_REGEX.match?(host) end end |
.valid_ip_literal?(content) ⇒ Boolean
IP-literal = IPv6address / IPvFuture (latter is rare and unused in SIWE — reject).
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/siwe/util.rb', line 130 def valid_ip_literal?(content) return false if content.nil? || content.empty? return false if content.start_with?("v", "V") # IPvFuture not supported ipv6 = content if content.include?(".") last_colon = content.rindex(":") return false if last_colon.nil? ipv4 = content[(last_colon + 1)..] return false unless valid_dotted_ipv4?(ipv4) # IPAddr rejects leading zeros in IPv4 octets ("zero-filled ambiguous"), but the # SIWE test vectors treat them as valid. Rewrite the IPv4 tail as two h16 groups. octets = ipv4.split(".").map(&:to_i) g1 = format("%x", (octets[0] << 8) | octets[1]) g2 = format("%x", (octets[2] << 8) | octets[3]) ipv6 = "#{content[0..last_colon]}#{g1}:#{g2}" end IPAddr.new(ipv6).ipv6? rescue IPAddr::InvalidAddressError false end |
.valid_iso8601?(str) ⇒ Boolean
45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/siwe/util.rb', line 45 def valid_iso8601?(str) return false if str.nil? || str.empty? m = ISO8601_REGEX.match(str) return false if m.nil? year, month, day = m[:date].split("-").map(&:to_i) return false unless Date.valid_date?(year, month, day) Time.iso8601(str) true rescue ArgumentError false end |
.valid_uri?(str) ⇒ Boolean
Validate an absolute RFC 3986 URI: scheme “:” hier-part [ “?” query ] [ “#” fragment ].
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 |
# File 'lib/siwe/util.rb', line 104 def valid_uri?(str) return false if str.nil? || str.empty? scheme, rest = str.split(":", 2) return false if rest.nil? return false unless SCHEME_REGEX.match?(scheme) m = rest.match(/\A(?<hier>[^?#]*)(?:\?(?<query>[^#]*))?(?:\#(?<frag>.*))?\z/) return false if m.nil? return false unless valid_hier_part?(m[:hier]) return false if m[:query] && !QUERY_FRAGMENT_REGEX.match?(m[:query]) return false if m[:frag] && !QUERY_FRAGMENT_REGEX.match?(m[:frag]) true end |