authorgravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-01-20 23:47:35 -08:00
committergravatar for hello@nektro.netMeghan Denny <hello@nektro.net> 2026-01-20 23:47:35 -08:00
logf7a805254e559c21b3fc11f78b46221c0a4b42ab
tree531b8f81fa8c6dbfeb6868eeb314620d64269785
parent889939330ae5cff12cc54e0f0df4dc59808f1fce

move CheckBidi into inner iteration


1 files changed, 47 insertions(+), 41 deletions(-)

idna.zig+47-41
...@@ -249,6 +249,24 @@ fn Validity_Criteria(...@@ -249,6 +249,24 @@ fn Validity_Criteria(
249 var zwnj_state: enum { start, dual_joining, transparent1, zwnj, transparent2, right_joining } = .start;249 var zwnj_state: enum { start, dual_joining, transparent1, zwnj, transparent2, right_joining } = .start;
250 var check_zwnj_state: ?bool = null;250 var check_zwnj_state: ?bool = null;
251251
252 // 9. If CheckBidi, and if the domain name is a Bidi domain name, then the label must satisfy all six of the numbered conditions in RFC 5893 Section 2.
253 // 9.1. The first character must be a character with Bidi property L, R, or AL. If it has the R or AL property, it is an RTL label; if it has the L property, it is an LTR label.
254 // 9.2. In an RTL label, only characters with the Bidi properties R, AL, AN, EN, ES, CS, ET, ON, BN, or NSM are allowed.
255 // 9.3. In an RTL label, the end of the label must be a character with Bidi property R, AL, EN, or AN, followed by zero or more characters with Bidi property NSM.
256 // 9.4. In an RTL label, if an EN is present, no AN may be present, and vice versa.
257 // 9.5. In an LTR label, only characters with the Bidi properties L, EN, ES, CS, ET, ON, BN, or NSM are allowed.
258 // 9.6. In an LTR label, the end of the label must be a character with Bidi property L or EN, followed by zero or more characters with Bidi property NSM.
259 var seen_en = false;
260 var seen_an = false;
261 var last_bidi_class: ucd.BidiClass = .NSM;
262 if (CheckBidi) {
263 switch (first_p[4]) {
264 .L => {},
265 .R, .AL => {},
266 else => return error.IDNAFailure,
267 }
268 }
269
252 while (it.nextCodepointSlice()) |sl| {270 while (it.nextCodepointSlice()) |sl| {
253 const cp = std.unicode.utf8Decode(sl) catch unreachable;271 const cp = std.unicode.utf8Decode(sl) catch unreachable;
254 const status, _, _ = mappingRow(cp);272 const status, _, _ = mappingRow(cp);
...@@ -293,10 +311,10 @@ fn Validity_Criteria(...@@ -293,10 +311,10 @@ fn Validity_Criteria(
293311
294 // 3. In addition, if UseSTD3ASCIIRules=true and the code point is an ASCII code point (U+0000..U+007F), then it must be a lowercase letter (a-z), a digit (0-9), or a hyphen-minus (U+002D).312 // 3. In addition, if UseSTD3ASCIIRules=true and the code point is an ASCII code point (U+0000..U+007F), then it must be a lowercase letter (a-z), a digit (0-9), or a hyphen-minus (U+002D).
295 // (Note: This excludes uppercase ASCII A-Z which are mapped in UTS #46 and disallowed in IDNA2008.)313 // (Note: This excludes uppercase ASCII A-Z which are mapped in UTS #46 and disallowed in IDNA2008.)
296 if (UseSTD3ASCIIRules and std.ascii.isAscii(sl[0])) {314 if (UseSTD3ASCIIRules and std.ascii.isAscii(sl[0])) blk: {
297 if (std.ascii.isLower(sl[0])) continue;315 if (std.ascii.isLower(sl[0])) break :blk;
298 if (std.ascii.isDigit(sl[0])) continue;316 if (std.ascii.isDigit(sl[0])) break :blk;
299 if (sl[0] == '-') continue;317 if (sl[0] == '-') break :blk;
300 return error.IDNAFailure;318 return error.IDNAFailure;
301 }319 }
302320
...@@ -380,61 +398,49 @@ fn Validity_Criteria(...@@ -380,61 +398,49 @@ fn Validity_Criteria(
380 }398 }
381 point_2before = point_before;399 point_2before = point_before;
382 point_before = p;400 point_before = p;
401
402 if (CheckBidi) {
403 switch (first_p[4]) {
404 .L => {
405 switch (p[4]) {
406 .L, .EN, .ES, .CS, .ET, .ON, .BN => |cl| last_bidi_class = cl,
407 .NSM => {},
408 else => return error.IDNAFailure,
409 }
410 },
411 .R, .AL => {
412 switch (p[4]) {
413 .R, .AL, .AN, .EN, .ES, .CS, .ET, .ON, .BN => |cl| last_bidi_class = cl,
414 .NSM => {},
415 else => return error.IDNAFailure,
416 }
417 if (p[4] == .EN and seen_an) return error.IDNAFailure;
418 if (p[4] == .EN) seen_en = true;
419 if (p[4] == .AN and seen_en) return error.IDNAFailure;
420 if (p[4] == .AN) seen_an = true;
421 },
422 else => unreachable,
423 }
424 }
383 }425 }
384 if (CheckJoiners) {426 if (CheckJoiners) {
385 if (check_zwnj_state == true and zwnj_state != .right_joining) return error.IDNAFailure;427 if (check_zwnj_state == true and zwnj_state != .right_joining) return error.IDNAFailure;
386 }428 }
387
388 // 9. If CheckBidi, and if the domain name is a Bidi domain name, then the label must satisfy all six of the numbered conditions in RFC 5893 Section 2.
389 if (CheckBidi) {429 if (CheckBidi) {
390 // 1. The first character must be a character with Bidi property L, R, or AL. If it has the R or AL property, it is an RTL label; if it has the L property, it is an LTR label.
391 // 2. In an RTL label, only characters with the Bidi properties R, AL, AN, EN, ES, CS, ET, ON, BN, or NSM are allowed.
392 // 3. In an RTL label, the end of the label must be a character with Bidi property R, AL, EN, or AN, followed by zero or more characters with Bidi property NSM.
393 // 4. In an RTL label, if an EN is present, no AN may be present, and vice versa.
394 // 5. In an LTR label, only characters with the Bidi properties L, EN, ES, CS, ET, ON, BN, or NSM are allowed.
395 // 6. In an LTR label, the end of the label must be a character with Bidi property L or EN, followed by zero or more characters with Bidi property NSM.
396 switch (first_p[4]) {430 switch (first_p[4]) {
397 .L => {431 .L => {
398 it.i = 0;
399 var last_bidi_class: ucd.BidiClass = .NSM;
400 while (it.nextCodepointSlice()) |sl| {
401 const cp = std.unicode.utf8Decode(sl) catch unreachable;
402 const point = ucd.unicode_data.find(cp);
403 switch (point[4]) {
404 .L, .EN, .ES, .CS, .ET, .ON, .BN => |cl| last_bidi_class = cl,
405 .NSM => {},
406 else => return error.IDNAFailure,
407 }
408 }
409 switch (last_bidi_class) {432 switch (last_bidi_class) {
410 .L, .EN => {},433 .L, .EN => {},
411 else => return error.IDNAFailure,434 else => return error.IDNAFailure,
412 }435 }
413 },436 },
414 .R, .AL => {437 .R, .AL => {
415 it.i = 0;
416 var seen_en = false;
417 var seen_an = false;
418 var last_bidi_class: ucd.BidiClass = .NSM;
419 while (it.nextCodepointSlice()) |sl| {
420 const cp = std.unicode.utf8Decode(sl) catch unreachable;
421 const point = ucd.unicode_data.find(cp);
422 switch (point[4]) {
423 .R, .AL, .AN, .EN, .ES, .CS, .ET, .ON, .BN => |cl| last_bidi_class = cl,
424 .NSM => {},
425 else => return error.IDNAFailure,
426 }
427 if (point[4] == .EN and seen_an) return error.IDNAFailure;
428 if (point[4] == .EN) seen_en = true;
429 if (point[4] == .AN and seen_en) return error.IDNAFailure;
430 if (point[4] == .AN) seen_an = true;
431 }
432 switch (last_bidi_class) {438 switch (last_bidi_class) {
433 .R, .AL, .EN, .AN => {},439 .R, .AL, .EN, .AN => {},
434 else => return error.IDNAFailure,440 else => return error.IDNAFailure,
435 }441 }
436 },442 },
437 else => return error.IDNAFailure,443 else => unreachable,
438 }444 }
439 }445 }
440}446}