001/*******************************************************************************
002The MIT License (MIT)
003
004Copyright (c) 2024 KILLCODING.COM
005
006Permission is hereby granted, free of charge, to any person obtaining a copy
007of this software and associated documentation files (the "Software"), to deal
008in the Software without restriction, including without limitation the rights
009to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010copies of the Software, and to permit persons to whom the Software is
011furnished to do so, subject to the following conditions:
012
013The above copyright notice and this permission notice shall be included in
014all copies or substantial portions of the Software.
015
016THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
022THE SOFTWARE.
023*****************************************************************************/
024package com.killcoding.tool;
025
026import com.killcoding.file.BaseFile;
027import java.io.UnsupportedEncodingException;
028
029/**
030 * This class is support Base64 tools
031 * */
032public final class Base64Tools {
033
034        // Mapping table from 6-bit nibbles to Base64 characters.
035        private static final char[] map1 = new char[64];
036        static {
037                int i = 0;
038                for (char c = 'A'; c <= 'Z'; c++)
039                        map1[i++] = c;
040                for (char c = 'a'; c <= 'z'; c++)
041                        map1[i++] = c;
042                for (char c = '0'; c <= '9'; c++)
043                        map1[i++] = c;
044                map1[i++] = '+';
045                map1[i++] = '/';
046        }
047
048        // Mapping table from Base64 characters to 6-bit nibbles.
049        private static final byte[] map2 = new byte[128];
050        static {
051                for (int i = 0; i < map2.length; i++)
052                        map2[i] = -1;
053                for (int i = 0; i < 64; i++)
054                        map2[map1[i]] = (byte) i;
055        }
056
057        public static String encodeString(String s) throws UnsupportedEncodingException {
058                return new String(encode(s.getBytes(BaseFile.CHARSET)));
059        }
060
061        public static String encodeLines(byte[] in) {
062                return encodeLines(in, 0, in.length, 76, System.lineSeparator());
063        }
064
065        public static String encodeLines(byte[] in, int iOff, int iLen,
066                        int lineLen, String lineSeparator) {
067                int blockLen = (lineLen * 3) / 4;
068                if (blockLen <= 0)
069                        throw new IllegalArgumentException();
070                int lines = (iLen + blockLen - 1) / blockLen;
071                int bufLen = ((iLen + 2) / 3) * 4 + lines * lineSeparator.length();
072                StringBuilder buf = new StringBuilder(bufLen);
073                int ip = 0;
074                while (ip < iLen) {
075                        int l = Math.min(iLen - ip, blockLen);
076                        buf.append(encode(in, iOff + ip, l));
077                        buf.append(lineSeparator);
078                        ip += l;
079                }
080                return buf.toString();
081        }
082
083        public static char[] encode(byte[] in) {
084                return encode(in, 0, in.length);
085        }
086
087        public static char[] encode(byte[] in, int iLen) {
088                return encode(in, 0, iLen);
089        }
090
091        public static char[] encode(byte[] in, int iOff, int iLen) {
092                int oDataLen = (iLen * 4 + 2) / 3; // output length without padding
093                int oLen = ((iLen + 2) / 3) * 4; // output length including padding
094                char[] out = new char[oLen];
095                int ip = iOff;
096                int iEnd = iOff + iLen;
097                int op = 0;
098                while (ip < iEnd) {
099                        int i0 = in[ip++] & 0xff;
100                        int i1 = ip < iEnd ? in[ip++] & 0xff : 0;
101                        int i2 = ip < iEnd ? in[ip++] & 0xff : 0;
102                        int o0 = i0 >>> 2;
103                        int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
104                        int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
105                        int o3 = i2 & 0x3F;
106                        out[op++] = map1[o0];
107                        out[op++] = map1[o1];
108                        out[op] = op < oDataLen ? map1[o2] : '=';
109                        op++;
110                        out[op] = op < oDataLen ? map1[o3] : '=';
111                        op++;
112                }
113                return out;
114        }
115
116        public static String decodeString(String s) throws UnsupportedEncodingException {
117                return new String(decode(s),BaseFile.CHARSET);
118        }
119
120        public static byte[] decodeLines(String s) {
121                char[] buf = new char[s.length()];
122                int p = 0;
123                for (int ip = 0; ip < s.length(); ip++) {
124                        char c = s.charAt(ip);
125                        if (c != ' ' && c != '\r' && c != '\n' && c != '\t')
126                                buf[p++] = c;
127                }
128                return decode(buf, 0, p);
129        }
130
131        public static byte[] decode(String s) {
132                return decode(s.toCharArray());
133        }
134
135        public static byte[] decode(char[] in) {
136                return decode(in, 0, in.length);
137        }
138
139        public static byte[] decode(char[] in, int iOff, int iLen) {
140                if (iLen % 4 != 0)
141                        throw new IllegalArgumentException(
142                                        "Length of Base64 encoded input string is not a multiple of 4.");
143                while (iLen > 0 && in[iOff + iLen - 1] == '=')
144                        iLen--;
145                int oLen = (iLen * 3) / 4;
146                byte[] out = new byte[oLen];
147                int ip = iOff;
148                int iEnd = iOff + iLen;
149                int op = 0;
150                while (ip < iEnd) {
151                        int i0 = in[ip++];
152                        int i1 = in[ip++];
153                        int i2 = ip < iEnd ? in[ip++] : 'A';
154                        int i3 = ip < iEnd ? in[ip++] : 'A';
155                        if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127)
156                                throw new IllegalArgumentException(
157                                                "Illegal character in Base64 encoded data.");
158                        int b0 = map2[i0];
159                        int b1 = map2[i1];
160                        int b2 = map2[i2];
161                        int b3 = map2[i3];
162                        if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0)
163                                throw new IllegalArgumentException(
164                                                "Illegal character in Base64 encoded data.");
165                        int o0 = (b0 << 2) | (b1 >>> 4);
166                        int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2);
167                        int o2 = ((b2 & 3) << 6) | b3;
168                        out[op++] = (byte) o0;
169                        if (op < oLen)
170                                out[op++] = (byte) o1;
171                        if (op < oLen)
172                                out[op++] = (byte) o2;
173                }
174                return out;
175        }
176
177}