Coverage Report

Created: 2026-09-26 06:49

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibUnicode/Punycode.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2023, Simon Wanner <simon@skyrising.xyz>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <AK/Utf32View.h>
8
#include <AK/Utf8View.h>
9
#include <LibUnicode/Punycode.h>
10
11
namespace Unicode::Punycode {
12
13
// https://www.rfc-editor.org/rfc/rfc3492.html#section-5
14
static constexpr u32 BASE = 36;
15
static constexpr u32 TMIN = 1;
16
static constexpr u32 TMAX = 26;
17
static constexpr u32 SKEW = 38;
18
static constexpr u32 DAMP = 700;
19
static constexpr u32 INITIAL_BIAS = 72;
20
static constexpr u32 INITIAL_N = 0x80;
21
static constexpr u32 DELIMITER = '-';
22
23
static Optional<u32> digit_value_of_code_point(u32 code_point)
24
0
{
25
0
    if (code_point >= 'A' && code_point <= 'Z')
26
0
        return code_point - 'A';
27
0
    if (code_point >= 'a' && code_point <= 'z')
28
0
        return code_point - 'a';
29
0
    if (code_point >= '0' && code_point <= '9')
30
0
        return code_point - '0' + 26;
31
0
    return {};
32
0
}
33
34
static u32 code_point_value_of_digit(u32 digit)
35
0
{
36
0
    VERIFY(digit < 36);
37
0
    if (digit <= 25)
38
0
        return 'a' + digit;
39
0
    return '0' + digit - 26;
40
0
}
41
42
// https://www.rfc-editor.org/rfc/rfc3492.html#section-6.1
43
static u32 adapt(u32 delta, u32 num_points, bool first_time)
44
0
{
45
    // if firsttime then let delta = delta div damp
46
0
    if (first_time)
47
0
        delta = delta / DAMP;
48
    // else let delta = delta div 2
49
0
    else
50
0
        delta = delta / 2;
51
52
    // let delta = delta + (delta div numpoints)
53
0
    delta = delta + (delta / num_points);
54
55
    // let k = 0
56
0
    u32 k = 0;
57
58
    // while delta > ((base - tmin) * tmax) div 2 do begin
59
0
    while (delta > ((BASE - TMIN) * TMAX) / 2) {
60
        // let delta = delta div (base - tmin)
61
0
        delta = delta / (BASE - TMIN);
62
63
        // let k = k + base
64
0
        k = k + BASE;
65
0
    }
66
67
    // return k + (((base - tmin + 1) * delta) div (delta + skew))
68
0
    return k + (((BASE - TMIN + 1) * delta) / (delta + SKEW));
69
0
}
70
71
// https://www.rfc-editor.org/rfc/rfc3492.html#section-6.2
72
ErrorOr<String> decode(StringView input)
73
0
{
74
0
    size_t consumed = 0;
75
76
    // let n = initial_n
77
0
    Checked<size_t> n = INITIAL_N;
78
79
    // let i = 0
80
0
    Checked<u32> i = 0;
81
82
    // let bias = initial_bias
83
0
    u32 bias = INITIAL_BIAS;
84
85
    // let output = an empty string indexed from 0
86
0
    Vector<u32> output;
87
88
    // consume all code points before the last delimiter (if there is one)
89
    //   and copy them to output, fail on any non-basic code point
90
0
    Optional<size_t> last_delimiter_index = input.find_last(DELIMITER);
91
0
    if (last_delimiter_index.has_value()) {
92
0
        for (; consumed < last_delimiter_index.value(); consumed++) {
93
0
            if (!is_ascii(input[consumed]))
94
0
                return Error::from_string_literal("Unexpected non-basic code point");
95
0
            TRY(output.try_append(input[consumed]));
96
0
        }
97
98
        // if more than zero code points were consumed then consume one more
99
        //   (which will be the last delimiter)
100
0
        if (last_delimiter_index.value() > 0) {
101
0
            auto next = input[consumed++];
102
0
            VERIFY(next == DELIMITER);
103
0
        }
104
0
    }
105
106
    // while the input is not exhausted do begin
107
0
    while (consumed < input.length()) {
108
        // let oldi = i
109
0
        Checked<u32> old_i = i;
110
111
        // let w = 1
112
0
        Checked<u32> w = 1;
113
114
        // for k = base to infinity in steps of base do begin
115
0
        for (size_t k = BASE;; k += BASE) {
116
            // consume a code point, or fail if there was none to consume
117
0
            if (consumed >= input.length())
118
0
                return Error::from_string_literal("No more code points to consume");
119
0
            auto code_point = input[consumed++];
120
121
            // let digit = the code point's digit-value, fail if it has none
122
0
            auto digit = digit_value_of_code_point(code_point);
123
0
            if (!digit.has_value())
124
0
                return Error::from_string_literal("Invalid base-36 digit");
125
126
            // let i = i + digit * w, fail on overflow
127
0
            i = i + Checked(digit.value()) * w;
128
0
            if (i.has_overflow())
129
0
                return Error::from_string_literal("Numeric overflow");
130
131
            // let t = tmin if k <= bias {+ tmin}, or
132
            //         tmax if k >= bias + tmax, or k - bias otherwise
133
0
            u32 t = k <= bias ? TMIN : (k >= bias + TMAX ? TMAX : k - bias);
134
135
            // if digit < t then break
136
0
            if (digit.value() < t)
137
0
                break;
138
139
            // let w = w * (base - t), fail on overflow
140
0
            w = w * Checked(BASE - t);
141
0
            if (w.has_overflow())
142
0
                return Error::from_string_literal("Numeric overflow");
143
0
        }
144
        // let bias = adapt(i - oldi, length(output) + 1, test oldi is 0?)
145
0
        bias = adapt((i - old_i).value(), output.size() + 1, !old_i);
146
147
        // let n = n + i div (length(output) + 1), fail on overflow
148
0
        n = n + Checked(static_cast<size_t>(i.value() / static_cast<u32>(output.size() + 1)));
149
0
        if (n.has_overflow())
150
0
            return Error::from_string_literal("Numeric overflow");
151
152
        // let i = i mod (length(output) + 1)
153
0
        i = i % Checked(static_cast<u32>(output.size() + 1));
154
155
        // {if n is a basic code point then fail}
156
        // NOTE: The full statement enclosed in braces (checking whether n is a basic code point) can be omitted if initial_n exceeds all basic code points
157
        //       (which is true for Punycode), because n is never less than initial_n.
158
0
        VERIFY(!is_ascii(n.value()));
159
160
        // insert n into output at position i
161
0
        TRY(output.try_insert(i.value(), n.value()));
162
163
        // increment i
164
0
        i++;
165
0
    }
166
167
0
    StringBuilder builder;
168
0
    TRY(builder.try_append(Utf32View(output.data(), output.size())));
169
0
    return builder.to_string();
170
0
}
171
172
static Optional<u32> find_smallest_code_point_greater_than_or_equal(Utf32View code_points, u32 threshold)
173
0
{
174
0
    Optional<u32> result;
175
0
    for (auto code_point : code_points) {
176
0
        if (code_point >= threshold && (!result.has_value() || code_point < result.value()))
177
0
            result = code_point;
178
0
    }
179
0
    return result;
180
0
}
181
182
ErrorOr<String> encode(StringView input)
183
0
{
184
0
    Vector<u32> code_points;
185
0
    for (auto code_point : Utf8View(input))
186
0
        TRY(code_points.try_append(code_point));
187
0
    return encode(Utf32View(code_points.data(), code_points.size()));
188
0
}
189
190
// https://www.rfc-editor.org/rfc/rfc3492.html#section-6.3
191
ErrorOr<String> encode(Utf32View input)
192
0
{
193
0
    Vector<u32> output;
194
195
    // let n = initial_n
196
0
    Checked<size_t> n = INITIAL_N;
197
198
    // let delta = 0
199
0
    Checked<size_t> delta = 0;
200
201
    // let bias = initial_bias
202
0
    u32 bias = INITIAL_BIAS;
203
204
    // let h = b = the number of basic code points in the input
205
    // copy them to the output in order, followed by a delimiter if b > 0
206
0
    size_t b = 0;
207
0
    for (auto code_point : input) {
208
0
        if (is_ascii(code_point)) {
209
0
            TRY(output.try_append(code_point));
210
0
            b++;
211
0
        }
212
0
    }
213
0
    auto h = b;
214
0
    if (b > 0)
215
0
        TRY(output.try_append(DELIMITER));
216
217
    // while h < length(input) do begin
218
0
    while (h < input.length()) {
219
        // let m = the minimum {non-basic} code point >= n in the input
220
0
        auto m = find_smallest_code_point_greater_than_or_equal(input, n.value());
221
0
        VERIFY(m.has_value());
222
223
        // let delta = delta + (m - n) * (h + 1), fail on overflow
224
0
        delta = delta + (Checked(static_cast<size_t>(m.value())) - n) * Checked(h + 1);
225
0
        if (delta.has_overflow())
226
0
            return Error::from_string_literal("Numeric overflow");
227
228
        // let n = m
229
0
        n = m.value();
230
231
        // for each code point c in the input (in order) do begin
232
0
        for (auto c : input) {
233
            // if c < n {or c is basic} then increment delta, fail on overflow
234
0
            if (c < n.value()) {
235
0
                delta++;
236
0
                if (delta.has_overflow())
237
0
                    return Error::from_string_literal("Numeric overflow");
238
0
            }
239
240
            // if c == n then begin
241
0
            if (c == n.value()) {
242
                // let q = delta
243
0
                auto q = delta.value();
244
245
                // for k = base to infinity in steps of base do begin
246
0
                for (size_t k = BASE;; k += BASE) {
247
                    // let t = tmin if k <= bias {+ tmin}, or
248
                    //         tmax if k >= bias + tmax, or k - bias otherwise
249
0
                    u32 t = k <= bias ? TMIN : (k >= bias + TMAX ? TMAX : k - bias);
250
251
                    // if q < t then break
252
0
                    if (q < t)
253
0
                        break;
254
255
                    // output the code point for digit t + ((q - t) mod (base - t))
256
0
                    auto digit = t + ((q - t) % (BASE - t));
257
0
                    TRY(output.try_append(code_point_value_of_digit(digit)));
258
259
                    // let q = (q - t) div (base - t)
260
0
                    q = (q - t) / (BASE - t);
261
0
                }
262
                // output the code point for digit q
263
0
                TRY(output.try_append(code_point_value_of_digit(q)));
264
265
                // let bias = adapt(delta, h + 1, test h equals b?)
266
0
                bias = adapt(delta.value(), h + 1, h == b);
267
268
                // let delta = 0
269
0
                delta = 0;
270
271
                // increment h
272
0
                h++;
273
0
            }
274
0
        }
275
276
        // increment delta and n
277
0
        delta++;
278
0
        n++;
279
0
    }
280
281
0
    StringBuilder builder;
282
0
    TRY(builder.try_append(Utf32View(output.data(), output.size())));
283
0
    return builder.to_string();
284
0
}
285
286
}