//
//  ========================================================================
//  Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.util;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Base64;

/**
 * Fast B64 Encoder/Decoder as described in RFC 1421.
 * <p>Does not insert or interpret whitespace as described in RFC
 * 1521. If you require this you must pre/post process your data.
 * <p> Note that in a web context the usual case is to not want
 * linebreaks or other white space in the encoded output.
 *
 * @deprecated use {@link java.util.Base64} instead
 */
@Deprecated
public class B64Code
{
    private static final char __pad = '=';
    private static final char[] __rfc1421alphabet =
        {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
            'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
            'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
        };

    private static final byte[] __rfc1421nibbles;

    static
    {
        __rfc1421nibbles = new byte[256];
        for (int i = 0; i < 256; i++)
        {
            __rfc1421nibbles[i] = -1;
        }
        for (byte b = 0; b < 64; b++)
        {
            __rfc1421nibbles[(byte)__rfc1421alphabet[b]] = b;
        }
        __rfc1421nibbles[(byte)__pad] = 0;
    }

    private static final char[] __rfc4648urlAlphabet =
        {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
            'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
            'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
            'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'
        };

    private static final byte[] __rfc4648urlNibbles;

    static
    {
        __rfc4648urlNibbles = new byte[256];
        for (int i = 0; i < 256; i++)
        {
            __rfc4648urlNibbles[i] = -1;
        }
        for (byte b = 0; b < 64; b++)
        {
            __rfc4648urlNibbles[(byte)__rfc4648urlAlphabet[b]] = b;
        }
        __rfc4648urlNibbles[(byte)__pad] = 0;
    }

    private B64Code()
    {
    }

    /**
     * Base 64 encode as described in RFC 1421.
     * <p>Does not insert whitespace as described in RFC 1521.
     *
     * @param s String to encode.
     * @return String containing the encoded form of the input.
     * @deprecated use {@link Base64.Encoder#encodeToString(byte[])}} instead.
     */
    public static String encode(String s)
    {
        // NOTE: no Jetty mainline code uses this anymore
        return encode(s, (Charset)null);
    }

    /**
     * Base 64 encode as described in RFC 1421.
     * <p>Does not insert whitespace as described in RFC 1521.
     *
     * @param s String to encode.
     * @param charEncoding String representing the name of
     * the character encoding of the provided input String.
     * @return String containing the encoded form of the input.
     */
    public static String encode(String s, String charEncoding)
    {
        // NOTE: no Jetty mainline code uses this anymore
        byte[] bytes;
        if (charEncoding == null)
            bytes = s.getBytes(StandardCharsets.ISO_8859_1);
        else
            bytes = s.getBytes(Charset.forName(charEncoding));
        return new String(encode(bytes));
    }

    /**
     * Base 64 encode as described in RFC 1421.
     * <p>Does not insert whitespace as described in RFC 1521.
     *
     * @param s String to encode.
     * @param charEncoding The character encoding of the provided input String.
     * @return String containing the encoded form of the input.
     */
    public static String encode(String s, Charset charEncoding)
    {
        // NOTE: no Jetty mainline code uses this anymore
        byte[] bytes = s.getBytes(charEncoding == null ? StandardCharsets.ISO_8859_1 : charEncoding);
        return new String(encode(bytes));
    }

    /**
     * Fast Base 64 encode as described in RFC 1421.
     * <p>Does not insert whitespace as described in RFC 1521.
     * <p> Avoids creating extra copies of the input/output.
     *
     * @param b byte array to encode.
     * @return char array containing the encoded form of the input.
     */
    public static char[] encode(byte[] b)
    {
        // NOTE: no Jetty mainline code uses this anymore
        if (b == null)
            return null;

        int bLen = b.length;
        int cLen = ((bLen + 2) / 3) * 4;
        char[] c = new char[cLen];
        int ci = 0;
        int bi = 0;
        byte b0;
        byte b1;
        byte b2;
        int stop = (bLen / 3) * 3;
        while (bi < stop)
        {
            b0 = b[bi++];
            b1 = b[bi++];
            b2 = b[bi++];
            c[ci++] = __rfc1421alphabet[(b0 >>> 2) & 0x3f];
            c[ci++] = __rfc1421alphabet[(b0 << 4) & 0x3f | (b1 >>> 4) & 0x0f];
            c[ci++] = __rfc1421alphabet[(b1 << 2) & 0x3f | (b2 >>> 6) & 0x03];
            c[ci++] = __rfc1421alphabet[b2 & 0x3f];
        }

        if (bLen != bi)
        {
            switch (bLen % 3)
            {
                case 2:
                    b0 = b[bi++];
                    b1 = b[bi++];
                    c[ci++] = __rfc1421alphabet[(b0 >>> 2) & 0x3f];
                    c[ci++] = __rfc1421alphabet[(b0 << 4) & 0x3f | (b1 >>> 4) & 0x0f];
                    c[ci++] = __rfc1421alphabet[(b1 << 2) & 0x3f];
                    c[ci++] = __pad;
                    break;

                case 1:
                    b0 = b[bi++];
                    c[ci++] = __rfc1421alphabet[(b0 >>> 2) & 0x3f];
                    c[ci++] = __rfc1421alphabet[(b0 << 4) & 0x3f];
                    c[ci++] = __pad;
                    c[ci++] = __pad;
                    break;

                default:
                    break;
            }
        }

        return c;
    }

