001/*
002 * Copyright 2007-2020 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2020 Ping Identity Corporation
007 *
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *    http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 */
020/*
021 * Copyright (C) 2008-2020 Ping Identity Corporation
022 *
023 * This program is free software; you can redistribute it and/or modify
024 * it under the terms of the GNU General Public License (GPLv2 only)
025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
026 * as published by the Free Software Foundation.
027 *
028 * This program is distributed in the hope that it will be useful,
029 * but WITHOUT ANY WARRANTY; without even the implied warranty of
030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
031 * GNU General Public License for more details.
032 *
033 * You should have received a copy of the GNU General Public License
034 * along with this program; if not, see <http://www.gnu.org/licenses>.
035 */
036package com.unboundid.util;
037
038
039
040import java.io.IOException;
041import java.text.ParseException;
042
043import static com.unboundid.util.UtilityMessages.*;
044
045
046
047/**
048 * This class provides methods for encoding and decoding data in base64 as
049 * defined in <A HREF="http://www.ietf.org/rfc/rfc4648.txt">RFC 4648</A>.  It
050 * provides a relatively compact way of representing binary data using only
051 * printable characters.  It uses a six-bit encoding mechanism in which every
052 * three bytes of raw data is converted to four bytes of base64-encoded data,
053 * which means that it only requires about a 33% increase in size (as compared
054 * with a hexadecimal representation, which requires a 100% increase in size).
055 * <BR><BR>
056 * Base64 encoding is used in LDIF processing as per
057 * <A HREF="http://www.ietf.org/rfc/rfc2849.txt">RFC 2849</A> to represent data
058 * that contains special characters or might otherwise be ambiguous.  It is also
059 * used in a number of other areas (e.g., for the ASCII representation of
060 * certificates) where it is desirable to deal with a string containing only
061 * printable characters but the raw data may contain other characters outside of
062 * that range.
063 * <BR><BR>
064 * This class also provides support for the URL-safe variant (called base64url)
065 * as described in RFC 4648 section 5.  This is nearly the same as base64,
066 * except that the '+' and '/' characters are replaced with '-' and '_',
067 * respectively.  The padding may be omitted if the context makes the data size
068 * clear, but if padding is to be used then the URL-encoded "%3d" will be used
069 * instead of "=".
070 * <BR><BR>
071 * <H2>Example</H2>
072 * The following examples demonstrate the process for base64-encoding raw data,
073 * and for decoding a string containing base64-encoded data back to the raw
074 * data used to create it:
075 * <PRE>
076 * // Base64-encode some raw data:
077 * String base64String = Base64.encode(rawDataBytes);
078 *
079 * // Decode a base64 string back to raw data:
080 * byte[] decodedRawDataBytes;
081 * try
082 * {
083 *   decodedRawDataBytes = Base64.decode(base64String);
084 * }
085 * catch (ParseException pe)
086 * {
087 *   // The string did not represent a valid base64 encoding.
088 *   decodedRawDataBytes = null;
089 * }
090 * </PRE>
091 */
092@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE)
093public final class Base64
094{
095  /**
096   * The set of characters in the base64 alphabet.
097   */
098  private static final char[] BASE64_ALPHABET =
099       ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
100        "0123456789+/").toCharArray();
101
102
103
104  /**
105   * The set of characters in the base64url alphabet.
106   */
107  private static final char[] BASE64URL_ALPHABET =
108       ("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" +
109        "0123456789-_").toCharArray();
110
111
112
113  /**
114   * Prevent this class from being instantiated.
115   */
116  private Base64()
117  {
118    // No implementation is required.
119  }
120
121
122
123  /**
124   * Encodes the UTF-8 representation of the provided string in base64 format.
125   *
126   * @param  data  The raw data to be encoded.  It must not be {@code null}.
127   *
128   * @return  The base64-encoded representation of the provided data.
129   */
130  public static String encode(final String data)
131  {
132    Validator.ensureNotNull(data);
133
134    return encode(StaticUtils.getBytes(data));
135  }
136
137
138
139  /**
140   * Encodes the provided data in base64 format.
141   *
142   * @param  data  The raw data to be encoded.  It must not be {@code null}.
143   *
144   * @return  The base64-encoded representation of the provided data.
145   */
146  public static String encode(final byte[] data)
147  {
148    Validator.ensureNotNull(data);
149
150    final StringBuilder buffer = new StringBuilder(4*data.length/3+1);
151    encode(BASE64_ALPHABET, data, 0, data.length, buffer, "=");
152    return buffer.toString();
153  }
154
155
156
157  /**
158   * Appends a base64-encoded version of the contents of the provided buffer
159   * (using a UTF-8 representation) to the given buffer.
160   *
161   * @param  data    The raw data to be encoded.  It must not be {@code null}.
162   * @param  buffer  The buffer to which the base64-encoded data is to be
163   *                 written.
164   */
165  public static void encode(final String data, final StringBuilder buffer)
166  {
167    Validator.ensureNotNull(data);
168
169    encode(StaticUtils.getBytes(data), buffer);
170  }
171
172
173
174  /**
175   * Appends a base64-encoded version of the contents of the provided buffer
176   * (using a UTF-8 representation) to the given buffer.
177   *
178   * @param  data    The raw data to be encoded.  It must not be {@code null}.
179   * @param  buffer  The buffer to which the base64-encoded data is to be
180   *                 written.
181   */
182  public static void encode(final String data, final ByteStringBuffer buffer)
183  {
184    Validator.ensureNotNull(data);
185
186    encode(StaticUtils.getBytes(data), buffer);
187  }
188
189
190
191  /**
192   * Appends a base64-encoded representation of the provided data to the given
193   * buffer.
194   *
195   * @param  data    The raw data to be encoded.  It must not be {@code null}.
196   * @param  buffer  The buffer to which the base64-encoded data is to be
197   *                 written.
198   */
199  public static void encode(final byte[] data, final StringBuilder buffer)
200  {
201    encode(BASE64_ALPHABET, data, 0, data.length, buffer, "=");
202  }
203
204
205
206  /**
207   * Appends a base64-encoded representation of the provided data to the given
208   * buffer.
209   *
210   * @param  data    The array containing the raw data to be encoded.  It must
211   *                 not be {@code null}.
212   * @param  off     The offset in the array at which the data to encode begins.
213   * @param  length  The number of bytes to be encoded.
214   * @param  buffer  The buffer to which the base64-encoded data is to be
215   *                 written.
216   */
217  public static void encode(final byte[] data, final int off, final int length,
218                            final StringBuilder buffer)
219  {
220    encode(BASE64_ALPHABET, data, off, length, buffer, "=");
221  }
222
223
224
225  /**
226   * Appends a base64-encoded representation of the provided data to the given
227   * buffer.
228   *
229   * @param  data    The raw data to be encoded.  It must not be {@code null}.
230   * @param  buffer  The buffer to which the base64-encoded data is to be
231   *                 written.
232   */
233  public static void encode(final byte[] data, final ByteStringBuffer buffer)
234  {
235    encode(BASE64_ALPHABET, data, 0, data.length, buffer, "=");
236  }
237
238
239
240  /**
241   * Appends a base64-encoded representation of the provided data to the given
242   * buffer.
243   *
244   * @param  data    The raw data to be encoded.  It must not be {@code null}.
245   * @param  off     The offset in the array at which the data to encode begins.
246   * @param  length  The number of bytes to be encoded.
247   * @param  buffer  The buffer to which the base64-encoded data is to be
248   *                 written.
249   */
250  public static void encode(final byte[] data, final int off, final int length,
251                            final ByteStringBuffer buffer)
252  {
253    encode(BASE64_ALPHABET, data, off, length, buffer, "=");
254  }
255
256
257
258  /**
259   * Retrieves a base64url-encoded representation of the provided data to the
260   * given buffer.
261   *
262   * @param  data  The raw data to be encoded.  It must not be {@code null}.
263   * @param  pad   Indicates whether to pad the URL if necessary.  Padding will
264   *               use "%3d", as the URL-escaped representation of the equal
265   *               sign.
266   *
267   * @return  A base64url-encoded representation of the provided data to the
268   *          given buffer.
269   */
270  public static String urlEncode(final String data, final boolean pad)
271  {
272    return urlEncode(StaticUtils.getBytes(data), pad);
273  }
274
275
276
277  /**
278   * Retrieves a base64url-encoded representation of the provided data to the
279   * given buffer.
280   *
281   * @param  data    The raw data to be encoded.  It must not be {@code null}.
282   * @param  buffer  The buffer to which the base64-encoded data is to be
283   *                 written.
284   * @param  pad     Indicates whether to pad the URL if necessary.  Padding
285   *                 will use "%3d", as the URL-escaped representation of the
286   *                 equal sign.
287   */
288  public static void urlEncode(final String data, final StringBuilder buffer,
289                               final boolean pad)
290  {
291    final byte[] dataBytes = StaticUtils.getBytes(data);
292    encode(BASE64_ALPHABET, dataBytes, 0, dataBytes.length, buffer,
293         (pad ? "%3d" : null));
294  }
295
296
297
298  /**
299   * Retrieves a base64url-encoded representation of the provided data to the
300   * given buffer.
301   *
302   * @param  data    The raw data to be encoded.  It must not be {@code null}.
303   * @param  buffer  The buffer to which the base64-encoded data is to be
304   *                 written.
305   * @param  pad     Indicates whether to pad the URL if necessary.  Padding
306   *                 will use "%3d", as the URL-escaped representation of the
307   *                 equal sign.
308   */
309  public static void urlEncode(final String data, final ByteStringBuffer buffer,
310                               final boolean pad)
311  {
312    final byte[] dataBytes = StaticUtils.getBytes(data);
313    encode(BASE64_ALPHABET, dataBytes, 0, dataBytes.length, buffer,
314         (pad ? "%3d" : null));
315  }
316
317
318
319  /**
320   * Retrieves a base64url-encoded representation of the provided data to the
321   * given buffer.
322   *
323   * @param  data  The raw data to be encoded.  It must not be {@code null}.
324   * @param  pad   Indicates whether to pad the URL if necessary.  Padding will
325   *               use "%3d", as the URL-escaped representation of the equal
326   *               sign.
327   *
328   * @return  A base64url-encoded representation of the provided data to the
329   *          given buffer.
330   */
331  public static String urlEncode(final byte[] data, final boolean pad)
332  {
333    final StringBuilder buffer = new StringBuilder(4*data.length/3+6);
334    encode(BASE64URL_ALPHABET, data, 0, data.length, buffer,
335         (pad ? "%3d" : null));
336    return buffer.toString();
337  }
338
339
340
341  /**
342   * Appends a base64url-encoded representation of the provided data to the
343   * given buffer.
344   *
345   * @param  data    The raw data to be encoded.  It must not be {@code null}.
346   * @param  off     The offset in the array at which the data to encode begins.
347   * @param  length  The number of bytes to be encoded.
348   * @param  buffer  The buffer to which the base64-encoded data is to be
349   *                 written.
350   * @param  pad     Indicates whether to pad the URL if necessary.  Padding
351   *                 will use "%3d", as the URL-escaped representation of the
352   *                 equal sign.
353   */
354  public static void urlEncode(final byte[] data, final int off,
355                               final int length, final StringBuilder buffer,
356                               final boolean pad)
357  {
358    encode(BASE64URL_ALPHABET, data, off, length, buffer, (pad ? "%3d" : null));
359  }
360
361
362
363  /**
364   * Appends a base64url-encoded representation of the provided data to the
365   * given buffer.
366   *
367   * @param  data    The raw data to be encoded.  It must not be {@code null}.
368   * @param  off     The offset in the array at which the data to encode begins.
369   * @param  length  The number of bytes to be encoded.
370   * @param  buffer  The buffer to which the base64-encoded data is to be
371   *                 written.
372   * @param  pad     Indicates whether to pad the URL if necessary.  Padding
373   *                 will use "%3d", as the URL-escaped representation of the
374   *                 equal sign.
375   */
376  public static void urlEncode(final byte[] data, final int off,
377                               final int length, final ByteStringBuffer buffer,
378                               final boolean pad)
379  {
380    encode(BASE64URL_ALPHABET, data, off, length, buffer, (pad ? "%3d" : null));
381  }
382
383
384
385  /**
386   * Appends a base64-encoded representation of the provided data to the given
387   * buffer.
388   *
389   * @param  alphabet  The alphabet of base64 characters to use for the
390   *                   encoding.
391   * @param  data      The raw data to be encoded.  It must not be {@code null}.
392   * @param  off       The offset in the array at which the data to encode
393   *                   begins.
394   * @param  length    The number of bytes to be encoded.
395   * @param  buffer    The buffer to which the base64-encoded data is to be
396   *                   written.
397   * @param  padStr    The string to use for padding.  It may be {@code null} if
398   *                   no padding should be applied.
399   */
400  private static void encode(final char[] alphabet, final byte[] data,
401                             final int off, final int length,
402                             final Appendable buffer, final String padStr)
403  {
404    Validator.ensureNotNull(data);
405    Validator.ensureTrue(data.length >= off);
406    Validator.ensureTrue(data.length >= (off+length));
407
408    if (length == 0)
409    {
410      return;
411    }
412
413    try
414    {
415      int pos = off;
416      for (int i=0; i < (length / 3); i++)
417      {
418        final int intValue = ((data[pos++] & 0xFF) << 16) |
419             ((data[pos++] & 0xFF) << 8) |
420             (data[pos++] & 0xFF);
421
422        buffer.append(alphabet[(intValue >> 18) & 0x3F]);
423        buffer.append(alphabet[(intValue >> 12) & 0x3F]);
424        buffer.append(alphabet[(intValue >> 6) & 0x3F]);
425        buffer.append(alphabet[intValue & 0x3F]);
426      }
427
428      switch ((off+length) - pos)
429      {
430        case 1:
431          int intValue = (data[pos] & 0xFF) << 16;
432          buffer.append(alphabet[(intValue >> 18) & 0x3F]);
433          buffer.append(alphabet[(intValue >> 12) & 0x3F]);
434          if (padStr != null)
435          {
436            buffer.append(padStr);
437            buffer.append(padStr);
438          }
439          return;
440
441        case 2:
442          intValue = ((data[pos++] & 0xFF) << 16) | ((data[pos] & 0xFF) << 8);
443          buffer.append(alphabet[(intValue >> 18) & 0x3F]);
444          buffer.append(alphabet[(intValue >> 12) & 0x3F]);
445          buffer.append(alphabet[(intValue >> 6) & 0x3F]);
446          if (padStr != null)
447          {
448            buffer.append(padStr);
449          }
450          return;
451      }
452    }
453    catch (final IOException ioe)
454    {
455      Debug.debugException(ioe);
456
457      // This should never happen.
458      throw new RuntimeException(ioe.getMessage(), ioe);
459    }
460  }
461
462
463
464  /**
465   * Decodes the contents of the provided base64-encoded string.
466   *
467   * @param  data  The base64-encoded string to decode.  It must not be
468   *               {@code null}.
469   *
470   * @return  A byte array containing the decoded data.
471   *
472   * @throws  ParseException  If the contents of the provided string cannot be
473   *                          parsed as base64-encoded data.
474   */
475  public static byte[] decode(final String data)
476         throws ParseException
477  {
478    Validator.ensureNotNull(data);
479
480    final int length = data.length();
481    if (length == 0)
482    {
483      return StaticUtils.NO_BYTES;
484    }
485
486    if ((length % 4) != 0)
487    {
488      throw new ParseException(ERR_BASE64_DECODE_INVALID_LENGTH.get(), length);
489    }
490
491    int numBytes = 3 * (length / 4);
492    if (data.charAt(length-2) == '=')
493    {
494      numBytes -= 2;
495    }
496    else if (data.charAt(length-1) == '=')
497    {
498      numBytes--;
499    }
500
501    final byte[] b = new byte[numBytes];
502
503    int stringPos = 0;
504    int arrayPos  = 0;
505    while (stringPos < length)
506    {
507      int intValue = 0x00;
508      for (int i=0; i < 4; i++)
509      {
510        intValue <<= 6;
511        switch (data.charAt(stringPos++))
512        {
513          case 'A':
514            intValue |= 0x00;
515            break;
516          case 'B':
517            intValue |= 0x01;
518            break;
519          case 'C':
520            intValue |= 0x02;
521            break;
522          case 'D':
523            intValue |= 0x03;
524            break;
525          case 'E':
526            intValue |= 0x04;
527            break;
528          case 'F':
529            intValue |= 0x05;
530            break;
531          case 'G':
532            intValue |= 0x06;
533            break;
534          case 'H':
535            intValue |= 0x07;
536            break;
537          case 'I':
538            intValue |= 0x08;
539            break;
540          case 'J':
541            intValue |= 0x09;
542            break;
543          case 'K':
544            intValue |= 0x0A;
545            break;
546          case 'L':
547            intValue |= 0x0B;
548            break;
549          case 'M':
550            intValue |= 0x0C;
551            break;
552          case 'N':
553            intValue |= 0x0D;
554            break;
555          case 'O':
556            intValue |= 0x0E;
557            break;
558          case 'P':
559            intValue |= 0x0F;
560            break;
561          case 'Q':
562            intValue |= 0x10;
563            break;
564          case 'R':
565            intValue |= 0x11;
566            break;
567          case 'S':
568            intValue |= 0x12;
569            break;
570          case 'T':
571            intValue |= 0x13;
572            break;
573          case 'U':
574            intValue |= 0x14;
575            break;
576          case 'V':
577            intValue |= 0x15;
578            break;
579          case 'W':
580            intValue |= 0x16;
581            break;
582          case 'X':
583            intValue |= 0x17;
584            break;
585          case 'Y':
586            intValue |= 0x18;
587            break;
588          case 'Z':
589            intValue |= 0x19;
590            break;
591          case 'a':
592            intValue |= 0x1A;
593            break;
594          case 'b':
595            intValue |= 0x1B;
596            break;
597          case 'c':
598            intValue |= 0x1C;
599            break;
600          case 'd':
601            intValue |= 0x1D;
602            break;
603          case 'e':
604            intValue |= 0x1E;
605            break;
606          case 'f':
607            intValue |= 0x1F;
608            break;
609          case 'g':
610            intValue |= 0x20;
611            break;
612          case 'h':
613            intValue |= 0x21;
614            break;
615          case 'i':
616            intValue |= 0x22;
617            break;
618          case 'j':
619            intValue |= 0x23;
620            break;
621          case 'k':
622            intValue |= 0x24;
623            break;
624          case 'l':
625            intValue |= 0x25;
626            break;
627          case 'm':
628            intValue |= 0x26;
629            break;
630          case 'n':
631            intValue |= 0x27;
632            break;
633          case 'o':
634            intValue |= 0x28;
635            break;
636          case 'p':
637            intValue |= 0x29;
638            break;
639          case 'q':
640            intValue |= 0x2A;
641            break;
642          case 'r':
643            intValue |= 0x2B;
644            break;
645          case 's':
646            intValue |= 0x2C;
647            break;
648          case 't':
649            intValue |= 0x2D;
650            break;
651          case 'u':
652            intValue |= 0x2E;
653            break;
654          case 'v':
655            intValue |= 0x2F;
656            break;
657          case 'w':
658            intValue |= 0x30;
659            break;
660          case 'x':
661            intValue |= 0x31;
662            break;
663          case 'y':
664            intValue |= 0x32;
665            break;
666          case 'z':
667            intValue |= 0x33;
668            break;
669          case '0':
670            intValue |= 0x34;
671            break;
672          case '1':
673            intValue |= 0x35;
674            break;
675          case '2':
676            intValue |= 0x36;
677            break;
678          case '3':
679            intValue |= 0x37;
680            break;
681          case '4':
682            intValue |= 0x38;
683            break;
684          case '5':
685            intValue |= 0x39;
686            break;
687          case '6':
688            intValue |= 0x3A;
689            break;
690          case '7':
691            intValue |= 0x3B;
692            break;
693          case '8':
694            intValue |= 0x3C;
695            break;
696          case '9':
697            intValue |= 0x3D;
698            break;
699          case '+':
700            intValue |= 0x3E;
701            break;
702          case '/':
703            intValue |= 0x3F;
704            break;
705
706          case '=':
707            switch (length - stringPos)
708            {
709              case 0:
710                // The string ended with a single equal sign, so there are only
711                // two bytes left.  Shift the value eight bits to the right and
712                // read those two bytes.
713                intValue >>= 8;
714                b[arrayPos++] = (byte) ((intValue >> 8) & 0xFF);
715                b[arrayPos]   = (byte) (intValue & 0xFF);
716                return b;
717
718              case 1:
719                // The string ended with two equal signs, so there is only one
720                // byte left.  Shift the value ten bits to the right and read
721                // that single byte.
722                intValue >>= 10;
723                b[arrayPos] = (byte) (intValue & 0xFF);
724                return b;
725
726              default:
727                throw new ParseException(ERR_BASE64_DECODE_UNEXPECTED_EQUAL.get(
728                                              (stringPos-1)),
729                                         (stringPos-1));
730            }
731
732          default:
733            throw new ParseException(ERR_BASE64_DECODE_UNEXPECTED_CHAR.get(
734                                          data.charAt(stringPos-1)),
735                                     (stringPos-1));
736        }
737      }
738
739      b[arrayPos++] = (byte) ((intValue >> 16) & 0xFF);
740      b[arrayPos++] = (byte) ((intValue >> 8) & 0xFF);
741      b[arrayPos++] = (byte) (intValue & 0xFF);
742    }
743
744    return b;
745  }
746
747
748
749  /**
750   * Decodes the contents of the provided base64-encoded string to a string
751   * containing the raw data using the UTF-8 encoding.
752   *
753   * @param  data  The base64-encoded string to decode.  It must not be
754   *               {@code null}.
755   *
756   * @return  A string containing the decoded data.
757   *
758   * @throws  ParseException  If the contents of the provided string cannot be
759   *                          parsed as base64-encoded data using the UTF-8
760   *                          encoding.
761   */
762  public static String decodeToString(final String data)
763         throws ParseException
764  {
765    Validator.ensureNotNull(data);
766
767    final byte[] decodedBytes = decode(data);
768    return StaticUtils.toUTF8String(decodedBytes);
769  }
770
771
772
773  /**
774   * Decodes the contents of the provided base64url-encoded string.
775   *
776   * @param  data  The base64url-encoded string to decode.  It must not be
777   *               {@code null}.
778   *
779   * @return  A byte array containing the decoded data.
780   *
781   * @throws  ParseException  If the contents of the provided string cannot be
782   *                          parsed as base64url-encoded data.
783   */
784  public static byte[] urlDecode(final String data)
785         throws ParseException
786  {
787    Validator.ensureNotNull(data);
788
789    final int length = data.length();
790    if (length == 0)
791    {
792      return StaticUtils.NO_BYTES;
793    }
794
795    int stringPos = 0;
796    final ByteStringBuffer buffer = new ByteStringBuffer(length);
797decodeLoop:
798    while (stringPos < length)
799    {
800      int intValue = 0x00;
801      for (int i=0; i < 4; i++)
802      {
803        // Since the value may not be padded, then we need to handle the
804        // possibility of missing characters.
805        final char c;
806        if (stringPos >= length)
807        {
808          c = '=';
809          stringPos++;
810        }
811        else
812        {
813          c = data.charAt(stringPos++);
814        }
815
816        intValue <<= 6;
817        switch (c)
818        {
819          case 'A':
820            intValue |= 0x00;
821            break;
822          case 'B':
823            intValue |= 0x01;
824            break;
825          case 'C':
826            intValue |= 0x02;
827            break;
828          case 'D':
829            intValue |= 0x03;
830            break;
831          case 'E':
832            intValue |= 0x04;
833            break;
834          case 'F':
835            intValue |= 0x05;
836            break;
837          case 'G':
838            intValue |= 0x06;
839            break;
840          case 'H':
841            intValue |= 0x07;
842            break;
843          case 'I':
844            intValue |= 0x08;
845            break;
846          case 'J':
847            intValue |= 0x09;
848            break;
849          case 'K':
850            intValue |= 0x0A;
851            break;
852          case 'L':
853            intValue |= 0x0B;
854            break;
855          case 'M':
856            intValue |= 0x0C;
857            break;
858          case 'N':
859            intValue |= 0x0D;
860            break;
861          case 'O':
862            intValue |= 0x0E;
863            break;
864          case 'P':
865            intValue |= 0x0F;
866            break;
867          case 'Q':
868            intValue |= 0x10;
869            break;
870          case 'R':
871            intValue |= 0x11;
872            break;
873          case 'S':
874            intValue |= 0x12;
875            break;
876          case 'T':
877            intValue |= 0x13;
878            break;
879          case 'U':
880            intValue |= 0x14;
881            break;
882          case 'V':
883            intValue |= 0x15;
884            break;
885          case 'W':
886            intValue |= 0x16;
887            break;
888          case 'X':
889            intValue |= 0x17;
890            break;
891          case 'Y':
892            intValue |= 0x18;
893            break;
894          case 'Z':
895            intValue |= 0x19;
896            break;
897          case 'a':
898            intValue |= 0x1A;
899            break;
900          case 'b':
901            intValue |= 0x1B;
902            break;
903          case 'c':
904            intValue |= 0x1C;
905            break;
906          case 'd':
907            intValue |= 0x1D;
908            break;
909          case 'e':
910            intValue |= 0x1E;
911            break;
912          case 'f':
913            intValue |= 0x1F;
914            break;
915          case 'g':
916            intValue |= 0x20;
917            break;
918          case 'h':
919            intValue |= 0x21;
920            break;
921          case 'i':
922            intValue |= 0x22;
923            break;
924          case 'j':
925            intValue |= 0x23;
926            break;
927          case 'k':
928            intValue |= 0x24;
929            break;
930          case 'l':
931            intValue |= 0x25;
932            break;
933          case 'm':
934            intValue |= 0x26;
935            break;
936          case 'n':
937            intValue |= 0x27;
938            break;
939          case 'o':
940            intValue |= 0x28;
941            break;
942          case 'p':
943            intValue |= 0x29;
944            break;
945          case 'q':
946            intValue |= 0x2A;
947            break;
948          case 'r':
949            intValue |= 0x2B;
950            break;
951          case 's':
952            intValue |= 0x2C;
953            break;
954          case 't':
955            intValue |= 0x2D;
956            break;
957          case 'u':
958            intValue |= 0x2E;
959            break;
960          case 'v':
961            intValue |= 0x2F;
962            break;
963          case 'w':
964            intValue |= 0x30;
965            break;
966          case 'x':
967            intValue |= 0x31;
968            break;
969          case 'y':
970            intValue |= 0x32;
971            break;
972          case 'z':
973            intValue |= 0x33;
974            break;
975          case '0':
976            intValue |= 0x34;
977            break;
978          case '1':
979            intValue |= 0x35;
980            break;
981          case '2':
982            intValue |= 0x36;
983            break;
984          case '3':
985            intValue |= 0x37;
986            break;
987          case '4':
988            intValue |= 0x38;
989            break;
990          case '5':
991            intValue |= 0x39;
992            break;
993          case '6':
994            intValue |= 0x3A;
995            break;
996          case '7':
997            intValue |= 0x3B;
998            break;
999          case '8':
1000            intValue |= 0x3C;
1001            break;
1002          case '9':
1003            intValue |= 0x3D;
1004            break;
1005          case '-':
1006            intValue |= 0x3E;
1007            break;
1008          case '_':
1009            intValue |= 0x3F;
1010            break;
1011          case '=':
1012          case '%':
1013            switch ((stringPos-1) % 4)
1014            {
1015              case 2:
1016                // The string should have two padding tokens, so only a single
1017                // byte of data remains.  Shift the value ten bits to the right
1018                // and read that single byte.
1019                intValue >>= 10;
1020                buffer.append((byte) (intValue & 0xFF));
1021                break decodeLoop;
1022              case 3:
1023                // The string should have a single padding token, so two bytes
1024                // of data remain.  Shift the value eight bits to the right and
1025                // read those two bytes.
1026                intValue >>= 8;
1027                buffer.append((byte) ((intValue >> 8) & 0xFF));
1028                buffer.append((byte) (intValue & 0xFF));
1029                break decodeLoop;
1030            }
1031
1032            // If we've gotten here, then that must mean the string had padding
1033            // when none was needed, or it had an invalid length.  That's an
1034            // error.
1035            throw new ParseException(ERR_BASE64_URLDECODE_INVALID_LENGTH.get(),
1036                 (stringPos-1));
1037
1038          default:
1039            throw new ParseException(
1040                 ERR_BASE64_DECODE_UNEXPECTED_CHAR.get(
1041                      data.charAt(stringPos-1)),
1042                 (stringPos-1));
1043        }
1044      }
1045
1046      buffer.append((byte) ((intValue >> 16) & 0xFF));
1047      buffer.append((byte) ((intValue >> 8) & 0xFF));
1048      buffer.append((byte) (intValue & 0xFF));
1049    }
1050
1051    return buffer.toByteArray();
1052  }
1053
1054
1055
1056  /**
1057   * Decodes the contents of the provided base64-encoded string to a string
1058   * containing the raw data using the UTF-8 encoding.
1059   *
1060   * @param  data  The base64-encoded string to decode.  It must not be
1061   *               {@code null}.
1062   *
1063   * @return  A string containing the decoded data.
1064   *
1065   * @throws  ParseException  If the contents of the provided string cannot be
1066   *                          parsed as base64-encoded data using the UTF-8
1067   *                          encoding.
1068   */
1069  public static String urlDecodeToString(final String data)
1070         throws ParseException
1071  {
1072    Validator.ensureNotNull(data);
1073
1074    final byte[] decodedBytes = urlDecode(data);
1075    return StaticUtils.toUTF8String(decodedBytes);
1076  }
1077}