View Javadoc
1   package org.djutils.decoderdumper;
2   
3   import java.io.ByteArrayOutputStream;
4   import java.io.IOException;
5   
6   /**
7    * Decode base64 encoded data and show it as hex bytes. See https://en.wikipedia.org/wiki/Base64
8    * <p>
9    * Copyright (c) 2013-2019 Delft University of Technology, PO Box 5, 2600 AA, Delft, the Netherlands. All rights reserved. <br>
10   * BSD-style license. See <a href="https://opentrafficsim.org/node/13">OpenTrafficSim License</a>.
11   * <p>
12   * @version $Revision$, $LastChangedDate$, by $Author$, initial version Jan 7, 2019 <br>
13   * @author <a href="https://www.tudelft.nl/averbraeck">Alexander Verbraeck</a>
14   * @author <a href="https://www.tudelft.nl/staff/p.knoppers/">Peter Knoppers</a>
15   * @author <a href="https://www.transport.citg.tudelft.nl">Wouter Schakel</a>
16   */
17  public class Base64Decoder implements Decoder
18  {
19      /** Assembling space for decoded data. */
20      private int notYetDecodedData = 0;
21  
22      /** Number of accumulated bits in <code>notYetDecodedData</code>. */
23      private int accumulatedBits = 0;
24  
25      /** Dumper used internally to assemble the decoded data into hex values and char values. */
26      final private Dumper<Base64Decoder> internalDumper = new Dumper<>();
27  
28      /** Collector for the output of the internal dumper. */
29      final private ByteArrayOutputStream baos;
30  
31      /** Count number of equals (=) symbols seen. */
32      private int endOfInputCharsSeen = 0;
33  
34      /** Set when an error is detected in the input stream. */
35      private boolean errorDetected = false;
36  
37      /**
38       * Construct a new Base64Decoder.
39       * @param decodedBytesPerLine int; maximum number of decoded input characters resulting in one output line
40       * @param extraSpaceAfterEvery int; insert an extra space after every N output fields (a multiple of 3 makes sense for the
41       *            base64 decoder because base64 encodes three bytes into 4 characters)
42       */
43      public Base64Decoder(int decodedBytesPerLine, int extraSpaceAfterEvery)
44      {
45          this.baos = new ByteArrayOutputStream();
46          this.internalDumper.setOutputStream(this.baos);
47          int maximumBytesPerOutputLine = (decodedBytesPerLine + 3) / 4 * 3;
48          this.internalDumper.addDecoder(new HexDecoder(maximumBytesPerOutputLine, extraSpaceAfterEvery));
49          this.internalDumper.addDecoder(new FixedString("  "));
50          this.internalDumper.addDecoder(new CharDecoder(maximumBytesPerOutputLine, extraSpaceAfterEvery));
51      }
52  
53      /** {@inheritDoc} */
54      @Override
55      public String getResult()
56      {
57          try
58          {
59              this.internalDumper.flush();
60              String result = this.baos.toString();
61              this.baos.reset();
62              return result;
63          }
64          catch (IOException ioe)
65          {
66              // Cannot happen because writing to a ByteArrayOutputStream should never fail
67              return null;
68          }
69      }
70  
71      /** {@inheritDoc} */
72      @Override
73      public int getMaximumWidth()
74      {
75          return this.internalDumper.getMaximumWidth();
76      }
77  
78      /** {@inheritDoc} */
79      @Override
80      public boolean append(int address, byte theByte) throws IOException
81      {
82          if (theByte == 61)
83          {
84              this.endOfInputCharsSeen++; // equals
85          }
86          if (this.endOfInputCharsSeen > 0)
87          {
88              return false; // This decoder does not handle multiple base64 encoded objects in its input
89          }
90          int value;
91          if (theByte >= 48 && theByte <= 57)
92          {
93              value = theByte - 48 + 52; // Digit
94          }
95          else if (theByte >= 65 && theByte <= 90)
96          {
97              value = theByte - 65 + 0; // Capital letter
98          }
99          else if (theByte >= 97 && theByte <= 122)
100         {
101             value = theByte - 97 + 26;
102         }
103         else if (theByte == 43 || theByte == 45 || theByte == 46)
104         {
105             value = 62; // Plus or minus or dot
106         }
107         else if (theByte == 47 || theByte == 95 || theByte == 44)
108         {
109             value = 63; // Slash or underscore or comma
110         }
111         else if (theByte <= 32)
112         {
113             return false; // White space can appear anywhere and should be ignored (but this test for white space is bad)
114         }
115         else
116         {
117             // Illegal byte in input
118             if (!this.errorDetected) // First error
119             {
120                 // At this point we might insert some indicator in the output to indicate the location of the first error
121             }
122             this.errorDetected = true;
123             return false;
124         }
125         this.notYetDecodedData = (this.notYetDecodedData << 6) + value;
126         this.accumulatedBits += 6;
127         if (this.accumulatedBits >= 8)
128         {
129             int byteValue = this.notYetDecodedData >> (this.accumulatedBits - 8);
130             this.accumulatedBits -= 8;
131             this.notYetDecodedData -= byteValue << this.accumulatedBits;
132             boolean result = this.internalDumper.append((byte) byteValue);
133             return result;
134         }
135         return false;
136     }
137 
138     /** {@inheritDoc} */
139     @Override
140     public boolean ignoreForIdenticalOutputCheck()
141     {
142         return false;
143     }
144 
145 }