|
| 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 | +} |
0 commit comments