/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package libcore.icu; /** * Java version of Android's NativeIDN class, rewritten for J2ObjC because the * Android version uses native code that depends on ICU's uidna functions, which * aren't public on iOS. * <p> * This code is converted from the pseudo-code from the RFC 3492 specification * (https://www.ietf.org/rfc/rfc3492.txt). */ public final class NativeIDN { // Bootstring parameters for Punycode. private static final int BASE = 36; private static final int TMIN = 1; private static final int TMAX = 26; private static final int SKEW = 38; private static final int DAMP = 700; private static final int INITIAL_BIAS = 72; private static final int INITIAL_N = 128; private static final char DELIMITER = '-'; /** * Convert a Unicode string to Punycode/ASCII. The flags parameter is * ignored; it's used by the ICU functions, but the spec doesn't describe * a need for them. */ public static String toASCII(String s, int flags) { int n = INITIAL_N; int delta = 0; int bias = INITIAL_BIAS; StringBuffer output = new StringBuffer(); // Copy all basic code points to the output. int b = 0; for (int i = 0; i < s.length(); i++) { char c = s.charAt(i); if (basicCodePoint(c)) { output.append(c); b++; } } if (b > 0) { output.append(DELIMITER); } int h = b; while (h < s.length()) { int m = Integer.MAX_VALUE; for (int i = 0; i < s.length(); i++) { int c = s.charAt(i); if (c >= n && c < m) { m = c; } } if (m - n > (Integer.MAX_VALUE - delta) / (h + 1)) { // This should probably be a java.text.ParseException, but // IllegalArgumentException is what Android's IDN expects. throw new IllegalArgumentException("encoding overflow"); } delta = delta + (m - n) * (h + 1); n = m; for (int j = 0; j < s.length(); j++) { int c = s.charAt(j); if (c < n) { delta++; if (0 == delta) { throw new IllegalArgumentException("encoding overflow"); } } if (c == n) { int q = delta; for (int k = BASE;; k += BASE) { int t; if (k <= bias) { t = TMIN; } else if (k >= bias + TMAX) { t = TMAX; } else { t = k - bias; } if (q < t) { break; } output.append((char) encodeDigit(t + (q - t) % (BASE - t))); q = (q - t) / (BASE - t); } output.append((char) encodeDigit(q)); bias = adapt(delta, h + 1, h == b); delta = 0; h++; } } delta++; n++; } return output.toString(); } /** * Convert a Punycode/ASCII string to Unicode. The flags parameter is ignored; * it's used by the ICU functions, but the spec doesn't describe a need * for them. */ public static String toUnicode(String s, int flags) { int n = INITIAL_N; int i = 0; int bias = INITIAL_BIAS; StringBuffer output = new StringBuffer(); int d = s.lastIndexOf(DELIMITER); if (d > 0) { for (int j = 0; j < d; j++) { char c = s.charAt(j); if (!basicCodePoint(c)) { throw new IllegalArgumentException("bad input: " + c); } output.append(c); } d++; } else { d = 0; } while (d < s.length()) { int oldi = i; int w = 1; for (int k = BASE;; k += BASE) { if (d == s.length()) { throw new IllegalArgumentException("bad input: " + d); } int c = s.charAt(d++); int digit = decodeDigit(c); if (digit > (Integer.MAX_VALUE - i) / w) { throw new IllegalArgumentException("encoding overflow"); } i = i + digit * w; int t; if (k <= bias) { t = TMIN; } else if (k >= bias + TMAX) { t = TMAX; } else { t = k - bias; } if (digit < t) { break; } w = w * (BASE - t); } bias = adapt(i - oldi, output.length() + 1, oldi == 0); if (i / (output.length() + 1) > Integer.MAX_VALUE - n) { throw new IllegalArgumentException("encoding overflow"); } n = n + i / (output.length() + 1); i = i % (output.length() + 1); output.insert(i, (char) n); i++; } return output.toString(); } private static int adapt(int delta, int numpoints, boolean firsttime) { if (firsttime) { delta = delta / DAMP; } else { delta = delta / 2; } delta = delta + (delta / numpoints); int k = 0; while (delta > ((BASE - TMIN) * TMAX) / 2) { delta = delta / (BASE - TMIN); k = k + BASE; } return k + (((BASE - TMIN + 1) * delta) / (delta + SKEW)); } private static int decodeDigit(int c) { if (c >= 'A' && c <= 'Z') { return c - 'A'; } if (c >= 'a' && c <= 'z') { return (c - 'a'); } if (c >= '0' && c <= '9') { return c - '0' + 26; } throw new IllegalArgumentException("bad input: " + c); } private static char encodeDigit(int d) { if (d >= 0 && d <= 25) { return (char) (d + 'a'); } if (d >= 26 && d <= 35) { return (char) (d - 26 + '0'); } throw new IllegalArgumentException("bad input: " + d); } private static boolean basicCodePoint(int cp) { return cp < 0x80; } private NativeIDN() { } }