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 davmail.ldap;
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) {
84 offset = 0;
85 pStream = new PrintStream(o);
86 }
87
88 protected void encodeLinePrefix(int len) {
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(byte[] buf, int off) {
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() {
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(numBytes);
164 for (j = 0; j < numBytes; j += bytesPerAtom()) {
165 encodeAtom(tmpbuffer, j);
166 }
167 if (numBytes < bytesPerLine()) {
168 break;
169 } else {
170 encodeLineSuffix();
171 }
172 }
173 }
174
175 /**
176 * A 'streamless' version of encode that simply takes a buffer of
177 * bytes and returns a string containing the encoded buffer.
178 */
179 public String encode(byte[] aBuffer) {
180 ByteArrayOutputStream outStream = new ByteArrayOutputStream();
181 ByteArrayInputStream inStream = new ByteArrayInputStream(aBuffer);
182 String retVal;
183 try {
184 encode(inStream, outStream);
185 // explicit ascii->unicode conversion
186 retVal = outStream.toString("ISO-8859-1");
187 } catch (Exception IOException) {
188 // This should never happen.
189 throw new Error("CharacterEncoder.encode internal error");
190 }
191 return (retVal);
192 }
193
194 /**
195 * Return a byte array from the remaining bytes in this ByteBuffer.
196 * <P>
197 * The ByteBuffer's position will be advanced to ByteBuffer's limit.
198 * <P>
199 * To avoid an extra copy, the implementation will attempt to return the
200 * byte array backing the ByteBuffer. If this is not possible, a
201 * new byte array will be created.
202 */
203 private byte [] getBytes(ByteBuffer bb) {
204 /*
205 * This should never return a BufferOverflowException, as we're
206 * careful to allocate just the right amount.
207 */
208 byte [] buf = null;
209
210 /*
211 * If it has a usable backing byte buffer, use it. Use only
212 * if the array exactly represents the current ByteBuffer.
213 */
214 if (bb.hasArray()) {
215 byte [] tmp = bb.array();
216 if ((tmp.length == bb.capacity()) &&
217 (tmp.length == bb.remaining())) {
218 buf = tmp;
219 bb.position(bb.limit());
220 }
221 }
222
223 if (buf == null) {
224 /*
225 * This class doesn't have a concept of encode(buf, len, off),
226 * so if we have a partial buffer, we must reallocate
227 * space.
228 */
229 buf = new byte[bb.remaining()];
230
231 /*
232 * position() automatically updated
233 */
234 bb.get(buf);
235 }
236
237 return buf;
238 }
239
240 /**
241 * A 'streamless' version of encode that simply takes a ByteBuffer
242 * and returns a string containing the encoded buffer.
243 * <P>
244 * The ByteBuffer's position will be advanced to ByteBuffer's limit.
245 */
246 public String encode(ByteBuffer aBuffer) {
247 byte [] buf = getBytes(aBuffer);
248 return encode(buf);
249 }
250
251 /**
252 * Encode bytes from the input stream, and write them as text characters
253 * to the output stream. This method will run until it exhausts the
254 * input stream. It differs from encode in that it will add the
255 * line at the end of a final line that is shorter than bytesPerLine().
256 */
257 public void encodeBuffer(InputStream inStream, OutputStream outStream)
258 throws IOException
259 {
260 int j;
261 int numBytes;
262 byte[] tmpbuffer = new byte[bytesPerLine()];
263
264 encodeBufferPrefix(outStream);
265
266 while (true) {
267 numBytes = readFully(inStream, tmpbuffer);
268 if (numBytes == 0) {
269 break;
270 }
271 encodeLinePrefix(numBytes);
272 for (j = 0; j < numBytes; j += bytesPerAtom()) {
273 encodeAtom(tmpbuffer, j);
274 }
275 encodeLineSuffix();
276 if (numBytes < bytesPerLine()) {
277 break;
278 }
279 }
280 }
281
282 /**
283 * Encode the buffer in <i>aBuffer</i> and write the encoded
284 * result to the OutputStream <i>aStream</i>.
285 */
286 public void encodeBuffer(byte[] aBuffer, OutputStream aStream)
287 throws IOException
288 {
289 ByteArrayInputStream inStream = new ByteArrayInputStream(aBuffer);
290 encodeBuffer(inStream, aStream);
291 }
292
293 /**
294 * A 'streamless' version of encode that simply takes a buffer of
295 * bytes and returns a string containing the encoded buffer.
296 */
297 @SuppressWarnings("unused")
298 public String encodeBuffer(byte[] aBuffer) {
299 ByteArrayOutputStream outStream = new ByteArrayOutputStream();
300 ByteArrayInputStream inStream = new ByteArrayInputStream(aBuffer);
301 try {
302 encodeBuffer(inStream, outStream);
303 } catch (Exception IOException) {
304 // This should never happen.
305 throw new Error("CharacterEncoder.encodeBuffer internal error");
306 }
307 return (outStream.toString());
308 }
309
310 /**
311 * Encode the <i>aBuffer</i> ByteBuffer and write the encoded
312 * result to the OutputStream <i>aStream</i>.
313 * <P>
314 * The ByteBuffer's position will be advanced to ByteBuffer's limit.
315 */
316 @SuppressWarnings("unused")
317 public void encodeBuffer(ByteBuffer aBuffer, OutputStream aStream)
318 throws IOException
319 {
320 byte [] buf = getBytes(aBuffer);
321 encodeBuffer(buf, aStream);
322 }
323
324 }