1 /*
2  * Copyright (c) 1995, 2015, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 
27 package sun.security.util;
28 
29 import java.io.ByteArrayInputStream;
30 import java.io.ByteArrayOutputStream;
31 import java.io.InputStream;
32 import java.io.PrintStream;
33 import java.io.OutputStream;
34 import java.io.IOException;
35 import java.nio.ByteBuffer;
36 
37 /**
38  * This class encodes a buffer into the classic: "Hexadecimal Dump" format of
39  * the past. It is useful for analyzing the contents of binary buffers.
40  * The format produced is as follows:
41  * <pre>
42  * xxxx: 00 11 22 33 44 55 66 77   88 99 aa bb cc dd ee ff ................
43  * </pre>
44  * Where xxxx is the offset into the buffer in 16 byte chunks, followed
45  * by ascii coded hexadecimal bytes followed by the ASCII representation of
46  * the bytes or '.' if they are not valid bytes.
47  *
48  * @author      Chuck McManis
49  */
50 
51 public class HexDumpEncoder {
52 
53     private int offset;
54     private int thisLineLength;
55     private int currentByte;
56     private byte thisLine[] = new byte[16];
57 
hexDigit(PrintStream p, byte x)58     static void hexDigit(PrintStream p, byte x) {
59         char c;
60 
61         c = (char) ((x >> 4) & 0xf);
62         if (c > 9)
63             c = (char) ((c-10) + 'A');
64         else
65             c = (char)(c + '0');
66         p.write(c);
67         c = (char) (x & 0xf);
68         if (c > 9)
69             c = (char)((c-10) + 'A');
70         else
71             c = (char)(c + '0');
72         p.write(c);
73     }
74 
bytesPerAtom()75     protected int bytesPerAtom() {
76         return (1);
77     }
78 
bytesPerLine()79     protected int bytesPerLine() {
80         return (16);
81     }
82 
encodeBufferPrefix(OutputStream o)83     protected void encodeBufferPrefix(OutputStream o) throws IOException {
84         offset = 0;
85         pStream = new PrintStream(o);
86     }
87 
encodeLinePrefix(OutputStream o, int len)88     protected void encodeLinePrefix(OutputStream o, int len) throws IOException {
89         hexDigit(pStream, (byte)((offset >>> 8) & 0xff));
90         hexDigit(pStream, (byte)(offset & 0xff));
91         pStream.print(": ");
92         currentByte = 0;
93         thisLineLength = len;
94     }
95 
encodeAtom(OutputStream o, byte buf[], int off, int len)96     protected void encodeAtom(OutputStream o, byte buf[], int off, int len) throws IOException {
97         thisLine[currentByte] = buf[off];
98         hexDigit(pStream, buf[off]);
99         pStream.print(" ");
100         currentByte++;
101         if (currentByte == 8)
102             pStream.print("  ");
103     }
104 
encodeLineSuffix(OutputStream o)105     protected void encodeLineSuffix(OutputStream o) throws IOException {
106         if (thisLineLength < 16) {
107             for (int i = thisLineLength; i < 16; i++) {
108                 pStream.print("   ");
109                 if (i == 7)
110                     pStream.print("  ");
111             }
112         }
113         pStream.print(" ");
114         for (int i = 0; i < thisLineLength; i++) {
115             if ((thisLine[i] < ' ') || (thisLine[i] > 'z')) {
116                 pStream.print(".");
117             } else {
118                 pStream.write(thisLine[i]);
119             }
120         }
121         pStream.println();
122         offset += thisLineLength;
123     }
124 
125     /** Stream that understands "printing" */
126     protected PrintStream pStream;
127 
128     /**
129      * This method works around the bizarre semantics of BufferedInputStream's
130      * read method.
131      */
readFully(InputStream in, byte buffer[])132     protected int readFully(InputStream in, byte buffer[])
133             throws java.io.IOException {
134         for (int i = 0; i < buffer.length; i++) {
135             int q = in.read();
136             if (q == -1)
137                 return i;
138             buffer[i] = (byte)q;
139         }
140         return buffer.length;
141     }
142 
143     /**
144      * Encode bytes from the input stream, and write them as text characters
145      * to the output stream. This method will run until it exhausts the
146      * input stream, but does not print the line suffix for a final
147      * line that is shorter than bytesPerLine().
148      */
encode(InputStream inStream, OutputStream outStream)149     public void encode(InputStream inStream, OutputStream outStream)
150         throws IOException
151     {
152         int     j;
153         int     numBytes;
154         byte    tmpbuffer[] = new byte[bytesPerLine()];
155 
156         encodeBufferPrefix(outStream);
157 
158         while (true) {
159             numBytes = readFully(inStream, tmpbuffer);
160             if (numBytes == 0) {
161                 break;
162             }
163             encodeLinePrefix(outStream, numBytes);
164             for (j = 0; j < numBytes; j += bytesPerAtom()) {
165 
166                 if ((j + bytesPerAtom()) <= numBytes) {
167                     encodeAtom(outStream, tmpbuffer, j, bytesPerAtom());
168                 } else {
169                     encodeAtom(outStream, tmpbuffer, j, (numBytes)- j);
170                 }
171             }
172             if (numBytes < bytesPerLine()) {
173                 break;
174             } else {
175                 encodeLineSuffix(outStream);
176             }
177         }
178     }
179 
180     /**
181      * A 'streamless' version of encode that simply takes a buffer of
182      * bytes and returns a string containing the encoded buffer.
183      */
encode(byte aBuffer[])184     public String encode(byte aBuffer[]) {
185         ByteArrayOutputStream outStream = new ByteArrayOutputStream();
186         ByteArrayInputStream    inStream = new ByteArrayInputStream(aBuffer);
187         String retVal = null;
188         try {
189             encode(inStream, outStream);
190             // explicit ascii->unicode conversion
191             retVal = outStream.toString("ISO-8859-1");
192         } catch (Exception IOException) {
193             // This should never happen.
194             throw new Error("CharacterEncoder.encode internal error");
195         }
196         return (retVal);
197     }
198 
199     /**
200      * Return a byte array from the remaining bytes in this ByteBuffer.
201      * <P>
202      * The ByteBuffer's position will be advanced to ByteBuffer's limit.
203      * <P>
204      * To avoid an extra copy, the implementation will attempt to return the
205      * byte array backing the ByteBuffer.  If this is not possible, a
206      * new byte array will be created.
207      */
getBytes(ByteBuffer bb)208     private byte [] getBytes(ByteBuffer bb) {
209         /*
210          * This should never return a BufferOverflowException, as we're
211          * careful to allocate just the right amount.
212          */
213         byte [] buf = null;
214 
215         /*
216          * If it has a usable backing byte buffer, use it.  Use only
217          * if the array exactly represents the current ByteBuffer.
218          */
219         if (bb.hasArray()) {
220             byte [] tmp = bb.array();
221             if ((tmp.length == bb.capacity()) &&
222                     (tmp.length == bb.remaining())) {
223                 buf = tmp;
224                 bb.position(bb.limit());
225             }
226         }
227 
228         if (buf == null) {
229             /*
230              * This class doesn't have a concept of encode(buf, len, off),
231              * so if we have a partial buffer, we must reallocate
232              * space.
233              */
234             buf = new byte[bb.remaining()];
235 
236             /*
237              * position() automatically updated
238              */
239             bb.get(buf);
240         }
241 
242         return buf;
243     }
244 
245     /**
246      * A 'streamless' version of encode that simply takes a ByteBuffer
247      * and returns a string containing the encoded buffer.
248      * <P>
249      * The ByteBuffer's position will be advanced to ByteBuffer's limit.
250      */
encode(ByteBuffer aBuffer)251     public String encode(ByteBuffer aBuffer) {
252         byte [] buf = getBytes(aBuffer);
253         return encode(buf);
254     }
255 
256     /**
257      * Encode bytes from the input stream, and write them as text characters
258      * to the output stream. This method will run until it exhausts the
259      * input stream. It differs from encode in that it will add the
260      * line at the end of a final line that is shorter than bytesPerLine().
261      */
encodeBuffer(InputStream inStream, OutputStream outStream)262     public void encodeBuffer(InputStream inStream, OutputStream outStream)
263         throws IOException
264     {
265         int     j;
266         int     numBytes;
267         byte    tmpbuffer[] = new byte[bytesPerLine()];
268 
269         encodeBufferPrefix(outStream);
270 
271         while (true) {
272             numBytes = readFully(inStream, tmpbuffer);
273             if (numBytes == 0) {
274                 break;
275             }
276             encodeLinePrefix(outStream, numBytes);
277             for (j = 0; j < numBytes; j += bytesPerAtom()) {
278                 if ((j + bytesPerAtom()) <= numBytes) {
279                     encodeAtom(outStream, tmpbuffer, j, bytesPerAtom());
280                 } else {
281                     encodeAtom(outStream, tmpbuffer, j, (numBytes)- j);
282                 }
283             }
284             encodeLineSuffix(outStream);
285             if (numBytes < bytesPerLine()) {
286                 break;
287             }
288         }
289     }
290 
291     /**
292      * Encode the buffer in <i>aBuffer</i> and write the encoded
293      * result to the OutputStream <i>aStream</i>.
294      */
encodeBuffer(byte aBuffer[], OutputStream aStream)295     public void encodeBuffer(byte aBuffer[], OutputStream aStream)
296         throws IOException
297     {
298         ByteArrayInputStream inStream = new ByteArrayInputStream(aBuffer);
299         encodeBuffer(inStream, aStream);
300     }
301 
302     /**
303      * A 'streamless' version of encode that simply takes a buffer of
304      * bytes and returns a string containing the encoded buffer.
305      */
encodeBuffer(byte aBuffer[])306     public String encodeBuffer(byte aBuffer[]) {
307         ByteArrayOutputStream   outStream = new ByteArrayOutputStream();
308         ByteArrayInputStream    inStream = new ByteArrayInputStream(aBuffer);
309         try {
310             encodeBuffer(inStream, outStream);
311         } catch (Exception IOException) {
312             // This should never happen.
313             throw new Error("CharacterEncoder.encodeBuffer internal error");
314         }
315         return (outStream.toString());
316     }
317 
318     /**
319      * Encode the <i>aBuffer</i> ByteBuffer and write the encoded
320      * result to the OutputStream <i>aStream</i>.
321      * <P>
322      * The ByteBuffer's position will be advanced to ByteBuffer's limit.
323      */
encodeBuffer(ByteBuffer aBuffer, OutputStream aStream)324     public void encodeBuffer(ByteBuffer aBuffer, OutputStream aStream)
325         throws IOException
326     {
327         byte [] buf = getBytes(aBuffer);
328         encodeBuffer(buf, aStream);
329     }
330 
331 }
332