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 
  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 
  75     protected int bytesPerAtom() {
  76         return (1);
  77     }
  78 
  79     protected int bytesPerLine() {
  80         return (16);
  81     }
  82 
  83     protected void encodeBufferPrefix(OutputStream o) throws IOException {
  84         offset = 0;
  85         pStream = new PrintStream(o);
  86     }
  87 
  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 
  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 
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 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      */
 324     public void encodeBuffer(ByteBuffer aBuffer, OutputStream aStream)
 325         throws IOException
 326     {
 327         byte [] buf = getBytes(aBuffer);
 328         encodeBuffer(buf, aStream);
 329     }
 330 
 331 }