-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
punycode.cr
173 lines (148 loc) · 4.02 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
164
165
166
167
168
169
170
171
172
173
# `Punycode` provides an interface for IDNA encoding (RFC 5980),
# which is defined in RFC 3493
#
# Implementation based on Mathias Bynens `punnycode.js` project
# https://github.com/bestiejs/punycode.js/
#
# RFC 3492:
# Method to use non-ascii characters as host name of URI
# https://www.ietf.org/rfc/rfc3492.txt
#
# RFC 5980:
# Internationalized Domain Names in Application
# https://www.ietf.org/rfc/rfc5980.txt
class URI
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.build { |io| encode string, io }
end
def self.encode(string, io)
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)
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)
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