All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.phloc.web.encoding.Punycode Maven / Gradle / Ivy

/**
 * Copyright (C) 2006-2015 phloc systems
 * http://www.phloc.com
 * office[at]phloc[dot]com
 *
 * 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 com.phloc.web.encoding;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;

import com.phloc.commons.annotations.PresentForCodeCoverage;
import com.phloc.commons.codec.DecoderException;
import com.phloc.commons.codec.EncoderException;
import com.phloc.commons.i18n.CodepointIteratorCharArray;
import com.phloc.commons.i18n.CodepointUtils;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * Implementation of the Punycode encoding scheme used by IDNA
 * 
 * @author Apache Abdera
 */
@Immutable
public final class Punycode
{
  private static final int BASE = 0x24; // 36
  private static final int TMIN = 0x01; // 1
  private static final int TMAX = 0x1A; // 26
  private static final int SKEW = 0x26; // 38
  private static final int DAMP = 0x02BC; // 700
  private static final int INITIAL_BIAS = 0x48; // 72
  private static final int INITIAL_N = 0x80; // 0x80
  private static final int DELIMITER = 0x2D; // 0x2D

  @PresentForCodeCoverage
  @SuppressWarnings ("unused")
  private static final Punycode s_aInstance = new Punycode ();

  private Punycode ()
  {}

  private static boolean _basic (final int cp)
  {
    return cp < 0x80;
  }

  private static boolean _delim (final int cp)
  {
    return cp == DELIMITER;
  }

  private static boolean _flagged (final int bcp)
  {
    return (bcp - 65) < 26;
  }

  private static int _decode_digit (final int cp)
  {
    return (cp - 48 < 10) ? cp - 22 : (cp - 65 < 26) ? cp - 65 : (cp - 97 < 26) ? cp - 97 : BASE;
  }

  private static int _t (final boolean c)
  {
    return c ? 1 : 0;
  }

  private static int _encode_digit (final int d, final boolean upper)
  {
    return (d + 22 + 75 * _t (d < 26)) - (_t (upper) << 5);
  }

  private static int _adapt (final int pdelta, final int numpoints, final boolean firsttime)
  {
    int delta = pdelta;
    int k;
    delta = (firsttime) ? delta / DAMP : delta >> 1;
    delta += delta / numpoints;
    for (k = 0; delta > ((BASE - TMIN) * TMAX) / 2; k += BASE)
    {
      delta /= BASE - TMIN;
    }
    return k + (BASE - TMIN + 1) * delta / (delta + SKEW);
  }

  @Nullable
  public static String encode (@Nullable final String s)
  {
    if (s == null)
      return null;
    return encode (s.toCharArray (), null);
  }

  public static String encode (@Nonnull final char [] aChars, @Nullable final boolean [] aCaseFlags)
  {
    final StringBuilder aSB = new StringBuilder ();
    final CodepointIteratorCharArray aIter = new CodepointIteratorCharArray (aChars);
    int n = INITIAL_N;
    int delta = 0;
    int bias = INITIAL_BIAS;
    int i = -1;
    while (aIter.hasNext ())
    {
      i = aIter.next ().getValue ();
      if (_basic (i))
      {
        if (aCaseFlags == null)
        {
          aSB.append ((char) i);
        }
      }
    }
    int h, b, m, q, k, t;
    b = aSB.length ();
    h = b;
    if (b > 0)
      aSB.append ((char) DELIMITER);
    while (h < aChars.length)
    {
      aIter.position (0);
      i = -1;
      m = Integer.MAX_VALUE;
      while (aIter.hasNext ())
      {
        i = aIter.next ().getValue ();
        if (i >= n && i < m)
          m = i;
      }
      if (m - n > (Integer.MAX_VALUE - delta) / (h + 1))
        throw new EncoderException ("Overflow");
      delta += (m - n) * (h + 1);
      n = m;
      aIter.position (0);
      i = -1;
      while (aIter.hasNext ())
      {
        i = aIter.next ().getValue ();
        if (i < n)
        {
          if (++delta == 0)
            throw new EncoderException ("Overflow");
        }
        if (i == n)
        {
          for (q = delta, k = BASE;; k += BASE)
          {
            t = k <= bias ? TMIN : k >= bias + TMAX ? TMAX : k - bias;
            if (q < t)
              break;
            aSB.append ((char) _encode_digit (t + (q - t) % (BASE - t), false));
            q = (q - t) / (BASE - t);
          }
          aSB.append ((char) _encode_digit (q, (aCaseFlags != null) ? aCaseFlags[aIter.position () - 1] : false));
          bias = _adapt (delta, h + 1, h == b);
          delta = 0;
          ++h;
        }
      }
      ++delta;
      ++n;
    }
    return aSB.toString ();
  }

  @Nullable
  public static String decode (@Nullable final String s)
  {
    if (s == null)
      return null;
    return decode (s.toCharArray (), null);
  }

  @SuppressFBWarnings ("QF_QUESTIONABLE_FOR_LOOP")
  @Nonnull
  public static String decode (@Nonnull final char [] aChars, @Nullable final boolean [] aCaseFlags)
  {
    final StringBuilder aSB = new StringBuilder ();
    int n, out, i, bias, b, j, in, oldi, w, k, digit, t;
    n = INITIAL_N;
    out = 0;
    i = 0;
    bias = INITIAL_BIAS;
    for (b = 0, j = 0; j < aChars.length; ++j)
      if (_delim (aChars[j]))
        b = j;
    for (j = 0; j < b; ++j)
    {
      if (aCaseFlags != null)
        aCaseFlags[out] = _flagged (aChars[j]);
      if (!_basic (aChars[j]))
        throw new DecoderException ("Bad Input");
      aSB.append (aChars[j]);
    }
    out = aSB.length ();
    for (in = (b > 0) ? b + 1 : 0; in < aChars.length; ++out)
    {
      for (oldi = i, w = 1, k = BASE;; k += BASE)
      {
        if (in > aChars.length)
          throw new DecoderException ("Bad input");
        digit = _decode_digit (aChars[in++]);
        if (digit >= BASE)
          throw new DecoderException ("Bad input");
        if (digit > (Integer.MAX_VALUE - i) / w)
          throw new DecoderException ("Overflow");
        i += digit * w;
        t = (k <= bias) ? TMIN : (k >= bias + TMAX) ? TMAX : k - bias;
        if (digit < t)
          break;
        if (w > Integer.MAX_VALUE / (BASE - t))
          throw new DecoderException ("Overflow");
        w *= (BASE - t);
      }
      bias = _adapt (i - oldi, out + 1, oldi == 0);
      if (i / (out + 1) > Integer.MAX_VALUE - n)
        throw new DecoderException ("Overflow");
      n += i / (out + 1);
      i %= (out + 1);
      if (aCaseFlags != null)
      {
        // not sure if this is right
        System.arraycopy (aCaseFlags, i, aCaseFlags, i + Character.charCount (n), aCaseFlags.length - i);
      }
      CodepointUtils.insert (aSB, i++, n);
    }
    return aSB.toString ();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy