Module: Philiprehberger::Base64Url
- Defined in:
- lib/philiprehberger/base64_url.rb,
lib/philiprehberger/base64_url/version.rb
Defined Under Namespace
Classes: Error
Constant Summary collapse
- VERSION =
'0.7.0'
Class Method Summary collapse
-
.byte_length(encoded) ⇒ Integer
Calculate the decoded byte length from an encoded string without decoding.
-
.decode(data) ⇒ String
Decode URL-safe Base64 data.
-
.decode_json(str) ⇒ Hash
Decode a URL-safe Base64 string and parse as JSON.
-
.decode_to_file(encoded, path) ⇒ void
Decode a URL-safe Base64 string and write to a file.
-
.decode_uuid(encoded) ⇒ String
Decode a 22-character URL-safe Base64 string back to a canonical UUID.
-
.encode(data, padding: false) ⇒ String
Encode data to URL-safe Base64.
-
.encode_file(path, padding: false) ⇒ String
Encode a file’s contents to URL-safe Base64.
-
.encode_json(hash) ⇒ String
Encode a hash as JSON then URL-safe Base64.
-
.encode_uuid(uuid) ⇒ String
Encode a canonical UUID as a compact 22-character URL-safe Base64 string.
-
.from_std(data) ⇒ String
Convert a standard Base64 string to a URL-safe Base64 string.
-
.random(bytes: 32) ⇒ String
Generate a URL-safe Base64 random token.
-
.secure_compare(a, b) ⇒ Boolean
Constant-time comparison of two Base64 strings.
-
.to_std(data) ⇒ String
Convert a URL-safe Base64 string to a standard Base64 string.
-
.valid?(data) ⇒ Boolean
Check if a string is valid URL-safe Base64.
Class Method Details
.byte_length(encoded) ⇒ Integer
Calculate the decoded byte length from an encoded string without decoding.
85 86 87 88 89 90 91 |
# File 'lib/philiprehberger/base64_url.rb', line 85 def self.byte_length(encoded) return 0 if encoded.nil? || encoded.empty? stripped = encoded.delete('=') padding = (4 - (stripped.length % 4)) % 4 ((stripped.length + padding) * 3 / 4) - padding end |
.decode(data) ⇒ String
Decode URL-safe Base64 data
26 27 28 29 30 |
# File 'lib/philiprehberger/base64_url.rb', line 26 def self.decode(data) Base64.urlsafe_decode64(data) rescue ArgumentError => e raise Error, "invalid Base64: #{e.}" end |
.decode_json(str) ⇒ Hash
Decode a URL-safe Base64 string and parse as JSON
45 46 47 48 49 |
# File 'lib/philiprehberger/base64_url.rb', line 45 def self.decode_json(str) JSON.parse(decode(str)) rescue JSON::ParserError => e raise Error, "invalid JSON: #{e.}" end |
.decode_to_file(encoded, path) ⇒ void
This method returns an undefined value.
Decode a URL-safe Base64 string and write to a file.
124 125 126 |
# File 'lib/philiprehberger/base64_url.rb', line 124 def self.decode_to_file(encoded, path) File.binwrite(path, decode(encoded)) end |
.decode_uuid(encoded) ⇒ String
Decode a 22-character URL-safe Base64 string back to a canonical UUID.
Accepts input with or without ‘=` padding. Returns a lowercase canonical UUID (“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”).
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/philiprehberger/base64_url.rb', line 179 def self.decode_uuid(encoded) raise ArgumentError, 'encoded must be a String' unless encoded.is_a?(String) begin bytes = Base64.urlsafe_decode64(encoded) rescue ArgumentError => e raise ArgumentError, "invalid encoded UUID: #{e.}" end unless bytes.bytesize == 16 raise ArgumentError, "invalid encoded UUID: expected 16 decoded bytes, got #{bytes.bytesize}" end hex = bytes.unpack1('H*') "#{hex[0, 8]}-#{hex[8, 4]}-#{hex[12, 4]}-#{hex[16, 4]}-#{hex[20, 12]}" end |
.encode(data, padding: false) ⇒ String
Encode data to URL-safe Base64
17 18 19 |
# File 'lib/philiprehberger/base64_url.rb', line 17 def self.encode(data, padding: false) Base64.urlsafe_encode64(data, padding: padding) end |
.encode_file(path, padding: false) ⇒ String
Encode a file’s contents to URL-safe Base64.
99 100 101 |
# File 'lib/philiprehberger/base64_url.rb', line 99 def self.encode_file(path, padding: false) encode(File.binread(path), padding: padding) end |
.encode_json(hash) ⇒ String
Encode a hash as JSON then URL-safe Base64
36 37 38 |
# File 'lib/philiprehberger/base64_url.rb', line 36 def self.encode_json(hash) encode(JSON.generate(hash)) end |
.encode_uuid(uuid) ⇒ String
Encode a canonical UUID as a compact 22-character URL-safe Base64 string.
Strips dashes, packs the 32 hex characters into 16 binary bytes, then URL-safe Base64 encodes without padding. Accepts mixed-case input.
163 164 165 166 167 168 169 |
# File 'lib/philiprehberger/base64_url.rb', line 163 def self.encode_uuid(uuid) raise ArgumentError, 'uuid must be a String' unless uuid.is_a?(String) raise ArgumentError, "invalid UUID: #{uuid.inspect}" unless UUID_REGEX.match?(uuid) bytes = [uuid.delete('-')].pack('H*') Base64.urlsafe_encode64(bytes, padding: false) end |
.from_std(data) ⇒ String
Convert a standard Base64 string to a URL-safe Base64 string.
Replaces ‘+` with `-`, `/` with `_`, and strips trailing `=` padding. Does not validate or decode the input.
148 149 150 |
# File 'lib/philiprehberger/base64_url.rb', line 148 def self.from_std(data) data.tr('+/', '-_').delete('=') end |
.random(bytes: 32) ⇒ String
Generate a URL-safe Base64 random token
Encodes ‘SecureRandom.bytes(bytes)` with URL-safe Base64 and no padding. Useful for session IDs, one-time tokens, and CSRF values that must be safe to pass in URLs and cookies.
112 113 114 115 116 |
# File 'lib/philiprehberger/base64_url.rb', line 112 def self.random(bytes: 32) raise ArgumentError, 'bytes must be non-negative' if bytes.negative? encode(SecureRandom.bytes(bytes)) end |
.secure_compare(a, b) ⇒ Boolean
Constant-time comparison of two Base64 strings.
Prevents timing attacks by always comparing all bytes.
69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/philiprehberger/base64_url.rb', line 69 def self.secure_compare(a, b) return false unless a.bytesize == b.bytesize left = a.unpack('C*') right = b.unpack('C*') result = 0 left.each_with_index { |byte, i| result |= byte ^ right[i] } result.zero? end |
.to_std(data) ⇒ String
Convert a URL-safe Base64 string to a standard Base64 string.
Replaces ‘-` with `+`, `_` with `/`, and adds `=` padding so the length is a multiple of 4. Does not validate or decode the input.
135 136 137 138 139 |
# File 'lib/philiprehberger/base64_url.rb', line 135 def self.to_std(data) converted = data.tr('-_', '+/') padding = (4 - (converted.length % 4)) % 4 converted + ('=' * padding) end |
.valid?(data) ⇒ Boolean
Check if a string is valid URL-safe Base64
55 56 57 58 59 60 |
# File 'lib/philiprehberger/base64_url.rb', line 55 def self.valid?(data) decode(data) true rescue Error false end |