1/*
2 * Copyright 2014 Square Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package okio;
17
18import java.io.EOFException;
19import java.io.IOException;
20import java.io.InputStream;
21import java.io.OutputStream;
22import java.security.MessageDigest;
23import java.security.NoSuchAlgorithmException;
24import java.util.Arrays;
25
26/**
27 * An immutable sequence of bytes.
28 *
29 * <p><strong>Full disclosure:</strong> this class provides untrusted input and
30 * output streams with raw access to the underlying byte array. A hostile
31 * stream implementation could keep a reference to the mutable byte string,
32 * violating the immutable guarantee of this class. For this reason a byte
33 * string's immutability guarantee cannot be relied upon for security in applets
34 * and other environments that run both trusted and untrusted code in the same
35 * process.
36 */
37public final class ByteString {
38  private static final char[] HEX_DIGITS =
39      { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
40
41  /** A singleton empty {@code ByteString}. */
42  public static final ByteString EMPTY = ByteString.of();
43
44  final byte[] data;
45  private transient int hashCode; // Lazily computed; 0 if unknown.
46  private transient String utf8; // Lazily computed.
47
48  ByteString(byte[] data) {
49    this.data = data; // Trusted internal constructor doesn't clone data.
50  }
51
52  /**
53   * Returns a new byte string containing a clone of the bytes of {@code data}.
54   */
55  public static ByteString of(byte... data) {
56    return new ByteString(data.clone());
57  }
58
59  /** Returns a new byte string containing the {@code UTF-8} bytes of {@code s}. */
60  public static ByteString encodeUtf8(String s) {
61    ByteString byteString = new ByteString(s.getBytes(Util.UTF_8));
62    byteString.utf8 = s;
63    return byteString;
64  }
65
66  /** Constructs a new {@code String} by decoding the bytes as {@code UTF-8}. */
67  public String utf8() {
68    String result = utf8;
69    // We don't care if we double-allocate in racy code.
70    return result != null ? result : (utf8 = new String(data, Util.UTF_8));
71  }
72
73  /**
74   * Returns this byte string encoded as <a
75   * href="http://www.ietf.org/rfc/rfc2045.txt">Base64</a>. In violation of the
76   * RFC, the returned string does not wrap lines at 76 columns.
77   */
78  public String base64() {
79    return Base64.encode(data);
80  }
81
82  /**
83   * Decodes the Base64-encoded bytes and returns their value as a byte string.
84   * Returns null if {@code base64} is not a Base64-encoded sequence of bytes.
85   */
86  public static ByteString decodeBase64(String base64) {
87    byte[] decoded = Base64.decode(base64);
88    return decoded != null ? new ByteString(decoded) : null;
89  }
90
91  /** Returns this byte string encoded in hexadecimal. */
92  public String hex() {
93    char[] result = new char[data.length * 2];
94    int c = 0;
95    for (byte b : data) {
96      result[c++] = HEX_DIGITS[(b >> 4) & 0xf];
97      result[c++] = HEX_DIGITS[b & 0xf];
98    }
99    return new String(result);
100  }
101
102  /** Decodes the hex-encoded bytes and returns their value a byte string. */
103  public static ByteString decodeHex(String hex) {
104    if (hex.length() % 2 != 0) throw new IllegalArgumentException("Unexpected hex string: " + hex);
105
106    byte[] result = new byte[hex.length() / 2];
107    for (int i = 0; i < result.length; i++) {
108      int d1 = decodeHexDigit(hex.charAt(i * 2)) << 4;
109      int d2 = decodeHexDigit(hex.charAt(i * 2 + 1));
110      result[i] = (byte) (d1 + d2);
111    }
112    return of(result);
113  }
114
115  private static int decodeHexDigit(char c) {
116    if (c >= '0' && c <= '9') return c - '0';
117    if (c >= 'a' && c <= 'f') return c - 'a' + 10;
118    if (c >= 'A' && c <= 'F') return c - 'A' + 10;
119    throw new IllegalArgumentException("Unexpected hex digit: " + c);
120  }
121
122  /**
123   * Reads {@code count} bytes from {@code in} and returns the result.
124   *
125   * @throws java.io.EOFException if {@code in} has fewer than {@code count}
126   *     bytes to read.
127   */
128  public static ByteString read(InputStream in, int byteCount) throws IOException {
129    byte[] result = new byte[byteCount];
130    for (int offset = 0, read; offset < byteCount; offset += read) {
131      read = in.read(result, offset, byteCount - offset);
132      if (read == -1) throw new EOFException();
133    }
134    return new ByteString(result);
135  }
136
137  /**
138   * Returns a byte string equal to this byte string, but with the bytes 'A'
139   * through 'Z' replaced with the corresponding byte in 'a' through 'z'.
140   * Returns this byte string if it contains no bytes in 'A' through 'Z'.
141   */
142  public ByteString toAsciiLowercase() {
143    // Search for an uppercase character. If we don't find one, return this.
144    for (int i = 0; i < data.length; i++) {
145      byte c = data[i];
146      if (c < 'A' || c > 'Z') continue;
147
148      // If we reach this point, this string is not not lowercase. Create and
149      // return a new byte string.
150      byte[] lowercase = data.clone();
151      lowercase[i++] = (byte) (c - ('A' - 'a'));
152      for (; i < lowercase.length; i++) {
153        c = lowercase[i];
154        if (c < 'A' || c > 'Z') continue;
155        lowercase[i] = (byte) (c - ('A' - 'a'));
156      }
157      return new ByteString(lowercase);
158    }
159    return this;
160  }
161
162  /** Returns the byte at {@code pos}. */
163  public byte getByte(int pos) {
164    return data[pos];
165  }
166
167  /**
168   * Returns the number of bytes in this ByteString.
169   */
170  public int size() {
171    return data.length;
172  }
173
174  /**
175   * Returns a byte array containing a copy of the bytes in this {@code ByteString}.
176   */
177  public byte[] toByteArray() {
178    return data.clone();
179  }
180
181  /** Writes the contents of this byte string to {@code out}. */
182  public void write(OutputStream out) throws IOException {
183    out.write(data);
184  }
185
186  @Override public boolean equals(Object o) {
187    return o == this || o instanceof ByteString && Arrays.equals(((ByteString) o).data, data);
188  }
189
190  @Override public int hashCode() {
191    int result = hashCode;
192    return result != 0 ? result : (hashCode = Arrays.hashCode(data));
193  }
194
195  @Override public String toString() {
196    if (data.length == 0) {
197      return "ByteString[size=0]";
198    }
199
200    if (data.length <= 16) {
201      return String.format("ByteString[size=%s data=%s]", data.length, hex());
202    }
203
204    try {
205      return String.format("ByteString[size=%s md5=%s]", data.length,
206          ByteString.of(MessageDigest.getInstance("MD5").digest(data)).hex());
207    } catch (NoSuchAlgorithmException e) {
208      throw new AssertionError();
209    }
210  }
211}
212