001: /*
002: * Copyright (C) 2004 Joe Walnes.
003: * Copyright (C) 2006, 2007 XStream Committers.
004: * All rights reserved.
005: *
006: * The software in this package is published under the terms of the BSD
007: * style license a copy of which has been included with this distribution in
008: * the LICENSE.txt file.
009: *
010: * Created on 06. August 2004 by Joe Walnes
011: */
012: package com.thoughtworks.xstream.core.util;
013:
014: import java.io.ByteArrayOutputStream;
015: import java.io.IOException;
016: import java.io.Reader;
017: import java.io.StringReader;
018:
019: /**
020: * Encodes binary data to plain text as Base64.
021: *
022: * <p>Despite there being a gazillion other Base64 implementations out there, this has been written as part of XStream as
023: * it forms a core part but is too trivial to warrant an extra dependency.</p>
024: *
025: * <p>This meets the standard as described in RFC 1521, section 5.2 <http://www.freesoft.org/CIE/RFC/1521/7.htm>, allowing
026: * other Base64 tools to manipulate the data.</p>
027: *
028: * @author Joe Walnes
029: */
030: public class Base64Encoder {
031:
032: // Here's how encoding works:
033: //
034: // 1) Incoming bytes are broken up into groups of 3 (each byte having 8 bits).
035: //
036: // 2) The combined 24 bits (3 * 8) are split into 4 groups of 6 bits.
037: //
038: // input |------||------||------| (3 values each with 8 bits)
039: // 101010101010101010101010
040: // output |----||----||----||----| (4 values each with 6 bits)
041: //
042: // 3) Each of these 4 groups of 6 bits are converted back to a number, which will fall in the range of 0 - 63.
043: //
044: // 4) Each of these 4 numbers are converted to an alphanumeric char in a specified mapping table, to create
045: // a 4 character string.
046: //
047: // 5) This is repeated for all groups of three bytes.
048: //
049: // 6) Special padding is done at the end of the stream using the '=' char.
050:
051: private static final char[] SIXTY_FOUR_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
052: .toCharArray();
053: private static final int[] REVERSE_MAPPING = new int[123];
054:
055: static {
056: for (int i = 0; i < SIXTY_FOUR_CHARS.length; i++)
057: REVERSE_MAPPING[SIXTY_FOUR_CHARS[i]] = i + 1;
058: }
059:
060: public String encode(byte[] input) {
061: StringBuffer result = new StringBuffer();
062: int outputCharCount = 0;
063: for (int i = 0; i < input.length; i += 3) {
064: int remaining = Math.min(3, input.length - i);
065: int oneBigNumber = (input[i] & 0xff) << 16
066: | (remaining <= 1 ? 0 : input[i + 1] & 0xff) << 8
067: | (remaining <= 2 ? 0 : input[i + 2] & 0xff);
068: for (int j = 0; j < 4; j++)
069: result
070: .append(remaining + 1 > j ? SIXTY_FOUR_CHARS[0x3f & oneBigNumber >> 6 * (3 - j)]
071: : '=');
072: if ((outputCharCount += 4) % 76 == 0)
073: result.append('\n');
074: }
075: return result.toString();
076: }
077:
078: public byte[] decode(String input) {
079: try {
080: ByteArrayOutputStream out = new ByteArrayOutputStream();
081: StringReader in = new StringReader(input);
082: for (int i = 0; i < input.length(); i += 4) {
083: int a[] = { mapCharToInt(in), mapCharToInt(in),
084: mapCharToInt(in), mapCharToInt(in) };
085: int oneBigNumber = (a[0] & 0x3f) << 18
086: | (a[1] & 0x3f) << 12 | (a[2] & 0x3f) << 6
087: | (a[3] & 0x3f);
088: for (int j = 0; j < 3; j++)
089: if (a[j + 1] >= 0)
090: out.write(0xff & oneBigNumber >> 8 * (2 - j));
091: }
092: return out.toByteArray();
093: } catch (IOException e) {
094: throw new Error(e + ": " + e.getMessage());
095: }
096: }
097:
098: private int mapCharToInt(Reader input) throws IOException {
099: int c;
100: while ((c = input.read()) != -1) {
101: int result = REVERSE_MAPPING[c];
102: if (result != 0)
103: return result - 1;
104: if (c == '=')
105: return -1;
106: }
107: return -1;
108: }
109: }
|