View Javadoc
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 }