• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  */
16 package okio;
17 
18 import java.io.EOFException;
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.OutputStream;
22 import java.security.MessageDigest;
23 import java.security.NoSuchAlgorithmException;
24 import 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  */
37 public 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 
ByteString(byte[] data)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    */
of(byte... data)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}. */
encodeUtf8(String 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}. */
utf8()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    */
base64()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    */
decodeBase64(String base64)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. */
hex()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. */
decodeHex(String hex)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 
decodeHexDigit(char c)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    */
read(InputStream in, int byteCount)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    */
toAsciiLowercase()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}. */
getByte(int pos)163   public byte getByte(int pos) {
164     return data[pos];
165   }
166 
167   /**
168    * Returns the number of bytes in this ByteString.
169    */
size()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    */
toByteArray()177   public byte[] toByteArray() {
178     return data.clone();
179   }
180 
181   /** Writes the contents of this byte string to {@code out}. */
write(OutputStream out)182   public void write(OutputStream out) throws IOException {
183     out.write(data);
184   }
185 
equals(Object o)186   @Override public boolean equals(Object o) {
187     return o == this || o instanceof ByteString && Arrays.equals(((ByteString) o).data, data);
188   }
189 
hashCode()190   @Override public int hashCode() {
191     int result = hashCode;
192     return result != 0 ? result : (hashCode = Arrays.hashCode(data));
193   }
194 
toString()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