Skip to content

Commit fd36562

Browse files
committed
IDNA
1 parent fcb44d8 commit fd36562

File tree

2 files changed

+233
-3
lines changed

2 files changed

+233
-3
lines changed
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
/*
2+
* Copyright 2025 okome.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package net.siisise.lang;
17+
18+
import java.text.Normalizer;
19+
20+
/**
21+
* IDNAの簡易版.
22+
* IDNA 2003 か 2008 準拠予定。(まだ)
23+
* 区切り文字は IDNA2003 U+ も可能とした。 とりあえず static
24+
* で作ってあとで分ける
25+
*
26+
*/
27+
@Deprecated
28+
public class IDNA {
29+
30+
// IDNA2003 の区切りも可能としている
31+
private static final java.lang.String SEPARATER = "[\\u002E\\u3002\\uFF0E\\uFF61]";
32+
33+
static enum LDH_TYPE {
34+
NON_LDH, // bin, bit
35+
UNDER_LABEL,
36+
U_LABEL,
37+
// LDH,
38+
NR_LDH,
39+
R_LDH, // ??--
40+
// BQ_LABEL, // bq-- RACE 廃止
41+
// XN_LABEL, // xn--
42+
FAKE_A_LABEL,
43+
A_LABEL, // valid xn label
44+
}
45+
46+
/**
47+
* ラベルの判定.
48+
* U_LABEL,A_LABEL,NR_LDHとUNDER_LABELぐらいが正常
49+
* NON_LDH, は正規化が必要かも
50+
* R_LDH 未対応または不正
51+
* FAKE_A_LABEL は不正
52+
*
53+
* @param a ラベル
54+
* @return 判定
55+
*/
56+
static LDH_TYPE type(java.lang.String a) {
57+
if (!isLDH(a)) {
58+
if (isASCII(a)) {
59+
if (a.charAt(0) == '_' && isLDH(a.substring(1))) { // 仮
60+
return LDH_TYPE.UNDER_LABEL;
61+
}
62+
return LDH_TYPE.NON_LDH;
63+
} else {
64+
// U-label かもしれない
65+
return isUlabel2008(a) && Punycode.toASCII(a).length() < 60 ? LDH_TYPE.U_LABEL : LDH_TYPE.NON_LDH;
66+
}
67+
} else { // LDH
68+
if (a.length() > 4 && a.toLowerCase().startsWith("xn--")) {
69+
// Aラベル または 疑似Aラベル
70+
return isAlabel2008(a) ? LDH_TYPE.A_LABEL : LDH_TYPE.FAKE_A_LABEL;
71+
} else if (isR_LDH(a)) { // ??--
72+
return LDH_TYPE.R_LDH; // 不明 旧RACEなど
73+
} else {
74+
return LDH_TYPE.NR_LDH; // 正規
75+
}
76+
}
77+
}
78+
79+
/**
80+
* A-label 判定.
81+
*
82+
* @param a LDH判定済み
83+
* @return まだてきとう
84+
*/
85+
private static boolean isAlabel2008(java.lang.String a) {
86+
// isLDHで最後に-が来ることはない
87+
try {
88+
java.lang.String u = Punycode.toUnicode(a.substring(4));
89+
return isUlabel2008(u);
90+
} catch (IllegalStateException e) {
91+
return false;
92+
}
93+
}
94+
95+
/**
96+
* U-label 判定.
97+
*
98+
* @param u 候補
99+
* @return 判定
100+
*/
101+
private static boolean isUlabel2008(java.lang.String u) {
102+
java.lang.String c = Normalizer.normalize(u, Normalizer.Form.NFC);
103+
if (!c.equals(u) || u.toLowerCase().startsWith("xn--")) {
104+
return false;
105+
}
106+
// ToDo: その他判定
107+
108+
// throw new UnsupportedOperationException();
109+
return true; // 仮
110+
}
111+
112+
/**
113+
* Letter Digit Hyphn
114+
*
115+
* @param a
116+
* @return
117+
*/
118+
private static boolean isLDH(java.lang.String a) {
119+
int[] chs = a.chars().toArray();
120+
for (int ch : chs) {
121+
if (ch < 0x2d || (ch > 0x2d && ch < 0x30) || (ch > 0x39 && ch < 0x41) || (ch > 0x5a && ch < 0x61) || (ch > 0x7a)) {
122+
return false;
123+
}
124+
}
125+
if (chs[0] == '-' || chs[chs.length - 1] == '-') {
126+
return false;
127+
}
128+
return true;
129+
}
130+
131+
/**
132+
* U-ラベル? ACE可能か判定.
133+
*
134+
* @param a
135+
* @return
136+
*/
137+
private static boolean isASCII(java.lang.String a) {
138+
int[] chs = a.chars().toArray();
139+
for (int ch : chs) {
140+
if (ch > 0x7f) {
141+
return false;
142+
}
143+
}
144+
return true;
145+
}
146+
147+
/**
148+
* R_LDHの判定.
149+
*
150+
* @param a
151+
* @return
152+
*/
153+
private static boolean isR_LDH(java.lang.String a) {
154+
return a.length() > 4 && a.substring(2, 4).equals("--");
155+
}
156+
157+
/**
158+
* ドメインの変換.
159+
* 検索用途では先に正規化しておくといい
160+
* @param u 一般ドメイン
161+
* @return A-label側に揃えたドメイン
162+
*/
163+
public static java.lang.String toASCII(java.lang.String u) {
164+
// U+002E U+3002 U+FF0E U+FF61
165+
java.lang.String[] sp = u.split(SEPARATER); // IDNA 2008 ではピリオドのみ
166+
java.lang.String[] t = new java.lang.String[sp.length];
167+
for (int i = 0; i < t.length; i++) {
168+
t[i] = toASCIILabel(NFKC(sp[i]));
169+
}
170+
return java.lang.String.join(".", t);
171+
}
172+
173+
static final java.lang.String NFKC(java.lang.String u) {
174+
return Normalizer.normalize(u, Normalizer.Form.NFKC);
175+
}
176+
177+
/**
178+
* U-ラベルを正規化、A-ラベルに変換する。
179+
* その他は変換しない
180+
*
181+
* @param u U-ラベル
182+
* @return A-ラベル
183+
*/
184+
public static java.lang.String toASCIILabel(java.lang.String u) {
185+
u = NFKC(u).toLowerCase();
186+
LDH_TYPE t = type(u);
187+
if (t != LDH_TYPE.U_LABEL) {
188+
return u;
189+
}
190+
return "xn--" + Punycode.toASCII(u);
191+
}
192+
193+
/**
194+
* ドメインをUnicode側へ変換.
195+
* フィルタしていないので注意.
196+
*
197+
* @param a 一般ドメイン
198+
* @return U-label側にそろえた
199+
*
200+
*/
201+
public static java.lang.String toUnicode(java.lang.String a) {
202+
java.lang.String[] sp = a.split(SEPARATER);
203+
java.lang.String[] t = new java.lang.String[sp.length];
204+
for (int i = 0; i < t.length; i++) {
205+
t[i] = toUnicodeLabel(sp[i]);
206+
}
207+
return java.lang.String.join(".", t);
208+
}
209+
210+
/**
211+
* A-ラベルを正規化されたU-ラベルに変換する。
212+
* フィルタしていないので注意.
213+
* 逆変換の保証はしない
214+
* その他は変換しない
215+
*
216+
* @param a A-ラベル
217+
* @return U-ラベル
218+
*/
219+
public static java.lang.String toUnicodeLabel(java.lang.String a) {
220+
LDH_TYPE t = type(a);
221+
if (t != LDH_TYPE.A_LABEL) {
222+
return a;
223+
}
224+
java.lang.String u = Punycode.toUnicode(a.substring(4).toLowerCase());
225+
return NFKC(u);
226+
}
227+
}

