| Index: components/cronet/android/api/src/org/chromium/net/CronetEngine.java
|
| diff --git a/components/cronet/android/api/src/org/chromium/net/CronetEngine.java b/components/cronet/android/api/src/org/chromium/net/CronetEngine.java
|
| index 4bf88d7d8f3803f7a889188bf25592bf97600e82..9170046eb779ed34411aec3f24a54ef4735f2a68 100644
|
| --- a/components/cronet/android/api/src/org/chromium/net/CronetEngine.java
|
| +++ b/components/cronet/android/api/src/org/chromium/net/CronetEngine.java
|
| @@ -6,7 +6,12 @@
|
|
|
| import android.content.Context;
|
| import android.support.annotation.IntDef;
|
| +import android.util.Base64;
|
| import android.util.Log;
|
| +
|
| +import org.json.JSONArray;
|
| +import org.json.JSONException;
|
| +import org.json.JSONObject;
|
|
|
| import java.io.File;
|
| import java.lang.annotation.Retention;
|
| @@ -19,7 +24,6 @@
|
| import java.net.URLStreamHandlerFactory;
|
| import java.util.Date;
|
| import java.util.HashSet;
|
| -import java.util.LinkedList;
|
| import java.util.List;
|
| import java.util.Map;
|
| import java.util.Set;
|
| @@ -37,72 +41,18 @@
|
| * then {@link #build} is called to create the {@code CronetEngine}.
|
| */
|
| public static class Builder {
|
| - // A hint that a host supports QUIC.
|
| - static class QuicHint {
|
| - // The host.
|
| - final String mHost;
|
| - // Port of the server that supports QUIC.
|
| - final int mPort;
|
| - // Alternate protocol port.
|
| - final int mAlternatePort;
|
| -
|
| - QuicHint(String host, int port, int alternatePort) {
|
| - mHost = host;
|
| - mPort = port;
|
| - mAlternatePort = alternatePort;
|
| - }
|
| - }
|
| -
|
| - // A public key pin.
|
| - static class Pkp {
|
| - // Host to pin for.
|
| - final String mHost;
|
| - // Array of SHA-256 hashes of keys.
|
| - final byte[][] mHashes;
|
| - // Should pin apply to subdomains?
|
| - final boolean mIncludeSubdomains;
|
| - // When the pin expires.
|
| - final Date mExpirationDate;
|
| -
|
| - Pkp(String host, byte[][] hashes, boolean includeSubdomains, Date expirationDate) {
|
| - mHost = host;
|
| - mHashes = hashes;
|
| - mIncludeSubdomains = includeSubdomains;
|
| - mExpirationDate = expirationDate;
|
| - }
|
| - }
|
| -
|
| private static final Pattern INVALID_PKP_HOST_NAME = Pattern.compile("^[0-9\\.]*$");
|
|
|
| - // Private fields are simply storage of configuration for the resulting CronetEngine.
|
| - // See setters below for verbose descriptions.
|
| + private final JSONObject mConfig;
|
| private final Context mContext;
|
| - private final List<QuicHint> mQuicHints = new LinkedList<QuicHint>();
|
| - private final List<Pkp> mPkps = new LinkedList<Pkp>();
|
| - private String mUserAgent;
|
| - private String mStoragePath;
|
| - private boolean mLegacyModeEnabled;
|
| - private String mLibraryName;
|
| - private boolean mQuicEnabled;
|
| - private boolean mHttp2Enabled;
|
| - private boolean mSdchEnabled;
|
| - private String mDataReductionProxyKey;
|
| - private String mDataReductionProxyPrimaryProxy;
|
| - private String mDataReductionProxyFallbackProxy;
|
| - private String mDataReductionProxySecureProxyCheckUrl;
|
| - private boolean mDisableCache;
|
| - private int mHttpCacheMode;
|
| - private long mHttpCacheMaxSize;
|
| - private String mExperimentalOptions;
|
| - private long mMockCertVerifier;
|
|
|
| /**
|
| * Default config enables SPDY, disables QUIC, SDCH and HTTP cache.
|
| * @param context Android {@link Context} for engine to use.
|
| */
|
| public Builder(Context context) {
|
| + mConfig = new JSONObject();
|
| mContext = context;
|
| - setLibraryName("cronet");
|
| enableLegacyMode(false);
|
| enableQUIC(false);
|
| enableHTTP2(true);
|
| @@ -125,12 +75,11 @@
|
| * @return the builder to facilitate chaining.
|
| */
|
| public Builder setUserAgent(String userAgent) {
|
| - mUserAgent = userAgent;
|
| - return this;
|
| + return putString(CronetEngineBuilderList.USER_AGENT, userAgent);
|
| }
|
|
|
| String getUserAgent() {
|
| - return mUserAgent;
|
| + return mConfig.optString(CronetEngineBuilderList.USER_AGENT);
|
| }
|
|
|
| /**
|
| @@ -149,12 +98,12 @@
|
| throw new IllegalArgumentException(
|
| "Storage path must be set to existing directory");
|
| }
|
| - mStoragePath = value;
|
| - return this;
|
| +
|
| + return putString(CronetEngineBuilderList.STORAGE_PATH, value);
|
| }
|
|
|
| String storagePath() {
|
| - return mStoragePath;
|
| + return mConfig.optString(CronetEngineBuilderList.STORAGE_PATH);
|
| }
|
|
|
| /**
|
| @@ -166,12 +115,11 @@
|
| */
|
| @Deprecated
|
| public Builder enableLegacyMode(boolean value) {
|
| - mLegacyModeEnabled = value;
|
| - return this;
|
| + return putBoolean(CronetEngineBuilderList.ENABLE_LEGACY_MODE, value);
|
| }
|
|
|
| boolean legacyMode() {
|
| - return mLegacyModeEnabled;
|
| + return mConfig.optBoolean(CronetEngineBuilderList.ENABLE_LEGACY_MODE);
|
| }
|
|
|
| /**
|
| @@ -179,12 +127,11 @@
|
| * @return the builder to facilitate chaining.
|
| */
|
| Builder setLibraryName(String libName) {
|
| - mLibraryName = libName;
|
| - return this;
|
| + return putString(CronetEngineBuilderList.NATIVE_LIBRARY_NAME, libName);
|
| }
|
|
|
| String libraryName() {
|
| - return mLibraryName;
|
| + return mConfig.optString(CronetEngineBuilderList.NATIVE_LIBRARY_NAME, "cronet");
|
| }
|
|
|
| /**
|
| @@ -193,12 +140,7 @@
|
| * @return the builder to facilitate chaining.
|
| */
|
| public Builder enableQUIC(boolean value) {
|
| - mQuicEnabled = value;
|
| - return this;
|
| - }
|
| -
|
| - boolean quicEnabled() {
|
| - return mQuicEnabled;
|
| + return putBoolean(CronetEngineBuilderList.ENABLE_QUIC, value);
|
| }
|
|
|
| /**
|
| @@ -207,12 +149,7 @@
|
| * @return the builder to facilitate chaining.
|
| */
|
| public Builder enableHTTP2(boolean value) {
|
| - mHttp2Enabled = value;
|
| - return this;
|
| - }
|
| -
|
| - boolean http2Enabled() {
|
| - return mHttp2Enabled;
|
| + return putBoolean(CronetEngineBuilderList.ENABLE_SPDY, value);
|
| }
|
|
|
| /**
|
| @@ -223,12 +160,7 @@
|
| * @return the builder to facilitate chaining.
|
| */
|
| public Builder enableSDCH(boolean value) {
|
| - mSdchEnabled = value;
|
| - return this;
|
| - }
|
| -
|
| - boolean sdchEnabled() {
|
| - return mSdchEnabled;
|
| + return putBoolean(CronetEngineBuilderList.ENABLE_SDCH, value);
|
| }
|
|
|
| /**
|
| @@ -239,12 +171,7 @@
|
| * @return the builder to facilitate chaining.
|
| */
|
| public Builder enableDataReductionProxy(String key) {
|
| - mDataReductionProxyKey = key;
|
| - return this;
|
| - }
|
| -
|
| - String dataReductionProxyKey() {
|
| - return mDataReductionProxyKey;
|
| + return (putString(CronetEngineBuilderList.DATA_REDUCTION_PROXY_KEY, key));
|
| }
|
|
|
| /**
|
| @@ -270,22 +197,11 @@
|
| throw new IllegalArgumentException(
|
| "Primary and fallback proxies and check url must be set");
|
| }
|
| - mDataReductionProxyPrimaryProxy = primaryProxy;
|
| - mDataReductionProxyFallbackProxy = fallbackProxy;
|
| - mDataReductionProxySecureProxyCheckUrl = secureProxyCheckUrl;
|
| + putString(CronetEngineBuilderList.DATA_REDUCTION_PRIMARY_PROXY, primaryProxy);
|
| + putString(CronetEngineBuilderList.DATA_REDUCTION_FALLBACK_PROXY, fallbackProxy);
|
| + putString(CronetEngineBuilderList.DATA_REDUCTION_SECURE_PROXY_CHECK_URL,
|
| + secureProxyCheckUrl);
|
| return this;
|
| - }
|
| -
|
| - String dataReductionProxyPrimaryProxy() {
|
| - return mDataReductionProxyPrimaryProxy;
|
| - }
|
| -
|
| - String dataReductionProxyFallbackProxy() {
|
| - return mDataReductionProxyFallbackProxy;
|
| - }
|
| -
|
| - String dataReductionProxySecureProxyCheckUrl() {
|
| - return mDataReductionProxySecureProxyCheckUrl;
|
| }
|
|
|
| /** @deprecated not really deprecated but hidden. */
|
| @@ -333,45 +249,32 @@
|
| */
|
| public Builder enableHttpCache(@HttpCacheSetting int cacheMode, long maxSize) {
|
| if (cacheMode == HTTP_CACHE_DISK || cacheMode == HTTP_CACHE_DISK_NO_HTTP) {
|
| - if (storagePath() == null) {
|
| + if (storagePath().isEmpty()) {
|
| throw new IllegalArgumentException("Storage path must be set");
|
| }
|
| } else {
|
| - if (storagePath() != null) {
|
| - throw new IllegalArgumentException("Storage path must not be set");
|
| + if (!storagePath().isEmpty()) {
|
| + throw new IllegalArgumentException("Storage path must be empty");
|
| }
|
| }
|
| - mDisableCache =
|
| - (cacheMode == HTTP_CACHE_DISABLED || cacheMode == HTTP_CACHE_DISK_NO_HTTP);
|
| - mHttpCacheMaxSize = maxSize;
|
| + putBoolean(CronetEngineBuilderList.LOAD_DISABLE_CACHE,
|
| + cacheMode == HTTP_CACHE_DISABLED || cacheMode == HTTP_CACHE_DISK_NO_HTTP);
|
| + putLong(CronetEngineBuilderList.HTTP_CACHE_MAX_SIZE, maxSize);
|
|
|
| switch (cacheMode) {
|
| case HTTP_CACHE_DISABLED:
|
| - mHttpCacheMode = HttpCacheType.DISABLED;
|
| - break;
|
| + return putString(CronetEngineBuilderList.HTTP_CACHE,
|
| + CronetEngineBuilderList.HTTP_CACHE_DISABLED);
|
| case HTTP_CACHE_DISK_NO_HTTP:
|
| case HTTP_CACHE_DISK:
|
| - mHttpCacheMode = HttpCacheType.DISK;
|
| - break;
|
| + return putString(CronetEngineBuilderList.HTTP_CACHE,
|
| + CronetEngineBuilderList.HTTP_CACHE_DISK);
|
| +
|
| case HTTP_CACHE_IN_MEMORY:
|
| - mHttpCacheMode = HttpCacheType.MEMORY;
|
| - break;
|
| - default:
|
| - throw new IllegalArgumentException("Unknown cache mode");
|
| + return putString(CronetEngineBuilderList.HTTP_CACHE,
|
| + CronetEngineBuilderList.HTTP_CACHE_MEMORY);
|
| }
|
| return this;
|
| - }
|
| -
|
| - boolean cacheDisabled() {
|
| - return mDisableCache;
|
| - }
|
| -
|
| - long httpCacheMaxSize() {
|
| - return mHttpCacheMaxSize;
|
| - }
|
| -
|
| - int httpCacheMode() {
|
| - return mHttpCacheMode;
|
| }
|
|
|
| /**
|
| @@ -389,12 +292,22 @@
|
| if (host.contains("/")) {
|
| throw new IllegalArgumentException("Illegal QUIC Hint Host: " + host);
|
| }
|
| - mQuicHints.add(new QuicHint(host, port, alternatePort));
|
| + try {
|
| + JSONArray quicHints = mConfig.optJSONArray(CronetEngineBuilderList.QUIC_HINTS);
|
| + if (quicHints == null) {
|
| + quicHints = new JSONArray();
|
| + mConfig.put(CronetEngineBuilderList.QUIC_HINTS, quicHints);
|
| + }
|
| +
|
| + JSONObject hint = new JSONObject();
|
| + hint.put(CronetEngineBuilderList.QUIC_HINT_HOST, host);
|
| + hint.put(CronetEngineBuilderList.QUIC_HINT_PORT, port);
|
| + hint.put(CronetEngineBuilderList.QUIC_HINT_ALT_PORT, alternatePort);
|
| + quicHints.put(hint);
|
| + } catch (JSONException e) {
|
| + // Intentionally do nothing.
|
| + }
|
| return this;
|
| - }
|
| -
|
| - List<QuicHint> quicHints() {
|
| - return mQuicHints;
|
| }
|
|
|
| /**
|
| @@ -450,25 +363,51 @@
|
| throw new NullPointerException("The pin expiration date cannot be null");
|
| }
|
| String idnHostName = validateHostNameForPinningAndConvert(hostName);
|
| - // Convert the pin to BASE64 encoding. The hash set will eliminate duplications.
|
| - Set<byte[]> hashes = new HashSet<>(pinsSha256.size());
|
| - for (byte[] pinSha256 : pinsSha256) {
|
| - if (pinSha256 == null || pinSha256.length != 32) {
|
| - throw new IllegalArgumentException("Public key pin is invalid");
|
| + try {
|
| + // Add PKP_LIST JSON array element if it is not present.
|
| + JSONArray pkpList = mConfig.optJSONArray(CronetEngineBuilderList.PKP_LIST);
|
| + if (pkpList == null) {
|
| + pkpList = new JSONArray();
|
| + mConfig.put(CronetEngineBuilderList.PKP_LIST, pkpList);
|
| }
|
| - hashes.add(pinSha256);
|
| - }
|
| - // Add new element to PKP list.
|
| - mPkps.add(new Pkp(idnHostName, hashes.toArray(new byte[hashes.size()][]),
|
| - includeSubdomains, expirationDate));
|
| +
|
| + // Convert the pin to BASE64 encoding. The hash set will eliminate duplications.
|
| + Set<String> hashes = new HashSet<>(pinsSha256.size());
|
| + for (byte[] pinSha256 : pinsSha256) {
|
| + hashes.add(convertSha256ToBase64WithPrefix(pinSha256));
|
| + }
|
| +
|
| + // Add new element to PKP_LIST JSON array.
|
| + JSONObject pkp = new JSONObject();
|
| + pkp.put(CronetEngineBuilderList.PKP_HOST, idnHostName);
|
| + pkp.put(CronetEngineBuilderList.PKP_PIN_HASHES, new JSONArray(hashes));
|
| + pkp.put(CronetEngineBuilderList.PKP_INCLUDE_SUBDOMAINS, includeSubdomains);
|
| + // The expiration time is passed as a double, in seconds since January 1, 1970.
|
| + pkp.put(CronetEngineBuilderList.PKP_EXPIRATION_DATE,
|
| + (double) expirationDate.getTime() / 1000);
|
| + pkpList.put(pkp);
|
| + } catch (JSONException e) {
|
| + // This exception should never happen.
|
| + throw new RuntimeException(
|
| + "Failed to add pubic key pins with the given arguments", e);
|
| + }
|
| return this;
|
| }
|
|
|
| /**
|
| - * Returns list of public key pins.
|
| - */
|
| - List<Pkp> publicKeyPins() {
|
| - return mPkps;
|
| + * Converts a given SHA256 array of bytes to BASE64 encoded string and prepends
|
| + * {@code sha256/} prefix to it. The format corresponds to the format that is expected by
|
| + * {@code net::HashValue} class.
|
| + *
|
| + * @param sha256 SHA256 bytes to convert to BASE64.
|
| + * @return the BASE64 encoded SHA256 with the prefix.
|
| + * @throws IllegalArgumentException if the provided pin is invalid.
|
| + */
|
| + private static String convertSha256ToBase64WithPrefix(byte[] sha256) {
|
| + if (sha256 == null || sha256.length != 32) {
|
| + throw new IllegalArgumentException("Public key pin is invalid");
|
| + }
|
| + return "sha256/" + Base64.encodeToString(sha256, Base64.NO_WRAP);
|
| }
|
|
|
| /**
|
| @@ -507,24 +446,22 @@
|
| * @return the builder to facilitate chaining.
|
| */
|
| public Builder setExperimentalOptions(String options) {
|
| - mExperimentalOptions = options;
|
| - return this;
|
| - }
|
| -
|
| - String experimentalOptions() {
|
| - return mExperimentalOptions;
|
| + return putString(CronetEngineBuilderList.EXPERIMENTAL_OPTIONS, options);
|
| }
|
|
|
| /**
|
| * Sets a native MockCertVerifier for testing.
|
| */
|
| Builder setMockCertVerifierForTesting(long mockCertVerifier) {
|
| - mMockCertVerifier = mockCertVerifier;
|
| - return this;
|
| - }
|
| -
|
| - long mockCertVerifier() {
|
| - return mMockCertVerifier;
|
| + return putString(
|
| + CronetEngineBuilderList.MOCK_CERT_VERIFIER, String.valueOf(mockCertVerifier));
|
| + }
|
| +
|
| + /**
|
| + * Gets a JSON string representation of the builder.
|
| + */
|
| + String toJSONString() {
|
| + return mConfig.toString();
|
| }
|
|
|
| /**
|
| @@ -534,6 +471,48 @@
|
| */
|
| Context getContext() {
|
| return mContext;
|
| + }
|
| +
|
| + /**
|
| + * Sets a boolean value in the config. Returns a reference to the same
|
| + * config object, so you can chain put calls together.
|
| + * @return the builder to facilitate chaining.
|
| + */
|
| + private Builder putBoolean(String key, boolean value) {
|
| + try {
|
| + mConfig.put(key, value);
|
| + } catch (JSONException e) {
|
| + // Intentionally do nothing.
|
| + }
|
| + return this;
|
| + }
|
| +
|
| + /**
|
| + * Sets a long value in the config. Returns a reference to the same
|
| + * config object, so you can chain put calls together.
|
| + * @return the builder to facilitate chaining.
|
| + */
|
| + private Builder putLong(String key, long value) {
|
| + try {
|
| + mConfig.put(key, value);
|
| + } catch (JSONException e) {
|
| + // Intentionally do nothing.
|
| + }
|
| + return this;
|
| + }
|
| +
|
| + /**
|
| + * Sets a string value in the config. Returns a reference to the same
|
| + * config object, so you can chain put calls together.
|
| + * @return the builder to facilitate chaining.
|
| + */
|
| + private Builder putString(String key, String value) {
|
| + try {
|
| + mConfig.put(key, value);
|
| + } catch (JSONException e) {
|
| + // Intentionally do nothing.
|
| + }
|
| + return this;
|
| }
|
|
|
| /**
|
| @@ -543,7 +522,7 @@
|
| CronetEngine engine = createContext(this);
|
| // Clear MOCK_CERT_VERIFIER reference if there is any, since
|
| // the ownership has been transferred to the engine.
|
| - mMockCertVerifier = 0;
|
| + mConfig.remove(CronetEngineBuilderList.MOCK_CERT_VERIFIER);
|
| return engine;
|
| }
|
| }
|
| @@ -819,7 +798,7 @@
|
| @Deprecated
|
| public static CronetEngine createContext(Builder builder) {
|
| CronetEngine cronetEngine = null;
|
| - if (builder.getUserAgent() == null) {
|
| + if (builder.getUserAgent().isEmpty()) {
|
| builder.setUserAgent(builder.getDefaultUserAgent());
|
| }
|
| if (!builder.legacyMode()) {
|
|
|