    /**
     * Fast Base 64 encode as described in RFC 1421 and RFC2045
     * <p>Does not insert whitespace as described in RFC 1521, unless rfc2045 is passed as true.
     * <p> Avoids creating extra copies of the input/output.
     *
     * @param b byte array to encode.
     * @param rfc2045 If true, break lines at 76 characters with CRLF
     * @return char array containing the encoded form of the input.
     */
    public static char[] encode(byte[] b, boolean rfc2045)
    {
        // NOTE: no Jetty mainline code uses this anymore
        if (b == null)
            return null;
        if (!rfc2045)
            return encode(b);

        int bLen = b.length;
        int cLen = ((bLen + 2) / 3) * 4;
        cLen += 2 + 2 * (cLen / 76);
        char[] c = new char[cLen];
        int ci = 0;
        int bi = 0;
        byte b0;
        byte b1;
        byte b2;
        int stop = (bLen / 3) * 3;
        int l = 0;
        while (bi < stop)
        {
            b0 = b[bi++];
            b1 = b[bi++];
            b2 = b[bi++];
            c[ci++] = __rfc1421alphabet[(b0 >>> 2) & 0x3f];
            c[ci++] = __rfc1421alphabet[(b0 << 4) & 0x3f | (b1 >>> 4) & 0x0f];
            c[ci++] = __rfc1421alphabet[(b1 << 2) & 0x3f | (b2 >>> 6) & 0x03];
            c[ci++] = __rfc1421alphabet[b2 & 0x3f];
            l += 4;
            if (l % 76 == 0)
            {
                c[ci++] = 13;
                c[ci++] = 10;
            }
        }

        if (bLen != bi)
        {
            switch (bLen % 3)
            {
                case 2:
                    b0 = b[bi++];
                    b1 = b[bi++];
                    c[ci++] = __rfc1421alphabet[(b0 >>> 2) & 0x3f];
                    c[ci++] = __rfc1421alphabet[(b0 << 4) & 0x3f | (b1 >>> 4) & 0x0f];
                    c[ci++] = __rfc1421alphabet[(b1 << 2) & 0x3f];
                    c[ci++] = __pad;
                    break;

                case 1:
                    b0 = b[bi++];
                    c[ci++] = __rfc1421alphabet[(b0 >>> 2) & 0x3f];
                    c[ci++] = __rfc1421alphabet[(b0 << 4) & 0x3f];
                    c[ci++] = __pad;
                    c[ci++] = __pad;
                    break;

                default:
                    break;
            }
        }

        c[ci++] = 13;
        c[ci++] = 10;
        return c;
    }

    /**
     * Base 64 decode as described in RFC 2045.
     * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
     *
     * @param encoded String to decode.
     * @param charEncoding String representing the character encoding
     * used to map the decoded bytes into a String. If null
     * the platforms default charset is used.
     * @return String decoded byte array.
     * @throws UnsupportedCharsetException if the encoding is not supported
     * @throws IllegalArgumentException if the input is not a valid
     * B64 encoding.
     */
    @SuppressWarnings("DefaultCharset")
    public static String decode(String encoded, String charEncoding)
    {
        // FIXME: no Jetty mainline code uses this anymore
        byte[] decoded = decode(encoded);
        if (charEncoding == null)
            return new String(decoded);
        return new String(decoded, Charset.forName(charEncoding));
    }

    /**
     * Base 64 decode as described in RFC 2045.
     * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
     *
     * @param encoded String to decode.
     * @param charEncoding Character encoding
     * used to map the decoded bytes into a String. If null
     * the platforms default charset is used.
     * @return String decoded byte array.
     * @throws IllegalArgumentException if the input is not a valid
     * B64 encoding.
     */
    @SuppressWarnings("DefaultCharset")
    public static String decode(String encoded, Charset charEncoding)
    {
        // FIXME: no Jetty mainline code uses this anymore
        byte[] decoded = decode(encoded);
        if (charEncoding == null)
            return new String(decoded);
        return new String(decoded, charEncoding);
    }