src/main/java/net/siisise/lang/Punycode.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ public static java.lang.String toASCII(java.lang.String u) {
7676
if (sb.length() > 0) {
7777
// ASCII あり
7878
// xn-- を付ける場合 ASCII + 国際化両方あり
79-
sb.append('-');
79+
sb.append(DELIMIT);
8080
}
8181
if (st.size() == 0) { // 国際化なし ASCIIのみ
8282
return sb.toString();
@@ -92,11 +92,11 @@ public static java.lang.String toASCII(java.lang.String u) {
9292
tn++;
9393
int d = DAMP;
9494
while (st.length() > 0) {
95-
int ostat = c + n + 1;
95+
int cbase = c + n + 1;
9696
st.read(dc);
9797
c = Bin.btoi(dc)[0];
9898
n = c / tn;
99-
int delta = c - ostat;
99+
int delta = c - cbase;
100100
sb.append(toCh(delta, bias)); // delta からコード
101101
bias = adapt(delta, d, tn);
102102
d = 2;
@@ -205,6 +205,9 @@ public static java.lang.String toUnicode(java.lang.String a) {
205205
n = 0;
206206
}
207207
}
208+
if ( w != 1) {
209+
throw new IllegalStateException();
210+
}
208211

209212
return sb.toString();
210213
}

0 commit comments

Comments
 (0)