1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.codec.binary;
19
20 import java.nio.ByteBuffer;
21 import java.nio.charset.Charset;
22 import java.nio.charset.StandardCharsets;
23
24 import org.apache.commons.codec.BinaryDecoder;
25 import org.apache.commons.codec.BinaryEncoder;
26 import org.apache.commons.codec.CharEncoding;
27 import org.apache.commons.codec.DecoderException;
28 import org.apache.commons.codec.EncoderException;
29
30 /**
31 * Converts hexadecimal Strings. The Charset used for certain operation can be set, the default is set in
32 * {@link #DEFAULT_CHARSET_NAME}
33 *
34 * <p>
35 * Decoding accepts only the ASCII hexadecimal characters {@code 0-9}, {@code A-F}, and {@code a-f}. Non-ASCII Unicode digits and fullwidth letters
36 * are rejected.
37 * </p>
38 *
39 * <p>
40 * This class is thread-safe.
41 * </p>
42 *
43 * @since 1.1
44 */
45 public class Hex implements BinaryEncoder, BinaryDecoder {
46
47 /**
48 * Default charset is {@link StandardCharsets#UTF_8}.
49 *
50 * @since 1.7
51 */
52 public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
53
54 /**
55 * Default charset name is {@link CharEncoding#UTF_8}.
56 *
57 * @since 1.4
58 */
59 public static final String DEFAULT_CHARSET_NAME = CharEncoding.UTF_8;
60
61 /**
62 * Used to build output as hex.
63 */
64 private static final char[] DIGITS_LOWER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
65
66 /**
67 * Used to build output as hex.
68 */
69 private static final char[] DIGITS_UPPER = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
70
71 /**
72 * Converts an array of characters representing hexadecimal values into an array of bytes of those same values. The
73 * returned array will be half the length of the passed array, as it takes two characters to represent any given
74 * byte. An exception is thrown if the passed char array has an odd number of elements.
75 *
76 * @param data An array of characters containing hexadecimal digits.
77 * @return A byte array containing binary data decoded from the supplied char array.
78 * @throws DecoderException Thrown if an odd number of characters or illegal characters are supplied.
79 */
80 public static byte[] decodeHex(final char[] data) throws DecoderException {
81 final byte[] out = new byte[data.length >> 1];
82 decodeHex(data, out, 0);
83 return out;
84 }
85
86 /**
87 * Converts an array of characters representing hexadecimal values into an array of bytes of those same values. The
88 * returned array will be half the length of the passed array, as it takes two characters to represent any given
89 * byte. An exception is thrown if the passed char array has an odd number of elements.
90 *
91 * @param data An array of characters containing hexadecimal digits.
92 * @param out A byte array to contain the binary data decoded from the supplied char array.
93 * @param outOffset The position within {@code out} to start writing the decoded bytes.
94 * @return The number of bytes written to {@code out}.
95 * @throws DecoderException Thrown if an odd number of characters or illegal characters are supplied.
96 * @since 1.15
97 */
98 public static int decodeHex(final char[] data, final byte[] out, final int outOffset) throws DecoderException {
99 final int len = data.length;
100 if ((len & 1) != 0) {
101 throw new DecoderException("Odd number of characters %,d.", len);
102 }
103 final int outLen = len >> 1;
104 if (out.length - outOffset < outLen) {
105 throw new DecoderException("Output array is not large enough to accommodate decoded data.");
106 }
107 // two characters form the hex value.
108 for (int i = outOffset, j = 0; j < len; i++) {
109 int f = toDigit(data[j], j) << 4;
110 j++;
111 f |= toDigit(data[j], j);
112 j++;
113 out[i] = (byte) (f & 0xFF);
114 }
115 return outLen;
116 }
117
118 /**
119 * Converts a String representing hexadecimal values into an array of bytes of those same values. The returned array
120 * will be half the length of the passed String, as it takes two characters to represent any given byte. An
121 * exception is thrown if the passed String has an odd number of elements.
122 *
123 * @param data A String containing hexadecimal digits.
124 * @return A byte array containing binary data decoded from the supplied char array.
125 * @throws DecoderException Thrown if an odd number of characters or illegal characters are supplied.
126 * @since 1.11
127 */
128 public static byte[] decodeHex(final String data) throws DecoderException {
129 return decodeHex(data.toCharArray());
130 }
131
132 /**
133 * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
134 * The returned array will be double the length of the passed array, as it takes two characters to represent any
135 * given byte.
136 *
137 * @param data A byte[] to convert to hexadecimal characters.
138 * @return A char[] containing lower-case hexadecimal characters.
139 */
140 public static char[] encodeHex(final byte[] data) {
141 return encodeHex(data, true);
142 }
143
144 /**
145 * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
146 * The returned array will be double the length of the passed array, as it takes two characters to represent any
147 * given byte.
148 *
149 * @param data A byte[] to convert to Hex characters.
150 * @param toLowerCase {@code true} converts to lowercase, {@code false} to uppercase.
151 * @return A char[] containing hexadecimal characters in the selected case.
152 * @since 1.4
153 */
154 public static char[] encodeHex(final byte[] data, final boolean toLowerCase) {
155 return encodeHex(data, toAlphabet(toLowerCase));
156 }
157
158 /**
159 * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
160 * The returned array will be double the length of the passed array, as it takes two characters to represent any
161 * given byte.
162 *
163 * @param data A byte[] to convert to hexadecimal characters.
164 * @param toDigits The output alphabet (must contain at least 16 chars).
165 * @return A char[] containing the appropriate characters from the alphabet For best results, this should be either
166 * upper- or lower-case hex.
167 * @since 1.4
168 */
169 protected static char[] encodeHex(final byte[] data, final char[] toDigits) {
170 final int dataLength = data.length;
171 return encodeHex(data, 0, dataLength, toDigits, new char[dataLength << 1], 0);
172 }
173
174 /**
175 * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
176 *
177 * @param data A byte[] to convert to hexadecimal characters.
178 * @param dataOffset The position in {@code data} to start encoding from.
179 * @param dataLen The number of bytes from {@code dataOffset} to encode.
180 * @param toLowerCase {@code true} converts to lowercase, {@code false} to uppercase.
181 * @return A char[] containing the appropriate characters from the alphabet For best results, this should be either
182 * upper- or lower-case hex.
183 * @since 1.15
184 */
185 public static char[] encodeHex(final byte[] data, final int dataOffset, final int dataLen, final boolean toLowerCase) {
186 return encodeHex(data, dataOffset, dataLen, toAlphabet(toLowerCase), new char[dataLen << 1], 0);
187 }
188
189 /**
190 * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
191 *
192 * @param data A byte[] to convert to hexadecimal characters.
193 * @param dataOffset The position in {@code data} to start encoding from.
194 * @param dataLen The number of bytes from {@code dataOffset} to encode.
195 * @param toLowerCase {@code true} converts to lowercase, {@code false} to uppercase.
196 * @param out A char[] which will hold the resultant appropriate characters from the alphabet.
197 * @param outOffset The position within {@code out} at which to start writing the encoded characters.
198 * @since 1.15
199 */
200 public static void encodeHex(final byte[] data, final int dataOffset, final int dataLen, final boolean toLowerCase, final char[] out, final int outOffset) {
201 encodeHex(data, dataOffset, dataLen, toAlphabet(toLowerCase), out, outOffset);
202 }
203
204 /**
205 * Converts an array of bytes into an array of characters representing the hexadecimal values of each byte in order.
206 *
207 * @param data A byte[] to convert to hexadecimal characters.
208 * @param dataOffset The position in {@code data} to start encoding from.
209 * @param dataLen The number of bytes from {@code dataOffset} to encode.
210 * @param toDigits The output alphabet (must contain at least 16 chars).
211 * @param out A char[] which will hold the resultant appropriate characters from the alphabet.
212 * @param outOffset The position within {@code out} at which to start writing the encoded characters.
213 * @return The given {@code out}.
214 */
215 private static char[] encodeHex(final byte[] data, final int dataOffset, final int dataLen, final char[] toDigits, final char[] out, final int outOffset) {
216 // two characters form the hex value.
217 for (int i = dataOffset, j = outOffset; i < dataOffset + dataLen; i++) {
218 out[j++] = toDigits[(0xF0 & data[i]) >>> 4];
219 out[j++] = toDigits[0x0F & data[i]];
220 }
221 return out;
222 }
223
224 /**
225 * Converts a byte buffer into an array of characters representing the hexadecimal values of each byte in order. The
226 * returned array will be double the length of the passed array, as it takes two characters to represent any given
227 * byte.
228 *
229 * <p>
230 * All bytes identified by {@link ByteBuffer#remaining()} will be used; after this method
231 * the value {@link ByteBuffer#remaining() remaining()} will be zero.
232 * </p>
233 *
234 * @param data A byte buffer to convert to hexadecimal characters.
235 * @return A char[] containing lower-case hexadecimal characters.
236 * @since 1.11
237 */
238 public static char[] encodeHex(final ByteBuffer data) {
239 return encodeHex(data, true);
240 }
241
242 /**
243 * Converts a byte buffer into an array of characters representing the hexadecimal values of each byte in order. The
244 * returned array will be double the length of the passed array, as it takes two characters to represent any given
245 * byte.
246 *
247 * <p>
248 * All bytes identified by {@link ByteBuffer#remaining()} will be used; after this method
249 * the value {@link ByteBuffer#remaining() remaining()} will be zero.
250 * </p>
251 *
252 * @param data A byte buffer to convert to hexadecimal characters.
253 * @param toLowerCase {@code true} converts to lowercase, {@code false} to uppercase.
254 * @return A char[] containing hexadecimal characters in the selected case.
255 * @since 1.11
256 */
257 public static char[] encodeHex(final ByteBuffer data, final boolean toLowerCase) {
258 return encodeHex(data, toAlphabet(toLowerCase));
259 }
260
261 /**
262 * Converts a byte buffer into an array of characters representing the hexadecimal values of each byte in order. The
263 * returned array will be double the length of the passed array, as it takes two characters to represent any given
264 * byte.
265 *
266 * <p>
267 * All bytes identified by {@link ByteBuffer#remaining()} will be used; after this method
268 * the value {@link ByteBuffer#remaining() remaining()} will be zero.
269 * </p>
270 *
271 * @param byteBuffer A byte buffer to convert to hexadecimal characters.
272 * @param toDigits The output alphabet (must be at least 16 characters).
273 * @return A char[] containing the appropriate characters from the alphabet For best results, this should be either
274 * upper- or lower-case hex.
275 * @since 1.11
276 */
277 protected static char[] encodeHex(final ByteBuffer byteBuffer, final char[] toDigits) {
278 return encodeHex(toByteArray(byteBuffer), toDigits);
279 }
280
281 /**
282 * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned
283 * String will be double the length of the passed array, as it takes two characters to represent any given byte.
284 *
285 * @param data A byte[] to convert to hexadecimal characters.
286 * @return A String containing lower-case hexadecimal characters.
287 * @since 1.4
288 */
289 public static String encodeHexString(final byte[] data) {
290 return new String(encodeHex(data));
291 }
292
293 /**
294 * Converts an array of bytes into a String representing the hexadecimal values of each byte in order. The returned
295 * String will be double the length of the passed array, as it takes two characters to represent any given byte.
296 *
297 * @param data A byte[] to convert to hexadecimal characters.
298 * @param toLowerCase {@code true} converts to lowercase, {@code false} to uppercase.
299 * @return A String containing lower-case hexadecimal characters.
300 * @since 1.11
301 */
302 public static String encodeHexString(final byte[] data, final boolean toLowerCase) {
303 return new String(encodeHex(data, toLowerCase));
304 }
305
306 /**
307 * Converts a byte buffer into a String representing the hexadecimal values of each byte in order. The returned
308 * String will be double the length of the passed array, as it takes two characters to represent any given byte.
309 *
310 * <p>
311 * All bytes identified by {@link ByteBuffer#remaining()} will be used; after this method
312 * the value {@link ByteBuffer#remaining() remaining()} will be zero.
313 * </p>
314 *
315 * @param data A byte buffer to convert to hexadecimal characters.
316 * @return A String containing lower-case hexadecimal characters.
317 * @since 1.11
318 */
319 public static String encodeHexString(final ByteBuffer data) {
320 return new String(encodeHex(data));
321 }
322
323 /**
324 * Converts a byte buffer into a String representing the hexadecimal values of each byte in order. The returned
325 * String will be double the length of the passed array, as it takes two characters to represent any given byte.
326 *
327 * <p>
328 * All bytes identified by {@link ByteBuffer#remaining()} will be used; after this method
329 * the value {@link ByteBuffer#remaining() remaining()} will be zero.
330 * </p>
331 *
332 * @param data A byte buffer to convert to hexadecimal characters.
333 * @param toLowerCase {@code true} converts to lowercase, {@code false} to uppercase.
334 * @return A String containing lower-case hexadecimal characters.
335 * @since 1.11
336 */
337 public static String encodeHexString(final ByteBuffer data, final boolean toLowerCase) {
338 return new String(encodeHex(data, toLowerCase));
339 }
340
341 /**
342 * Converts a boolean to an alphabet.
343 *
344 * @param toLowerCase true for lowercase, false for uppercase.
345 * @return An alphabet.
346 */
347 private static char[] toAlphabet(final boolean toLowerCase) {
348 return toLowerCase ? DIGITS_LOWER : DIGITS_UPPER;
349 }
350
351 /**
352 * Convert the byte buffer to a byte array. All bytes identified by
353 * {@link ByteBuffer#remaining()} will be used.
354 *
355 * @param byteBuffer The byte buffer.
356 * @return The byte[].
357 */
358 private static byte[] toByteArray(final ByteBuffer byteBuffer) {
359 final int remaining = byteBuffer.remaining();
360 // Use the underlying buffer if possible
361 if (byteBuffer.hasArray()) {
362 final byte[] byteArray = byteBuffer.array();
363 if (remaining == byteArray.length) {
364 byteBuffer.position(remaining);
365 return byteArray;
366 }
367 }
368 // Copy the bytes
369 final byte[] byteArray = new byte[remaining];
370 byteBuffer.get(byteArray);
371 return byteArray;
372 }
373
374 /**
375 * Converts a hexadecimal character to an integer.
376 *
377 * <p>
378 * Only the ASCII characters {@code '0'} to {@code '9'}, {@code 'A'} to {@code 'F'} and {@code 'a'} to {@code 'f'} are accepted. Other Unicode digits,
379 * such as fullwidth or Arabic-Indic digits, are rejected even though {@link Character#digit(char, int)} would accept them. These alternate spellings
380 * can bypass textual blocklists or replay caches that compare hexadecimal strings without decoding or normalizing them first.
381 * </p>
382 *
383 * @param ch A character to convert to an integer digit.
384 * @param index The index of the character in the source.
385 * @return An integer.
386 * @throws DecoderException Thrown if ch is an illegal hexadecimal character.
387 */
388 protected static int toDigit(final char ch, final int index) throws DecoderException {
389 final int digit;
390 if (ch >= '0' && ch <= '9') {
391 digit = ch - '0';
392 } else if (ch >= 'A' && ch <= 'F') {
393 digit = ch - 'A' + 10;
394 } else if (ch >= 'a' && ch <= 'f') {
395 digit = ch - 'a' + 10;
396 } else {
397 digit = -1;
398 }
399 if (digit == -1) {
400 throw new DecoderException("Illegal hexadecimal character 0x%02X at index %,d.", ch & 0xFFFF, index);
401 }
402 return digit;
403 }
404
405 private final Charset charset;
406
407 /**
408 * Creates a new codec with the default charset name {@link #DEFAULT_CHARSET}
409 */
410 public Hex() {
411 // use default encoding
412 this.charset = DEFAULT_CHARSET;
413 }
414
415 /**
416 * Creates a new codec with the given Charset.
417 *
418 * @param charset The charset.
419 * @since 1.7
420 */
421 public Hex(final Charset charset) {
422 this.charset = charset;
423 }
424
425 /**
426 * Creates a new codec with the given charset name.
427 *
428 * @param charsetName The charset name.
429 * @throws java.nio.charset.UnsupportedCharsetException Thrown if the named charset is unavailable.
430 * @since 1.4
431 * @since 1.7 throws UnsupportedCharsetException if the named charset is unavailable
432 */
433 public Hex(final String charsetName) {
434 this(Charset.forName(charsetName));
435 }
436
437 /**
438 * Converts an array of character bytes representing hexadecimal values into an array of bytes of those same values.
439 * The returned array will be half the length of the passed array, as it takes two characters to represent any given
440 * byte. An exception is thrown if the passed char array has an odd number of elements.
441 *
442 * @param array An array of character bytes containing hexadecimal digits.
443 * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
444 * @throws DecoderException Thrown if an odd number of characters is supplied to this function.
445 * @see #decodeHex(char[])
446 */
447 @Override
448 public byte[] decode(final byte[] array) throws DecoderException {
449 return decodeHex(new String(array, getCharset()).toCharArray());
450 }
451
452 /**
453 * Converts a buffer of character bytes representing hexadecimal values into an array of bytes of those same values.
454 * The returned array will be half the length of the passed array, as it takes two characters to represent any given
455 * byte. An exception is thrown if the passed char array has an odd number of elements.
456 *
457 * <p>
458 * All bytes identified by {@link ByteBuffer#remaining()} will be used; after this method
459 * the value {@link ByteBuffer#remaining() remaining()} will be zero.
460 * </p>
461 *
462 * @param buffer An array of character bytes containing hexadecimal digits.
463 * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
464 * @throws DecoderException Thrown if an odd number of characters is supplied to this function.
465 * @see #decodeHex(char[])
466 * @since 1.11
467 */
468 public byte[] decode(final ByteBuffer buffer) throws DecoderException {
469 return decodeHex(new String(toByteArray(buffer), getCharset()).toCharArray());
470 }
471
472 /**
473 * Converts a String or an array of character bytes representing hexadecimal values into an array of bytes of those
474 * same values. The returned array will be half the length of the passed String or array, as it takes two characters
475 * to represent any given byte. An exception is thrown if the passed char array has an odd number of elements.
476 *
477 * @param object A String, ByteBuffer, byte[], or an array of character bytes containing hexadecimal digits.
478 * @return A byte array containing binary data decoded from the supplied byte array (representing characters).
479 * @throws DecoderException Thrown if an odd number of characters is supplied to this function or the object is not
480 * a String or char[].
481 * @see #decodeHex(char[])
482 */
483 @Override
484 public Object decode(final Object object) throws DecoderException {
485 if (object instanceof String) {
486 return decode(((String) object).toCharArray());
487 }
488 if (object instanceof byte[]) {
489 return decode((byte[]) object);
490 }
491 if (object instanceof ByteBuffer) {
492 return decode((ByteBuffer) object);
493 }
494 try {
495 return decodeHex((char[]) object);
496 } catch (final ClassCastException e) {
497 throw new DecoderException(e.getMessage(), e);
498 }
499 }
500
501 /**
502 * Converts an array of bytes into an array of bytes for the characters representing the hexadecimal values of each
503 * byte in order. The returned array will be double the length of the passed array, as it takes two characters to
504 * represent any given byte.
505 * <p>
506 * The conversion from hexadecimal characters to the returned bytes is performed with the charset named by
507 * {@link #getCharset()}.
508 * </p>
509 *
510 * @param array A byte[] to convert to hexadecimal characters.
511 * @return A byte[] containing the bytes of the lower-case hexadecimal characters.
512 * @since 1.7 No longer throws IllegalStateException if the charsetName is invalid.
513 * @see #encodeHex(byte[])
514 */
515 @Override
516 public byte[] encode(final byte[] array) {
517 return encodeHexString(array).getBytes(getCharset());
518 }
519
520 /**
521 * Converts byte buffer into an array of bytes for the characters representing the hexadecimal values of each byte
522 * in order. The returned array will be double the length of the passed array, as it takes two characters to
523 * represent any given byte.
524 *
525 * <p>
526 * The conversion from hexadecimal characters to the returned bytes is performed with the charset named by
527 * {@link #getCharset()}.
528 * </p>
529 *
530 * <p>
531 * All bytes identified by {@link ByteBuffer#remaining()} will be used; after this method
532 * the value {@link ByteBuffer#remaining() remaining()} will be zero.
533 * </p>
534 *
535 * @param array A byte buffer to convert to hexadecimal characters.
536 * @return A byte[] containing the bytes of the lower-case hexadecimal characters.
537 * @see #encodeHex(byte[])
538 * @since 1.11
539 */
540 public byte[] encode(final ByteBuffer array) {
541 return encodeHexString(array).getBytes(getCharset());
542 }
543
544 /**
545 * Converts a String or an array of bytes into an array of characters representing the hexadecimal values of each
546 * byte in order. The returned array will be double the length of the passed String or array, as it takes two
547 * characters to represent any given byte.
548 * <p>
549 * The conversion from hexadecimal characters to bytes to be encoded to performed with the charset named by
550 * {@link #getCharset()}.
551 * </p>
552 *
553 * @param object A String, ByteBuffer, or byte[] to convert to hexadecimal characters.
554 * @return A char[] containing lower-case hexadecimal characters.
555 * @throws EncoderException Thrown if the given object is not a String or byte[].
556 * @see #encodeHex(byte[])
557 */
558 @Override
559 public Object encode(final Object object) throws EncoderException {
560 final byte[] byteArray;
561 if (object instanceof String) {
562 byteArray = ((String) object).getBytes(getCharset());
563 } else if (object instanceof ByteBuffer) {
564 byteArray = toByteArray((ByteBuffer) object);
565 } else {
566 try {
567 byteArray = (byte[]) object;
568 } catch (final ClassCastException e) {
569 throw new EncoderException(e.getMessage(), e);
570 }
571 }
572 return encodeHex(byteArray);
573 }
574
575 /**
576 * Gets the charset.
577 *
578 * @return The charset.
579 * @since 1.7
580 */
581 public Charset getCharset() {
582 return this.charset;
583 }
584
585 /**
586 * Gets the charset name.
587 *
588 * @return The charset name.
589 * @since 1.4
590 */
591 public String getCharsetName() {
592 return this.charset.name();
593 }
594
595 /**
596 * Returns a string representation of the object, which includes the charset name.
597 *
598 * @return A string representation of the object.
599 */
600 @Override
601 public String toString() {
602 return super.toString() + "[charsetName=" + this.charset + "]";
603 }
604 }