    /**
     * Fast Base 64 decode as described in RFC 1421.
     *
     * <p>Unlike other decode methods, this does not attempt to
     * cope with extra whitespace as described in RFC 1521/2045.
     * <p> Avoids creating extra copies of the input/output.
     * <p> Note this code has been flattened for performance.
     *
     * @param b char array to decode.
     * @return byte array containing the decoded form of the input.
     * @throws IllegalArgumentException if the input is not a valid
     * B64 encoding.
     */
    public static byte[] decode(char[] b)
    {
        // FIXME: no Jetty mainline code uses this anymore
        if (b == null)
            return null;

        int bLen = b.length;
        if (bLen % 4 != 0)
            throw new IllegalArgumentException("Input block size is not 4");

        int li = bLen - 1;
        while (li >= 0 && b[li] == (byte)__pad)
        {
            li--;
        }

        if (li < 0)
            return new byte[0];

        // Create result array of exact required size.
        int rLen = ((li + 1) * 3) / 4;
        byte[] r = new byte[rLen];
        int ri = 0;
        int bi = 0;
        int stop = (rLen / 3) * 3;
        byte b0;
        byte b1;
        byte b2;
        byte b3;
        try
        {
            while (ri < stop)
            {
                b0 = __rfc1421nibbles[b[bi++]];
                b1 = __rfc1421nibbles[b[bi++]];
                b2 = __rfc1421nibbles[b[bi++]];
                b3 = __rfc1421nibbles[b[bi++]];
                if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0)
                    throw new IllegalArgumentException("Not B64 encoded");

                r[ri++] = (byte)(b0 << 2 | b1 >>> 4);
                r[ri++] = (byte)(b1 << 4 | b2 >>> 2);
                r[ri++] = (byte)(b2 << 6 | b3);
            }

            if (rLen != ri)
            {
                switch (rLen % 3)
                {
                    case 2:
                        b0 = __rfc1421nibbles[b[bi++]];
                        b1 = __rfc1421nibbles[b[bi++]];
                        b2 = __rfc1421nibbles[b[bi++]];
                        if (b0 < 0 || b1 < 0 || b2 < 0)
                            throw new IllegalArgumentException("Not B64 encoded");
                        r[ri++] = (byte)(b0 << 2 | b1 >>> 4);
                        r[ri++] = (byte)(b1 << 4 | b2 >>> 2);
                        break;

                    case 1:
                        b0 = __rfc1421nibbles[b[bi++]];
                        b1 = __rfc1421nibbles[b[bi++]];
                        if (b0 < 0 || b1 < 0)
                            throw new IllegalArgumentException("Not B64 encoded");
                        r[ri++] = (byte)(b0 << 2 | b1 >>> 4);
                        break;

                    default:
                        break;
                }
            }
        }
        catch (IndexOutOfBoundsException e)
        {
            throw new IllegalArgumentException("char " + bi + " was not B64 encoded");
        }

