| /* GENERATED SOURCE. DO NOT MODIFY. */ |
| /* |
| * Copyright 2014 Square Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.okhttp.okio; |
| |
| import java.io.EOFException; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.ObjectInputStream; |
| import java.io.ObjectOutputStream; |
| import java.io.OutputStream; |
| import java.io.Serializable; |
| import java.lang.reflect.Field; |
| import java.security.MessageDigest; |
| import java.security.NoSuchAlgorithmException; |
| import java.util.Arrays; |
| |
| import static com.android.okhttp.okio.Util.arrayRangeEquals; |
| import static com.android.okhttp.okio.Util.checkOffsetAndCount; |
| |
| /** |
| * An immutable sequence of bytes. |
| * |
| * <p>Byte strings compare lexicographically as a sequence of <strong>unsigned</strong> bytes. That |
| * is, the byte string {@code ff} sorts after {@code 00}. This is counter to the sort order of the |
| * corresponding bytes, where {@code -1} sorts before {@code 0}. |
| * |
| * <p><strong>Full disclosure:</strong> this class provides untrusted input and output streams with |
| * raw access to the underlying byte array. A hostile stream implementation could keep a reference |
| * to the mutable byte string, violating the immutable guarantee of this class. For this reason a |
| * byte string's immutability guarantee cannot be relied upon for security in applets and other |
| * environments that run both trusted and untrusted code in the same process. |
| * @hide This class is not part of the Android public SDK API |
| */ |
| public class ByteString implements Serializable, Comparable<ByteString> { |
| static final char[] HEX_DIGITS = |
| { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; |
| private static final long serialVersionUID = 1L; |
| |
| /** A singleton empty {@code ByteString}. */ |
| public static final ByteString EMPTY = ByteString.of(); |
| |
| final byte[] data; |
| transient int hashCode; // Lazily computed; 0 if unknown. |
| transient String utf8; // Lazily computed. |
| |
| ByteString(byte[] data) { |
| this.data = data; // Trusted internal constructor doesn't clone data. |
| } |
| |
| /** |
| * Returns a new byte string containing a clone of the bytes of {@code data}. |
| */ |
| public static ByteString of(byte... data) { |
| if (data == null) throw new IllegalArgumentException("data == null"); |
| return new ByteString(data.clone()); |
| } |
| |
| /** |
| * Returns a new byte string containing a copy of {@code byteCount} bytes of {@code data} starting |
| * at {@code offset}. |
| */ |
| public static ByteString of(byte[] data, int offset, int byteCount) { |
| if (data == null) throw new IllegalArgumentException("data == null"); |
| checkOffsetAndCount(data.length, offset, byteCount); |
| |
| byte[] copy = new byte[byteCount]; |
| System.arraycopy(data, offset, copy, 0, byteCount); |
| return new ByteString(copy); |
| } |
| |
| /** Returns a new byte string containing the {@code UTF-8} bytes of {@code s}. */ |
| public static ByteString encodeUtf8(String s) { |
| if (s == null) throw new IllegalArgumentException("s == null"); |
| ByteString byteString = new ByteString(s.getBytes(Util.UTF_8)); |
| byteString.utf8 = s; |
| return byteString; |
| } |
| |
| /** Constructs a new {@code String} by decoding the bytes as {@code UTF-8}. */ |
| public String utf8() { |
| String result = utf8; |
| // We don't care if we double-allocate in racy code. |
| return result != null ? result : (utf8 = new String(data, Util.UTF_8)); |
| } |
| |
| /** |
| * Returns this byte string encoded as <a |
| * href="http://www.ietf.org/rfc/rfc2045.txt">Base64</a>. In violation of the |
| * RFC, the returned string does not wrap lines at 76 columns. |
| */ |
| public String base64() { |
| return Base64.encode(data); |
| } |
| |
| /** Returns the MD5 hash of this byte string. */ |
| public ByteString md5() { |
| return digest("MD5"); |
| } |
| |
| /** Returns the SHA-256 hash of this byte string. */ |
| public ByteString sha256() { |
| return digest("SHA-256"); |
| } |
| |
| private ByteString digest(String digest) { |
| try { |
| return ByteString.of(MessageDigest.getInstance(digest).digest(data)); |
| } catch (NoSuchAlgorithmException e) { |
| throw new AssertionError(e); |
| } |
| } |
| |
| /** |
| * Returns this byte string encoded as <a href="http://www.ietf.org/rfc/rfc4648.txt">URL-safe |
| * Base64</a>. |
| */ |
| public String base64Url() { |
| return Base64.encodeUrl(data); |
| } |
| |
| /** |
| * Decodes the Base64-encoded bytes and returns their value as a byte string. |
| * Returns null if {@code base64} is not a Base64-encoded sequence of bytes. |
| */ |
| public static ByteString decodeBase64(String base64) { |
| if (base64 == null) throw new IllegalArgumentException("base64 == null"); |
| byte[] decoded = Base64.decode(base64); |
| return decoded != null ? new ByteString(decoded) : null; |
| } |
| |
| /** Returns this byte string encoded in hexadecimal. */ |
| public String hex() { |
| char[] result = new char[data.length * 2]; |
| int c = 0; |
| for (byte b : data) { |
| result[c++] = HEX_DIGITS[(b >> 4) & 0xf]; |
| result[c++] = HEX_DIGITS[b & 0xf]; |
| } |
| return new String(result); |
| } |
| |
| /** Decodes the hex-encoded bytes and returns their value a byte string. */ |
| public static ByteString decodeHex(String hex) { |
| if (hex == null) throw new IllegalArgumentException("hex == null"); |
| if (hex.length() % 2 != 0) throw new IllegalArgumentException("Unexpected hex string: " + hex); |
| |
| byte[] result = new byte[hex.length() / 2]; |
| for (int i = 0; i < result.length; i++) { |
| int d1 = decodeHexDigit(hex.charAt(i * 2)) << 4; |
| int d2 = decodeHexDigit(hex.charAt(i * 2 + 1)); |
| result[i] = (byte) (d1 + d2); |
| } |
| return of(result); |
| } |
| |
| private static int decodeHexDigit(char c) { |
| if (c >= '0' && c <= '9') return c - '0'; |
| if (c >= 'a' && c <= 'f') return c - 'a' + 10; |
| if (c >= 'A' && c <= 'F') return c - 'A' + 10; |
| throw new IllegalArgumentException("Unexpected hex digit: " + c); |
| } |
| |
| /** |
| * Reads {@code count} bytes from {@code in} and returns the result. |
| * |
| * @throws java.io.EOFException if {@code in} has fewer than {@code count} |
| * bytes to read. |
| */ |
| public static ByteString read(InputStream in, int byteCount) throws IOException { |
| if (in == null) throw new IllegalArgumentException("in == null"); |
| if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); |
| |
| byte[] result = new byte[byteCount]; |
| for (int offset = 0, read; offset < byteCount; offset += read) { |
| read = in.read(result, offset, byteCount - offset); |
| if (read == -1) throw new EOFException(); |
| } |
| return new ByteString(result); |
| } |
| |
| /** |
| * Returns a byte string equal to this byte string, but with the bytes 'A' |
| * through 'Z' replaced with the corresponding byte in 'a' through 'z'. |
| * Returns this byte string if it contains no bytes in 'A' through 'Z'. |
| */ |
| public ByteString toAsciiLowercase() { |
| // Search for an uppercase character. If we don't find one, return this. |
| for (int i = 0; i < data.length; i++) { |
| byte c = data[i]; |
| if (c < 'A' || c > 'Z') continue; |
| |
| // If we reach this point, this string is not not lowercase. Create and |
| // return a new byte string. |
| byte[] lowercase = data.clone(); |
| lowercase[i++] = (byte) (c - ('A' - 'a')); |
| for (; i < lowercase.length; i++) { |
| c = lowercase[i]; |
| if (c < 'A' || c > 'Z') continue; |
| lowercase[i] = (byte) (c - ('A' - 'a')); |
| } |
| return new ByteString(lowercase); |
| } |
| return this; |
| } |
| |
| /** |
| * Returns a byte string equal to this byte string, but with the bytes 'a' |
| * through 'z' replaced with the corresponding byte in 'A' through 'Z'. |
| * Returns this byte string if it contains no bytes in 'a' through 'z'. |
| */ |
| public ByteString toAsciiUppercase() { |
| // Search for an lowercase character. If we don't find one, return this. |
| for (int i = 0; i < data.length; i++) { |
| byte c = data[i]; |
| if (c < 'a' || c > 'z') continue; |
| |
| // If we reach this point, this string is not not uppercase. Create and |
| // return a new byte string. |
| byte[] lowercase = data.clone(); |
| lowercase[i++] = (byte) (c - ('a' - 'A')); |
| for (; i < lowercase.length; i++) { |
| c = lowercase[i]; |
| if (c < 'a' || c > 'z') continue; |
| lowercase[i] = (byte) (c - ('a' - 'A')); |
| } |
| return new ByteString(lowercase); |
| } |
| return this; |
| } |
| |
| /** |
| * Returns a byte string that is a substring of this byte string, beginning at the specified |
| * index until the end of this string. Returns this byte string if {@code beginIndex} is 0. |
| */ |
| public ByteString substring(int beginIndex) { |
| return substring(beginIndex, data.length); |
| } |
| |
| /** |
| * Returns a byte string that is a substring of this byte string, beginning at the specified |
| * {@code beginIndex} and ends at the specified {@code endIndex}. Returns this byte string if |
| * {@code beginIndex} is 0 and {@code endIndex} is the length of this byte string. |
| */ |
| public ByteString substring(int beginIndex, int endIndex) { |
| if (beginIndex < 0) throw new IllegalArgumentException("beginIndex < 0"); |
| if (endIndex > data.length) { |
| throw new IllegalArgumentException("endIndex > length(" + data.length + ")"); |
| } |
| |
| int subLen = endIndex - beginIndex; |
| if (subLen < 0) throw new IllegalArgumentException("endIndex < beginIndex"); |
| |
| if ((beginIndex == 0) && (endIndex == data.length)) { |
| return this; |
| } |
| |
| byte[] copy = new byte[subLen]; |
| System.arraycopy(data, beginIndex, copy, 0, subLen); |
| return new ByteString(copy); |
| } |
| |
| /** Returns the byte at {@code pos}. */ |
| public byte getByte(int pos) { |
| return data[pos]; |
| } |
| |
| /** |
| * Returns the number of bytes in this ByteString. |
| */ |
| public int size() { |
| return data.length; |
| } |
| |
| /** |
| * Returns a byte array containing a copy of the bytes in this {@code ByteString}. |
| */ |
| public byte[] toByteArray() { |
| return data.clone(); |
| } |
| |
| /** Writes the contents of this byte string to {@code out}. */ |
| public void write(OutputStream out) throws IOException { |
| if (out == null) throw new IllegalArgumentException("out == null"); |
| out.write(data); |
| } |
| |
| /** Writes the contents of this byte string to {@code buffer}. */ |
| void write(Buffer buffer) { |
| buffer.write(data, 0, data.length); |
| } |
| |
| /** |
| * Returns true if the bytes of this in {@code [offset..offset+byteCount)} equal the bytes of |
| * {@code other} in {@code [otherOffset..otherOffset+byteCount)}. Returns false if either range is |
| * out of bounds. |
| */ |
| public boolean rangeEquals(int offset, ByteString other, int otherOffset, int byteCount) { |
| return other.rangeEquals(otherOffset, this.data, offset, byteCount); |
| } |
| |
| /** |
| * Returns true if the bytes of this in {@code [offset..offset+byteCount)} equal the bytes of |
| * {@code other} in {@code [otherOffset..otherOffset+byteCount)}. Returns false if either range is |
| * out of bounds. |
| */ |
| public boolean rangeEquals(int offset, byte[] other, int otherOffset, int byteCount) { |
| return offset <= data.length - byteCount |
| && otherOffset <= other.length - byteCount |
| && arrayRangeEquals(data, offset, other, otherOffset, byteCount); |
| } |
| |
| @Override public boolean equals(Object o) { |
| if (o == this) return true; |
| return o instanceof ByteString |
| && ((ByteString) o).size() == data.length |
| && ((ByteString) o).rangeEquals(0, data, 0, data.length); |
| } |
| |
| @Override public int hashCode() { |
| int result = hashCode; |
| return result != 0 ? result : (hashCode = Arrays.hashCode(data)); |
| } |
| |
| @Override public int compareTo(ByteString byteString) { |
| int sizeA = size(); |
| int sizeB = byteString.size(); |
| for (int i = 0, size = Math.min(sizeA, sizeB); i < size; i++) { |
| int byteA = getByte(i) & 0xff; |
| int byteB = byteString.getByte(i) & 0xff; |
| if (byteA == byteB) continue; |
| return byteA < byteB ? -1 : 1; |
| } |
| if (sizeA == sizeB) return 0; |
| return sizeA < sizeB ? -1 : 1; |
| } |
| |
| @Override public String toString() { |
| if (data.length == 0) { |
| return "ByteString[size=0]"; |
| } |
| |
| if (data.length <= 16) { |
| return String.format("ByteString[size=%s data=%s]", data.length, hex()); |
| } |
| |
| return String.format("ByteString[size=%s md5=%s]", data.length, md5().hex()); |
| } |
| |
| private void readObject(ObjectInputStream in) throws IOException { |
| int dataLength = in.readInt(); |
| ByteString byteString = ByteString.read(in, dataLength); |
| try { |
| Field field = ByteString.class.getDeclaredField("data"); |
| field.setAccessible(true); |
| field.set(this, byteString.data); |
| } catch (NoSuchFieldException e) { |
| throw new AssertionError(); |
| } catch (IllegalAccessException e) { |
| throw new AssertionError(); |
| } |
| } |
| |
| private void writeObject(ObjectOutputStream out) throws IOException { |
| out.writeInt(data.length); |
| out.write(data); |
| } |
| } |