001/*
002 * Copyright 2007-2024 Ping Identity Corporation
003 * All Rights Reserved.
004 */
005/*
006 * Copyright 2007-2024 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) 2007-2024 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  @NotNull 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  @NotNull 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  @NotNull()
131  public static String encode(@NotNull final String data)
132  {
133    Validator.ensureNotNull(data);
134
135    return encode(StaticUtils.getBytes(data));
136  }
137
138
139
140  /**
141   * Encodes the provided data in base64 format.
142   *
143   * @param  data  The raw data to be encoded.  It must not be {@code null}.
144   *
145   * @return  The base64-encoded representation of the provided data.
146   */
147  @NotNull()
148  public static String encode(@NotNull final byte[] data)
149  {
150    Validator.ensureNotNull(data);
151
152    final StringBuilder buffer = new StringBuilder(4*data.length/3+1);
153    encode(BASE64_ALPHABET, data, 0, data.length, buffer, "=");
154    return buffer.toString();
155  }
156
157
158
159  /**
160   * Appends a base64-encoded version of the contents of the provided buffer
161   * (using a UTF-8 representation) to the given buffer.
162   *
163   * @param  data    The raw data to be encoded.  It must not be {@code null}.
164   * @param  buffer  The buffer to which the base64-encoded data is to be
165   *                 written.
166   */
167  public static void encode(@NotNull final String data,
168                            @NotNull final StringBuilder buffer)
169  {
170    Validator.ensureNotNull(data);
171
172    encode(StaticUtils.getBytes(data), buffer);
173  }
174
175
176
177  /**
178   * Appends a base64-encoded version of the contents of the provided buffer
179   * (using a UTF-8 representation) to the given buffer.
180   *
181   * @param  data    The raw data to be encoded.  It must not be {@code null}.
182   * @param  buffer  The buffer to which the base64-encoded data is to be
183   *                 written.
184   */
185  public static void encode(@NotNull final String data,
186                            @NotNull final ByteStringBuffer buffer)
187  {
188    Validator.ensureNotNull(data);
189
190    encode(StaticUtils.getBytes(data), buffer);
191  }
192
193
194
195  /**
196   * Appends a base64-encoded representation of the provided data to the given
197   * buffer.
198   *
199   * @param  data    The raw data to be encoded.  It must not be {@code null}.
200   * @param  buffer  The buffer to which the base64-encoded data is to be
201   *                 written.
202   */
203  public static void encode(@NotNull final byte[] data,
204                            @NotNull final StringBuilder buffer)
205  {
206    encode(BASE64_ALPHABET, data, 0, data.length, buffer, "=");
207  }
208
209
210
211  /**
212   * Appends a base64-encoded representation of the provided data to the given
213   * buffer.
214   *
215   * @param  data    The array containing the raw data to be encoded.  It must
216   *                 not be {@code null}.
217   * @param  off     The offset in the array at which the data to encode begins.
218   * @param  length  The number of bytes to be encoded.
219   * @param  buffer  The buffer to which the base64-encoded data is to be
220   *                 written.
221   */
222  public static void encode(@NotNull final byte[] data, final int off,
223                            final int length,
224                            @NotNull final StringBuilder buffer)
225  {
226    encode(BASE64_ALPHABET, data, off, length, buffer, "=");
227  }
228
229
230
231  /**
232   * Appends a base64-encoded representation of the provided data to the given
233   * buffer.
234   *
235   * @param  data    The raw data to be encoded.  It must not be {@code null}.
236   * @param  buffer  The buffer to which the base64-encoded data is to be
237   *                 written.
238   */
239  public static void encode(@NotNull final byte[] data,
240                            @NotNull final ByteStringBuffer buffer)
241  {
242    encode(BASE64_ALPHABET, data, 0, data.length, buffer, "=");
243  }
244
245
246
247  /**
248   * Appends a base64-encoded representation of the provided data to the given
249   * buffer.
250   *
251   * @param  data    The raw data to be encoded.  It must not be {@code null}.
252   * @param  off     The offset in the array at which the data to encode begins.
253   * @param  length  The number of bytes to be encoded.
254   * @param  buffer  The buffer to which the base64-encoded data is to be
255   *                 written.
256   */
257  public static void encode(@NotNull final byte[] data, final int off,
258                            final int length,
259                            @NotNull final ByteStringBuffer buffer)
260  {
261    encode(BASE64_ALPHABET, data, off, length, buffer, "=");
262  }
263
264
265
266  /**
267   * Retrieves a base64url-encoded representation of the provided data to the
268   * given buffer.
269   *
270   * @param  data  The raw data to be encoded.  It must not be {@code null}.
271   * @param  pad   Indicates whether to pad the URL if necessary.  Padding will
272   *               use "%3d", as the URL-escaped representation of the equal
273   *               sign.
274   *
275   * @return  A base64url-encoded representation of the provided data to the
276   *          given buffer.
277   */
278  @NotNull()
279  public static String urlEncode(@NotNull final String data, final boolean pad)
280  {
281    return urlEncode(StaticUtils.getBytes(data), pad);
282  }
283
284
285
286  /**
287   * Retrieves a base64url-encoded representation of the provided data to the
288   * given buffer.
289   *
290   * @param  data    The raw data to be encoded.  It must not be {@code null}.
291   * @param  buffer  The buffer to which the base64-encoded data is to be
292   *                 written.
293   * @param  pad     Indicates whether to pad the URL if necessary.  Padding
294   *                 will use "%3d", as the URL-escaped representation of the
295   *                 equal sign.
296   */
297  public static void urlEncode(@NotNull final String data,
298                               @NotNull final StringBuilder buffer,
299                               final boolean pad)
300  {
301    final byte[] dataBytes = StaticUtils.getBytes(data);
302    encode(BASE64_ALPHABET, dataBytes, 0, dataBytes.length, buffer,
303         (pad ? "%3d" : null));
304  }
305
306
307
308  /**
309   * Retrieves a base64url-encoded representation of the provided data to the
310   * given buffer.
311   *
312   * @param  data    The raw data to be encoded.  It must not be {@code null}.
313   * @param  buffer  The buffer to which the base64-encoded data is to be
314   *                 written.
315   * @param  pad     Indicates whether to pad the URL if necessary.  Padding
316   *                 will use "%3d", as the URL-escaped representation of the
317   *                 equal sign.
318   */
319  public static void urlEncode(@NotNull final String data,
320                               @NotNull final ByteStringBuffer buffer,
321                               final boolean pad)
322  {
323    final byte[] dataBytes = StaticUtils.getBytes(data);
324    encode(BASE64_ALPHABET, dataBytes, 0, dataBytes.length, buffer,
325         (pad ? "%3d" : null));
326  }
327
328
329
330  /**
331   * Retrieves a base64url-encoded representation of the provided data to the
332   * given buffer.
333   *
334   * @param  data  The raw data to be encoded.  It must not be {@code null}.
335   * @param  pad   Indicates whether to pad the URL if necessary.  Padding will
336   *               use "%3d", as the URL-escaped representation of the equal
337   *               sign.
338   *
339   * @return  A base64url-encoded representation of the provided data to the
340   *          given buffer.
341   */
342  @NotNull()
343  public static String urlEncode(@NotNull final byte[] data, final boolean pad)
344  {
345    final StringBuilder buffer = new StringBuilder(4*data.length/3+6);
346    encode(BASE64URL_ALPHABET, data, 0, data.length, buffer,
347         (pad ? "%3d" : null));
348    return buffer.toString();
349  }
350
351
352
353  /**
354   * Appends a base64url-encoded representation of the provided data to the
355   * given buffer.
356   *
357   * @param  data    The raw data to be encoded.  It must not be {@code null}.
358   * @param  off     The offset in the array at which the data to encode begins.
359   * @param  length  The number of bytes to be encoded.
360   * @param  buffer  The buffer to which the base64-encoded data is to be
361   *                 written.
362   * @param  pad     Indicates whether to pad the URL if necessary.  Padding
363   *                 will use "%3d", as the URL-escaped representation of the
364   *                 equal sign.
365   */
366  public static void urlEncode(@NotNull final byte[] data, final int off,
367                               final int length,
368                               @NotNull final StringBuilder buffer,
369                               final boolean pad)
370  {
371    encode(BASE64URL_ALPHABET, data, off, length, buffer, (pad ? "%3d" : null));
372  }
373
374
375
376  /**
377   * Appends a base64url-encoded representation of the provided data to the
378   * given buffer.
379   *
380   * @param  data    The raw data to be encoded.  It must not be {@code null}.
381   * @param  off     The offset in the array at which the data to encode begins.
382   * @param  length  The number of bytes to be encoded.
383   * @param  buffer  The buffer to which the base64-encoded data is to be
384   *                 written.
385   * @param  pad     Indicates whether to pad the URL if necessary.  Padding
386   *                 will use "%3d", as the URL-escaped representation of the
387   *                 equal sign.
388   */
389  public static void urlEncode(@NotNull final byte[] data, final int off,
390                               final int length,
391                               @NotNull final ByteStringBuffer buffer,
392                               final boolean pad)
393  {
394    encode(BASE64URL_ALPHABET, data, off, length, buffer, (pad ? "%3d" : null));
395  }
396
397
398
399  /**
400   * Appends a base64-encoded representation of the provided data to the given
401   * buffer.
402   *
403   * @param  alphabet  The alphabet of base64 characters to use for the
404   *                   encoding.
405   * @param  data      The raw data to be encoded.  It must not be {@code null}.
406   * @param  off       The offset in the array at which the data to encode
407   *                   begins.
408   * @param  length    The number of bytes to be encoded.
409   * @param  buffer    The buffer to which the base64-encoded data is to be
410   *                   written.
411   * @param  padStr    The string to use for padding.  It may be {@code null} if
412   *                   no padding should be applied.
413   */
414  private static void encode(@NotNull final char[] alphabet,
415                             @NotNull final byte[] data,
416                             final int off, final int length,
417                             @NotNull final Appendable buffer,
418                             @Nullable final String padStr)
419  {
420    Validator.ensureNotNull(data);
421    Validator.ensureTrue(data.length >= off);
422    Validator.ensureTrue(data.length >= (off+length));
423
424    if (length == 0)
425    {
426      return;
427    }
428
429    try
430    {
431      int pos = off;
432      for (int i=0; i < (length / 3); i++)
433      {
434        final int intValue = ((data[pos++] & 0xFF) << 16) |
435             ((data[pos++] & 0xFF) << 8) |
436             (data[pos++] & 0xFF);
437
438        buffer.append(alphabet[(intValue >> 18) & 0x3F]);
439        buffer.append(alphabet[(intValue >> 12) & 0x3F]);
440        buffer.append(alphabet[(intValue >> 6) & 0x3F]);
441        buffer.append(alphabet[intValue & 0x3F]);
442      }
443
444      switch ((off+length) - pos)
445      {
446        case 1:
447          int intValue = (data[pos] & 0xFF) << 16;
448          buffer.append(alphabet[(intValue >> 18) & 0x3F]);
449          buffer.append(alphabet[(intValue >> 12) & 0x3F]);
450          if (padStr != null)
451          {
452            buffer.append(padStr);
453            buffer.append(padStr);
454          }
455          return;
456
457        case 2:
458          intValue = ((data[pos++] & 0xFF) << 16) | ((data[pos] & 0xFF) << 8);
459          buffer.append(alphabet[(intValue >> 18) & 0x3F]);
460          buffer.append(alphabet[(intValue >> 12) & 0x3F]);
461          buffer.append(alphabet[(intValue >> 6) & 0x3F]);
462          if (padStr != null)
463          {
464            buffer.append(padStr);
465          }
466          return;
467      }
468    }
469    catch (final IOException ioe)
470    {
471      Debug.debugException(ioe);
472
473      // This should never happen.
474      throw new RuntimeException(ioe.getMessage(), ioe);
475    }
476  }
477
478
479
480  /**
481   * Decodes the contents of the provided base64-encoded string.
482   *
483   * @param  data  The base64-encoded string to decode.  It must not be
484   *               {@code null}.
485   *
486   * @return  A byte array containing the decoded data.
487   *
488   * @throws  ParseException  If the contents of the provided string cannot be
489   *                          parsed as base64-encoded data.
490   */
491  @NotNull()
492  public static byte[] decode(@NotNull final String data)
493         throws ParseException
494  {
495    Validator.ensureNotNull(data);
496
497    final int length = data.length();
498    if (length == 0)
499    {
500      return StaticUtils.NO_BYTES;
501    }
502
503    if ((length % 4) != 0)
504    {
505      throw new ParseException(ERR_BASE64_DECODE_INVALID_LENGTH.get(), length);
506    }
507
508    int numBytes = 3 * (length / 4);
509    if (data.charAt(length-2) == '=')
510    {
511      numBytes -= 2;
512    }
513    else if (data.charAt(length-1) == '=')
514    {
515      numBytes--;
516    }
517
518    final byte[] b = new byte[numBytes];
519
520    int stringPos = 0;
521    int arrayPos  = 0;
522    while (stringPos < length)
523    {
524      int intValue = 0x00;
525      for (int i=0; i < 4; i++)
526      {
527        intValue <<= 6;
528        switch (data.charAt(stringPos++))
529        {
530          case 'A':
531            intValue |= 0x00;
532            break;
533          case 'B':
534            intValue |= 0x01;
535            break;
536          case 'C':
537            intValue |= 0x02;
538            break;
539          case 'D':
540            intValue |= 0x03;
541            break;
542          case 'E':
543            intValue |= 0x04;
544            break;
545          case 'F':
546            intValue |= 0x05;
547            break;
548          case 'G':
549            intValue |= 0x06;
550            break;
551          case 'H':
552            intValue |= 0x07;
553            break;
554          case 'I':
555            intValue |= 0x08;
556            break;
557          case 'J':
558            intValue |= 0x09;
559            break;
560          case 'K':
561            intValue |= 0x0A;
562            break;
563          case 'L':
564            intValue |= 0x0B;
565            break;
566          case 'M':
567            intValue |= 0x0C;
568            break;
569          case 'N':
570            intValue |= 0x0D;
571            break;
572          case 'O':
573            intValue |= 0x0E;
574            break;
575          case 'P':
576            intValue |= 0x0F;
577            break;
578          case 'Q':
579            intValue |= 0x10;
580            break;
581          case 'R':
582            intValue |= 0x11;
583            break;
584          case 'S':
585            intValue |= 0x12;
586            break;
587          case 'T':
588            intValue |= 0x13;
589            break;
590          case 'U':
591            intValue |= 0x14;
592            break;
593          case 'V':
594            intValue |= 0x15;
595            break;
596          case 'W':
597            intValue |= 0x16;
598            break;
599          case 'X':
600            intValue |= 0x17;
601            break;
602          case 'Y':
603            intValue |= 0x18;
604            break;
605          case 'Z':
606            intValue |= 0x19;
607            break;
608          case 'a':
609            intValue |= 0x1A;
610            break;
611          case 'b':
612            intValue |= 0x1B;
613            break;
614          case 'c':
615            intValue |= 0x1C;
616            break;
617          case 'd':
618            intValue |= 0x1D;
619            break;
620          case 'e':
621            intValue |= 0x1E;
622            break;
623          case 'f':
624            intValue |= 0x1F;
625            break;
626          case 'g':
627            intValue |= 0x20;
628            break;
629          case 'h':
630            intValue |= 0x21;
631            break;
632          case 'i':
633            intValue |= 0x22;
634            break;
635          case 'j':
636            intValue |= 0x23;
637            break;
638          case 'k':
639            intValue |= 0x24;
640            break;
641          case 'l':
642            intValue |= 0x25;
643            break;
644          case 'm':
645            intValue |= 0x26;
646            break;
647          case 'n':
648            intValue |= 0x27;
649            break;
650          case 'o':
651            intValue |= 0x28;
652            break;
653          case 'p':
654            intValue |= 0x29;
655            break;
656          case 'q':
657            intValue |= 0x2A;
658            break;
659          case 'r':
660            intValue |= 0x2B;
661            break;
662          case 's':
663            intValue |= 0x2C;
664            break;
665          case 't':
666            intValue |= 0x2D;
667            break;
668          case 'u':
669            intValue |= 0x2E;
670            break;
671          case 'v':
672            intValue |= 0x2F;
673            break;
674          case 'w':
675            intValue |= 0x30;
676            break;
677          case 'x':
678            intValue |= 0x31;
679            break;
680          case 'y':
681            intValue |= 0x32;
682            break;
683          case 'z':
684            intValue |= 0x33;
685            break;
686          case '0':
687            intValue |= 0x34;
688            break;
689          case '1':
690            intValue |= 0x35;
691            break;
692          case '2':
693            intValue |= 0x36;
694            break;
695          case '3':
696            intValue |= 0x37;
697            break;
698          case '4':
699            intValue |= 0x38;
700            break;
701          case '5':
702            intValue |= 0x39;
703            break;
704          case '6':
705            intValue |= 0x3A;
706            break;
707          case '7':
708            intValue |= 0x3B;
709            break;
710          case '8':
711            intValue |= 0x3C;
712            break;
713          case '9':
714            intValue |= 0x3D;
715            break;
716          case '+':
717            intValue |= 0x3E;
718            break;
719          case '/':
720            intValue |= 0x3F;
721            break;
722
723          case '=':
724            switch (length - stringPos)
725            {
726              case 0:
727                // The string ended with a single equal sign, so there are only
728                // two bytes left.  Shift the value eight bits to the right and
729                // read those two bytes.
730                intValue >>= 8;
731                b[arrayPos++] = (byte) ((intValue >> 8) & 0xFF);
732                b[arrayPos]   = (byte) (intValue & 0xFF);
733                return b;
734
735              case 1:
736                // The string ended with two equal signs, so there is only one
737                // byte left.  Shift the value ten bits to the right and read
738                // that single byte.
739                intValue >>= 10;
740                b[arrayPos] = (byte) (intValue & 0xFF);
741                return b;
742
743              default:
744                throw new ParseException(ERR_BASE64_DECODE_UNEXPECTED_EQUAL.get(
745                                              (stringPos-1)),
746                                         (stringPos-1));
747            }
748
749          default:
750            throw new ParseException(ERR_BASE64_DECODE_UNEXPECTED_CHAR.get(
751                                          data.charAt(stringPos-1)),
752                                     (stringPos-1));
753        }
754      }
755
756      b[arrayPos++] = (byte) ((intValue >> 16) & 0xFF);
757      b[arrayPos++] = (byte) ((intValue >> 8) & 0xFF);
758      b[arrayPos++] = (byte) (intValue & 0xFF);
759    }
760
761    return b;
762  }
763
764
765
766  /**
767   * Decodes the contents of the provided base64-encoded string to a string
768   * containing the raw data using the UTF-8 encoding.
769   *
770   * @param  data  The base64-encoded string to decode.  It must not be
771   *               {@code null}.
772   *
773   * @return  A string containing the decoded data.
774   *
775   * @throws  ParseException  If the contents of the provided string cannot be
776   *                          parsed as base64-encoded data using the UTF-8
777   *                          encoding.
778   */
779  @NotNull()
780  public static String decodeToString(@NotNull final String data)
781         throws ParseException
782  {
783    Validator.ensureNotNull(data);
784
785    final byte[] decodedBytes = decode(data);
786    return StaticUtils.toUTF8String(decodedBytes);
787  }
788
789
790
791  /**
792   * Decodes the contents of the provided base64url-encoded string.
793   *
794   * @param  data  The base64url-encoded string to decode.  It must not be
795   *               {@code null}.
796   *
797   * @return  A byte array containing the decoded data.
798   *
799   * @throws  ParseException  If the contents of the provided string cannot be
800   *                          parsed as base64url-encoded data.
801   */
802  @NotNull()
803  public static byte[] urlDecode(@NotNull final String data)
804         throws ParseException
805  {
806    Validator.ensureNotNull(data);
807
808    final int length = data.length();
809    if (length == 0)
810    {
811      return StaticUtils.NO_BYTES;
812    }
813
814    int stringPos = 0;
815    final ByteStringBuffer buffer = new ByteStringBuffer(length);
816decodeLoop:
817    while (stringPos < length)
818    {
819      int intValue = 0x00;
820      for (int i=0; i < 4; i++)
821      {
822        // Since the value may not be padded, then we need to handle the
823        // possibility of missing characters.
824        final char c;
825        if (stringPos >= length)
826        {
827          c = '=';
828          stringPos++;
829        }
830        else
831        {
832          c = data.charAt(stringPos++);
833        }
834
835        intValue <<= 6;
836        switch (c)
837        {
838          case 'A':
839            intValue |= 0x00;
840            break;
841          case 'B':
842            intValue |= 0x01;
843            break;
844          case 'C':
845            intValue |= 0x02;
846            break;
847          case 'D':
848            intValue |= 0x03;
849            break;
850          case 'E':
851            intValue |= 0x04;
852            break;
853          case 'F':
854            intValue |= 0x05;
855            break;
856          case 'G':
857            intValue |= 0x06;
858            break;
859          case 'H':
860            intValue |= 0x07;
861            break;
862          case 'I':
863            intValue |= 0x08;
864            break;
865          case 'J':
866            intValue |= 0x09;
867            break;
868          case 'K':
869            intValue |= 0x0A;
870            break;
871          case 'L':
872            intValue |= 0x0B;
873            break;
874          case 'M':
875            intValue |= 0x0C;
876            break;
877          case 'N':
878            intValue |= 0x0D;
879            break;
880          case 'O':
881            intValue |= 0x0E;
882            break;
883          case 'P':
884            intValue |= 0x0F;
885            break;
886          case 'Q':
887            intValue |= 0x10;
888            break;
889          case 'R':
890            intValue |= 0x11;
891            break;
892          case 'S':
893            intValue |= 0x12;
894            break;
895          case 'T':
896            intValue |= 0x13;
897            break;
898          case 'U':
899            intValue |= 0x14;
900            break;
901          case 'V':
902            intValue |= 0x15;
903            break;
904          case 'W':
905            intValue |= 0x16;
906            break;
907          case 'X':
908            intValue |= 0x17;
909            break;
910          case 'Y':
911            intValue |= 0x18;
912            break;
913          case 'Z':
914            intValue |= 0x19;
915            break;
916          case 'a':
917            intValue |= 0x1A;
918            break;
919          case 'b':
920            intValue |= 0x1B;
921            break;
922          case 'c':
923            intValue |= 0x1C;
924            break;
925          case 'd':
926            intValue |= 0x1D;
927            break;
928          case 'e':
929            intValue |= 0x1E;
930            break;
931          case 'f':
932            intValue |= 0x1F;
933            break;
934          case 'g':
935            intValue |= 0x20;
936            break;
937          case 'h':
938            intValue |= 0x21;
939            break;
940          case 'i':
941            intValue |= 0x22;
942            break;
943          case 'j':
944            intValue |= 0x23;
945            break;
946          case 'k':
947            intValue |= 0x24;
948            break;
949          case 'l':
950            intValue |= 0x25;
951            break;
952          case 'm':
953            intValue |= 0x26;
954            break;
955          case 'n':
956            intValue |= 0x27;
957            break;
958          case 'o':
959            intValue |= 0x28;
960            break;
961          case 'p':
962            intValue |= 0x29;
963            break;
964          case 'q':
965            intValue |= 0x2A;
966            break;
967          case 'r':
968            intValue |= 0x2B;
969            break;
970          case 's':
971            intValue |= 0x2C;
972            break;
973          case 't':
974            intValue |= 0x2D;
975            break;
976          case 'u':
977            intValue |= 0x2E;
978            break;
979          case 'v':
980            intValue |= 0x2F;
981            break;
982          case 'w':
983            intValue |= 0x30;
984            break;
985          case 'x':
986            intValue |= 0x31;
987            break;
988          case 'y':
989            intValue |= 0x32;
990            break;
991          case 'z':
992            intValue |= 0x33;
993            break;
994          case '0':
995            intValue |= 0x34;
996            break;
997          case '1':
998            intValue |= 0x35;
999            break;
1000          case '2':
1001            intValue |= 0x36;
1002            break;
1003          case '3':
1004            intValue |= 0x37;
1005            break;
1006          case '4':
1007            intValue |= 0x38;
1008            break;
1009          case '5':
1010            intValue |= 0x39;
1011            break;
1012          case '6':
1013            intValue |= 0x3A;
1014            break;
1015          case '7':
1016            intValue |= 0x3B;
1017            break;
1018          case '8':
1019            intValue |= 0x3C;
1020            break;
1021          case '9':
1022            intValue |= 0x3D;
1023            break;
1024          case '-':
1025            intValue |= 0x3E;
1026            break;
1027          case '_':
1028            intValue |= 0x3F;
1029            break;
1030          case '=':
1031          case '%':
1032            switch ((stringPos-1) % 4)
1033            {
1034              case 2:
1035                // The string should have two padding tokens, so only a single
1036                // byte of data remains.  Shift the value ten bits to the right
1037                // and read that single byte.
1038                intValue >>= 10;
1039                buffer.append((byte) (intValue & 0xFF));
1040                break decodeLoop;
1041              case 3:
1042                // The string should have a single padding token, so two bytes
1043                // of data remain.  Shift the value eight bits to the right and
1044                // read those two bytes.
1045                intValue >>= 8;
1046                buffer.append((byte) ((intValue >> 8) & 0xFF));
1047                buffer.append((byte) (intValue & 0xFF));
1048                break decodeLoop;
1049            }
1050
1051            // If we've gotten here, then that must mean the string had padding
1052            // when none was needed, or it had an invalid length.  That's an
1053            // error.
1054            throw new ParseException(ERR_BASE64_URLDECODE_INVALID_LENGTH.get(),
1055                 (stringPos-1));
1056
1057          default:
1058            throw new ParseException(
1059                 ERR_BASE64_DECODE_UNEXPECTED_CHAR.get(
1060                      data.charAt(stringPos-1)),
1061                 (stringPos-1));
1062        }
1063      }
1064
1065      buffer.append((byte) ((intValue >> 16) & 0xFF));
1066      buffer.append((byte) ((intValue >> 8) & 0xFF));
1067      buffer.append((byte) (intValue & 0xFF));
1068    }
1069
1070    return buffer.toByteArray();
1071  }
1072
1073
1074
1075  /**
1076   * Decodes the contents of the provided base64-encoded string to a string
1077   * containing the raw data using the UTF-8 encoding.
1078   *
1079   * @param  data  The base64-encoded string to decode.  It must not be
1080   *               {@code null}.
1081   *
1082   * @return  A string containing the decoded data.
1083   *
1084   * @throws  ParseException  If the contents of the provided string cannot be
1085   *                          parsed as base64-encoded data using the UTF-8
1086   *                          encoding.
1087   */
1088  @NotNull()
1089  public static String urlDecodeToString(@NotNull final String data)
1090         throws ParseException
1091  {
1092    Validator.ensureNotNull(data);
1093
1094    final byte[] decodedBytes = urlDecode(data);
1095    return StaticUtils.toUTF8String(decodedBytes);
1096  }
1097}