Module: MonoVM::Whois::Punycode
- Defined in:
- lib/monovm/whois/punycode.rb
Overview
Punycode (RFC 3492) encoder and decoder.
Ruby has no Punycode or IDNA support in its standard library, and pulling in a gem for ~120 lines of well-specified arithmetic would cost this library its zero-dependency guarantee. So it lives here, transcribed from the reference pseudocode in RFC 3492 section 6 and checked against the test vectors in section 7.1.
This module deals in single labels. Whole-name conversion, the xn-- prefix
and validation belong to DomainName.
Defined Under Namespace
Classes: Error
Constant Summary collapse
- BASE =
36- TMIN =
1- TMAX =
26- SKEW =
38- DAMP =
700- INITIAL_BIAS =
72- INITIAL_N =
128- DELIMITER =
"-"- MAX_CODE_POINT =
Highest code point that survives a round trip through the bias arithmetic without overflowing what a DNS label could ever hold.
0x10FFFF
Class Method Summary collapse
-
.decode(input) ⇒ String
Decode a Punycode label.
-
.encode(input) ⇒ String
Encode a Unicode label as Punycode, without the
xn--prefix.
Class Method Details
.decode(input) ⇒ String
Decode a Punycode label. The xn-- prefix must already be stripped.
Punycode.decode("mnchen-3ya") # => "münchen"
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/monovm/whois/punycode.rb', line 84 def decode(input) delimiter_at = input.rindex(DELIMITER) if delimiter_at basic = input[0...delimiter_at] raise Error, "non-basic code point before the delimiter" unless basic.ascii_only? output = basic.codepoints cursor = delimiter_at + 1 else output = [] cursor = 0 end n = INITIAL_N i = 0 bias = INITIAL_BIAS digits = input.codepoints while cursor < digits.length old_i = i weight = 1 k = BASE loop do raise Error, "truncated punycode sequence" if cursor >= digits.length digit = digit_value(digits[cursor]) cursor += 1 i += digit * weight raise Error, "punycode overflow" if i > MAX_CODE_POINT * (output.length + 1) t = threshold(k, bias) break if digit < t weight *= (BASE - t) k += BASE end bias = adapt(i - old_i, output.length + 1, old_i.zero?) n += i / (output.length + 1) i %= (output.length + 1) raise Error, "punycode decoded to a basic code point" if basic?(n) raise Error, "punycode decoded outside Unicode" if n > MAX_CODE_POINT output.insert(i, n) i += 1 end output.pack("U*") end |
.encode(input) ⇒ String
Encode a Unicode label as Punycode, without the xn-- prefix.
Punycode.encode("münchen") # => "mnchen-3ya"
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 67 68 69 70 71 72 73 74 75 |
# File 'lib/monovm/whois/punycode.rb', line 40 def encode(input) code_points = input.codepoints basic = code_points.select { |cp| basic?(cp) } output = basic.pack("U*") output += DELIMITER unless basic.empty? n = INITIAL_N delta = 0 bias = INITIAL_BIAS handled = basic.length basic_length = basic.length while handled < code_points.length # The next code point to deal with is the smallest one we have not # reached yet; that ordering is what makes the encoding reversible. m = code_points.select { |cp| cp >= n }.min delta += (m - n) * (handled + 1) n = m code_points.each do |cp| delta += 1 if cp < n next unless cp == n output += encode_delta(delta, bias) bias = adapt(delta, handled + 1, handled == basic_length) delta = 0 handled += 1 end delta += 1 n += 1 end output end |