1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package io.netty5.handler.codec.base64;
21
22 import io.netty5.buffer.api.Buffer;
23 import io.netty5.buffer.api.BufferAllocator;
24 import io.netty5.util.ByteProcessor;
25
26 import static io.netty5.buffer.api.DefaultBufferAllocators.offHeapAllocator;
27 import static io.netty5.buffer.api.DefaultBufferAllocators.onHeapAllocator;
28 import static java.util.Objects.requireNonNull;
29
30
31
32
33
34
35
36
37
38 public final class Base64 {
39
40
41 private static final int MAX_LINE_LENGTH = 76;
42
43
44 private static final byte EQUALS_SIGN = (byte) '=';
45
46
47 private static final byte NEW_LINE = (byte) '\n';
48
49 private static final byte WHITE_SPACE_ENC = -5;
50
51 private static final byte EQUALS_SIGN_ENC = -1;
52
53 private static byte[] alphabet(Base64Dialect dialect) {
54 requireNonNull(dialect, "dialect");
55 return dialect.alphabet;
56 }
57
58 private static byte[] decodabet(Base64Dialect dialect) {
59 requireNonNull(dialect, "dialect");
60 return dialect.decodabet;
61 }
62
63 private static boolean breakLines(Base64Dialect dialect) {
64 requireNonNull(dialect, "dialect");
65 return dialect.breakLinesByDefault;
66 }
67
68 public static Buffer encode(Buffer src) {
69 return encode(src, Base64Dialect.STANDARD);
70 }
71
72 public static Buffer encode(Buffer src, Base64Dialect dialect) {
73 return encode(src, breakLines(dialect), dialect);
74 }
75
76 public static Buffer encode(Buffer src, boolean breakLines) {
77 return encode(src, breakLines, Base64Dialect.STANDARD);
78 }
79
80 public static Buffer encode(Buffer src, boolean breakLines, Base64Dialect dialect) {
81 requireNonNull(src, "src");
82
83 Buffer dest = encode(src, src.readerOffset(), src.readableBytes(), breakLines, dialect);
84 src.readerOffset(src.writerOffset());
85 return dest;
86 }
87
88 public static Buffer encode(Buffer src, int off, int len) {
89 return encode(src, off, len, Base64Dialect.STANDARD);
90 }
91
92 public static Buffer encode(Buffer src, int off, int len, Base64Dialect dialect) {
93 return encode(src, off, len, breakLines(dialect), dialect);
94 }
95
96 public static Buffer encode(
97 Buffer src, int off, int len, boolean breakLines) {
98 return encode(src, off, len, breakLines, Base64Dialect.STANDARD);
99 }
100
101 public static Buffer encode(
102 Buffer src, int off, int len, boolean breakLines, Base64Dialect dialect) {
103 return encode(src, off, len, breakLines, dialect,
104 src.isDirect() ? offHeapAllocator() : onHeapAllocator());
105 }
106
107 public static Buffer encode(
108 Buffer src, int off, int len, boolean breakLines, Base64Dialect dialect, BufferAllocator allocator) {
109 requireNonNull(src, "src");
110 requireNonNull(dialect, "dialect");
111
112 Buffer dest = allocator.allocate(encodedBufferSize(len, breakLines));
113 byte[] alphabet = alphabet(dialect);
114 int d = 0;
115 int e = 0;
116 int len2 = len - 2;
117 int lineLength = 0;
118 for (; d < len2; d += 3, e += 4) {
119 encode3to4(src, d + off, 3, dest, e, alphabet);
120
121 lineLength += 4;
122
123 if (breakLines && lineLength == MAX_LINE_LENGTH) {
124 dest.setByte(e + 4, NEW_LINE);
125 e ++;
126 lineLength = 0;
127 }
128 }
129
130 if (d < len) {
131 encode3to4(src, d + off, len - d, dest, e, alphabet);
132 e += 4;
133 }
134
135
136 if (e > 1 && dest.getByte(e - 1) == NEW_LINE) {
137 e--;
138 }
139
140 return dest.writerOffset(e);
141 }
142
143 private static void encode3to4(
144 Buffer src, int srcOffset, int numSigBytes, Buffer dest, int destOffset, byte[] alphabet) {
145
146
147
148
149
150
151
152
153
154
155
156 final int inBuff;
157 switch (numSigBytes) {
158 case 1:
159 inBuff = toInt(src.getByte(srcOffset));
160 break;
161 case 2:
162 inBuff = toIntBE(src.getShort(srcOffset));
163 break;
164 default:
165 inBuff = numSigBytes <= 0 ? 0 : toIntBE(src.getMedium(srcOffset));
166 break;
167 }
168 encode3to4BigEndian(inBuff, numSigBytes, dest, destOffset, alphabet);
169 }
170
171
172 static int encodedBufferSize(int len, boolean breakLines) {
173
174 long len43 = ((long) len << 2) / 3;
175
176
177 long ret = (len43 + 3) & ~3;
178
179 if (breakLines) {
180 ret += len43 / MAX_LINE_LENGTH;
181 }
182
183 return ret < Integer.MAX_VALUE ? (int) ret : Integer.MAX_VALUE;
184 }
185
186 private static int toInt(byte value) {
187 return (value & 0xff) << 16;
188 }
189
190 private static int toIntBE(short value) {
191 return (value & 0xff00) << 8 | (value & 0xff) << 8;
192 }
193
194 private static int toIntBE(int mediumValue) {
195 return (mediumValue & 0xff0000) | (mediumValue & 0xff00) | (mediumValue & 0xff);
196 }
197
198 private static void encode3to4BigEndian(
199 int inBuff, int numSigBytes, Buffer dest, int destOffset, byte[] alphabet) {
200
201 switch (numSigBytes) {
202 case 3:
203 dest.setInt(destOffset, alphabet[inBuff >>> 18 ] << 24 |
204 alphabet[inBuff >>> 12 & 0x3f] << 16 |
205 alphabet[inBuff >>> 6 & 0x3f] << 8 |
206 alphabet[inBuff & 0x3f]);
207 break;
208 case 2:
209 dest.setInt(destOffset, alphabet[inBuff >>> 18 ] << 24 |
210 alphabet[inBuff >>> 12 & 0x3f] << 16 |
211 alphabet[inBuff >>> 6 & 0x3f] << 8 |
212 EQUALS_SIGN);
213 break;
214 case 1:
215 dest.setInt(destOffset, alphabet[inBuff >>> 18 ] << 24 |
216 alphabet[inBuff >>> 12 & 0x3f] << 16 |
217 EQUALS_SIGN << 8 |
218 EQUALS_SIGN);
219 break;
220 default:
221
222 break;
223 }
224 }
225
226 public static Buffer decode(Buffer src) {
227 return decode(src, Base64Dialect.STANDARD);
228 }
229
230 public static Buffer decode(Buffer src, Base64Dialect dialect) {
231 requireNonNull(src, "src");
232
233 Buffer dest = decode(src, src.readerOffset(), src.readableBytes(), dialect);
234 src.readerOffset(src.writerOffset());
235 return dest;
236 }
237
238 public static Buffer decode(
239 Buffer src, int off, int len) {
240 return decode(src, off, len, Base64Dialect.STANDARD);
241 }
242
243 public static Buffer decode(
244 Buffer src, int off, int len, Base64Dialect dialect) {
245 return decode(src, off, len, dialect, src.isDirect() ? offHeapAllocator() : onHeapAllocator());
246 }
247
248 public static Buffer decode(
249 Buffer src, int off, int len, Base64Dialect dialect, BufferAllocator allocator) {
250 requireNonNull(src, "src");
251 requireNonNull(dialect, "dialect");
252
253
254 return new Decoder().decode(src, off, len, allocator, dialect);
255 }
256
257
258 static int decodedBufferSize(int len) {
259 return len - (len >>> 2);
260 }
261
262 private static final class Decoder implements ByteProcessor {
263 private final byte[] b4 = new byte[4];
264 private int b4Posn;
265 private byte[] decodabet;
266 private int outBuffPosn;
267 private Buffer dest;
268
269 Buffer decode(Buffer src, int off, int len, BufferAllocator allocator, Base64Dialect dialect) {
270 dest = allocator.allocate(decodedBufferSize(len));
271
272 try {
273 decodabet = decodabet(dialect);
274 src.openCursor(off, len).process(this);
275 return dest.writerOffset(outBuffPosn);
276 } catch (Throwable cause) {
277 dest.close();
278 throw cause;
279 }
280 }
281
282 @Override
283 public boolean process(byte value) {
284 if (value > 0) {
285 byte sbiDecode = decodabet[value];
286 if (sbiDecode >= WHITE_SPACE_ENC) {
287 if (sbiDecode >= EQUALS_SIGN_ENC) {
288 b4[b4Posn ++] = value;
289 if (b4Posn > 3) {
290 outBuffPosn += decode4to3(b4, dest, outBuffPosn, decodabet);
291 b4Posn = 0;
292
293
294 return value != EQUALS_SIGN;
295 }
296 }
297 return true;
298 }
299 }
300 throw new IllegalArgumentException(
301 "invalid Base64 input character: " + (short) (value & 0xFF) + " (decimal)");
302 }
303
304 private static int decode4to3(byte[] src, Buffer dest, int destOffset, byte[] decodabet) {
305 final byte src0 = src[0];
306 final byte src1 = src[1];
307 final byte src2 = src[2];
308 final int decodedValue;
309 if (src2 == EQUALS_SIGN) {
310
311 try {
312 decodedValue = (decodabet[src0] & 0xff) << 2 | (decodabet[src1] & 0xff) >>> 4;
313 } catch (IndexOutOfBoundsException ignored) {
314 throw new IllegalArgumentException("not encoded in Base64");
315 }
316 dest.setUnsignedByte(destOffset, (byte) decodedValue);
317 return 1;
318 }
319
320 final byte src3 = src[3];
321 if (src3 == EQUALS_SIGN) {
322
323 final byte b1 = decodabet[src1];
324
325 try {
326
327
328 decodedValue = ((decodabet[src0] & 0x3f) << 2 | (b1 & 0xf0) >> 4) << 8 |
329 (b1 & 0xf) << 4 | (decodabet[src2] & 0xfc) >>> 2;
330 } catch (IndexOutOfBoundsException ignored) {
331 throw new IllegalArgumentException("not encoded in Base64");
332 }
333 dest.setUnsignedShort(destOffset, decodedValue);
334 return 2;
335 }
336
337
338 try {
339 decodedValue = (decodabet[src0] & 0x3f) << 18 |
340 (decodabet[src1] & 0xff) << 12 |
341 (decodabet[src2] & 0xff) << 6 |
342 decodabet[src3] & 0xff;
343 } catch (IndexOutOfBoundsException ignored) {
344 throw new IllegalArgumentException("not encoded in Base64");
345 }
346 dest.setMedium(destOffset, decodedValue);
347 return 3;
348 }
349 }
350
351 private Base64() {
352
353 }
354 }