        return r;
    }

    /**
     * Base 64 decode as described in RFC 2045.
     * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
     *
     * @param encoded String to decode.
     * @return byte array containing the decoded form of the input.
     * @throws IllegalArgumentException if the input is not a valid
     * B64 encoding.
     */
    public static byte[] decode(String encoded)
    {
        // FIXME: no Jetty mainline code uses this anymore
        if (encoded == null)
            return null;

        ByteArrayOutputStream bout = new ByteArrayOutputStream(4 * encoded.length() / 3);
        decode(encoded, bout);
        return bout.toByteArray();
    }

    /**
     * Base 64 decode as described in RFC 2045.
     * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
     *
     * @param encoded String to decode.
     * @param bout stream for decoded bytes
     * @throws IllegalArgumentException if the input is not a valid
     * B64 encoding.
     */
    public static void decode(String encoded, ByteArrayOutputStream bout)
    {
        // FIXME: no Jetty mainline code uses this anymore
        if (encoded == null)
            return;

        if (bout == null)
            throw new IllegalArgumentException("No outputstream for decoded bytes");

        int ci = 0;
        byte[] nibbles = new byte[4];
        int s = 0;

        while (ci < encoded.length())
        {
            char c = encoded.charAt(ci++);

            if (c == __pad)
                break;

            if (Character.isWhitespace(c))
                continue;

            byte nibble = __rfc1421nibbles[c];
            if (nibble < 0)
                throw new IllegalArgumentException("Not B64 encoded");

            nibbles[s++] = __rfc1421nibbles[c];

            switch (s)
            {
                case 1:
                    break;
                case 2:
                    bout.write(nibbles[0] << 2 | nibbles[1] >>> 4);
                    break;
                case 3:
                    bout.write(nibbles[1] << 4 | nibbles[2] >>> 2);
                    break;
                case 4:
                    bout.write(nibbles[2] << 6 | nibbles[3]);
                    s = 0;
                    break;
            }
        }

        return;
    }

    public static byte[] decodeRFC4648URL(String encoded)
    {
        // FIXME: no Jetty mainline code uses this anymore
        if (encoded == null)
            return null;

        ByteArrayOutputStream bout = new ByteArrayOutputStream(4 * encoded.length() / 3);
        decodeRFC4648URL(encoded, bout);
        return bout.toByteArray();
    }

    /**
     * Base 64 decode as described in RFC 4648 URL.
     * <p>Unlike {@link #decode(char[])}, extra whitespace is ignored.
     *
     * @param encoded String to decode.
     * @param bout stream for decoded bytes
     * @throws IllegalArgumentException if the input is not a valid
     * B64 encoding.
     */
    public static void decodeRFC4648URL(String encoded, ByteArrayOutputStream bout)
    {
        // FIXME: no Jetty mainline code uses this anymore
        if (encoded == null)
            return;

        if (bout == null)
            throw new IllegalArgumentException("No outputstream for decoded bytes");

        int ci = 0;
        byte[] nibbles = new byte[4];
        int s = 0;

        while (ci < encoded.length())
        {
            char c = encoded.charAt(ci++);

            if (c == __pad)
                break;

            if (Character.isWhitespace(c))
                continue;

            byte nibble = __rfc4648urlNibbles[c];
            if (nibble < 0)
                throw new IllegalArgumentException("Not B64 encoded");

            nibbles[s++] = __rfc4648urlNibbles[c];

            switch (s)
            {
                case 1:
                    break;
                case 2:
                    bout.write(nibbles[0] << 2 | nibbles[1] >>> 4);
                    break;
                case 3:
                    bout.write(nibbles[1] << 4 | nibbles[2] >>> 2);
                    break;
                case 4:
                    bout.write(nibbles[2] << 6 | nibbles[3]);
                    s = 0;
                    break;
            }
        }

        return;
    }

    public static void encode(int value, Appendable buf) throws IOException
    {
        // FIXME: no Jetty mainline code uses this anymore
        buf.append(__rfc1421alphabet[0x3f & ((0xFC000000 & value) >> 26)]);
        buf.append(__rfc1421alphabet[0x3f & ((0x03F00000 & value) >> 20)]);
        buf.append(__rfc1421alphabet[0x3f & ((0x000FC000 & value) >> 14)]);
        buf.append(__rfc1421alphabet[0x3f & ((0x00003F00 & value) >> 8)]);
        buf.append(__rfc1421alphabet[0x3f & ((0x000000FC & value) >> 2)]);
        buf.append(__rfc1421alphabet[0x3f & ((0x00000003 & value) << 4)]);
    }

    public static void encode(long lvalue, Appendable buf) throws IOException
    {
        // FIXME: no Jetty mainline code uses this anymore
        int value = (int)(0xFFFFFFFC & (lvalue >> 32));
        buf.append(__rfc1421alphabet[0x3f & ((0xFC000000 & value) >> 26)]);
        buf.append(__rfc1421alphabet[0x3f & ((0x03F00000 & value) >> 20)]);
        buf.append(__rfc1421alphabet[0x3f & ((0x000FC000 & value) >> 14)]);
        buf.append(__rfc1421alphabet[0x3f & ((0x00003F00 & value) >> 8)]);
        buf.append(__rfc1421alphabet[0x3f & ((0x000000FC & value) >> 2)]);

        buf.append(__rfc1421alphabet[0x3f & ((0x00000003 & value) << 4) + (0xf & (int)(lvalue >> 28))]);

        value = 0x0FFFFFFF & (int)lvalue;
        buf.append(__rfc1421alphabet[0x3f & ((0x0FC00000 & value) >> 22)]);
        buf.append(__rfc1421alphabet[0x3f & ((0x003F0000 & value) >> 16)]);
        buf.append(__rfc1421alphabet[0x3f & ((0x0000FC00 & value) >> 10)]);
        buf.append(__rfc1421alphabet[0x3f & ((0x000003F0 & value) >> 4)]);
        buf.append(__rfc1421alphabet[0x3f & ((0x0000000F & value) << 2)]);
    }
}
