-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
punycode.cr
163 lines (138 loc) · 3.92 KB
/
punycode.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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
76
77
78
79
80
81
82
83
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
class URI
# `Punycode` provides an interface for IDNA encoding ([RFC 5980](https://tools.ietf.org/html/rfc5980)), defined in [RFC 3492](https://tools.ietf.org/html/rfc3492).
#
# Implementation based on Mathias Bynens' [punnycode.js](https://github.com/bestiejs/punycode.js) project.
class Punycode
private BASE = 36
private TMIN = 1
private TMAX = 26
private SKEW = 38
private DAMP = 700
private INITIAL_BIAS = 72
private INITIAL_N = 128
private DELIMITER = '-'
private BASE36 = "abcdefghijklmnopqrstuvwxyz0123456789"
private def self.adapt(delta, numpoints, firsttime)
delta //= firsttime ? DAMP : 2
delta += delta // numpoints
k = 0
while delta > ((BASE - TMIN) * TMAX) // 2
delta //= BASE - TMIN
k += BASE
end
k + (((BASE - TMIN + 1) * delta) // (delta + SKEW))
end
def self.encode(string) : String
String.build { |io| encode string, io }
end
def self.encode(string, io) : Nil
others = [] of Char
string.each_char do |c|
if c < '\u0080'
io << c
else
others.push c
end
end
return if others.empty?
others.sort!
h = string.size - others.size + 1
delta = 0_u32
n = INITIAL_N
bias = INITIAL_BIAS
firsttime = true
prev = nil
io << DELIMITER if h > 1
others.each do |m|
next if m == prev
prev = m
raise Exception.new("Overflow: input needs wider integers to process") if m.ord - n > (Int32::MAX - delta) // h
delta += (m.ord - n) * h
n = m.ord + 1
string.each_char do |c|
if c < m
raise Exception.new("Overflow: input needs wider integers to process") if delta > Int32::MAX - 1
delta += 1
elsif c == m
q = delta
k = BASE
loop do
t = k <= bias ? TMIN : k >= bias + TMAX ? TMAX : k - bias
break if q < t
io << BASE36[t + ((q - t) % (BASE - t))]
q = (q - t) // (BASE - t)
k += BASE
end
io << BASE36[q]
bias = adapt delta, h, firsttime
delta = 0
h += 1
firsttime = false
end
end
delta += 1
end
end
def self.decode(string) : String
output, _, rest = string.rpartition(DELIMITER)
output = output.chars
n = INITIAL_N
bias = INITIAL_BIAS
i = 0
init = true
w = oldi = k = 0
rest.each_char do |c|
if init
w = 1
oldi = i
k = BASE
init = false
end
digit = case c
when .ascii_lowercase?
c.ord - 0x61
when .ascii_uppercase?
c.ord - 0x41
when .ascii_number?
c.ord - 0x30 + 26
else
raise ArgumentError.new("Invalid input")
end
i += digit * w
t = k <= bias ? TMIN : k >= bias + TMAX ? TMAX : k - bias
unless digit < t
w *= BASE - t
k += BASE
else
outsize = output.size + 1
bias = adapt i - oldi, outsize, oldi == 0
n += i // outsize
i %= outsize
output.insert i, n.chr
i += 1
init = true
end
end
raise ArgumentError.new("Invalid input") unless init
output.join
end
def self.to_ascii(string) : String
return string if string.ascii_only?
String.build do |io|
first = true
string.split('.') do |part|
unless first
io << '.'
end
if part.ascii_only?
io << part
else
io << "xn--"
encode part, io
end
first = false
end
end
end
end
end