Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1225)

Unified Diff: components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBuilderImpl.java

Issue 2339223002: Cronet API Refactoring (Closed)
Patch Set: Rebase & Conflict Resolution Created 4 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBuilderImpl.java
diff --git a/components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBuilderImpl.java b/components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBuilderImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..54a93d03e128de50b1be09aec56086589de092e7
--- /dev/null
+++ b/components/cronet/android/java/src/org/chromium/net/impl/CronetEngineBuilderImpl.java
@@ -0,0 +1,516 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+package org.chromium.net.impl;
+
+import android.content.Context;
+import android.support.annotation.IntDef;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+
+import static org.chromium.net.CronetEngine.Builder.HTTP_CACHE_DISABLED;
+import static org.chromium.net.CronetEngine.Builder.HTTP_CACHE_DISK;
+import static org.chromium.net.CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP;
+import static org.chromium.net.CronetEngine.Builder.HTTP_CACHE_IN_MEMORY;
+
+import org.chromium.net.CronetEngine;
+import org.chromium.net.ExperimentalCronetEngine;
+import org.chromium.net.ICronetEngineBuilder;
+
+import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.Constructor;
+import java.net.IDN;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * Implementation of {@link ICronetEngineBuilder}.
+ */
+public class CronetEngineBuilderImpl extends ICronetEngineBuilder {
+ /**
+ * A hint that a host supports QUIC.
+ */
+ public 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.
+ */
+ public 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 String TAG = "CronetEngineBuilder";
+ private static final String NATIVE_CRONET_IMPL_CLASS =
+ "org.chromium.net.impl.CronetUrlRequestContext";
+ private static final String JAVA_CRONET_IMPL_CLASS = "org.chromium.net.impl.JavaCronetEngine";
+ 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 Context mContext;
+ private final List<QuicHint> mQuicHints = new LinkedList<>();
+ private final List<Pkp> mPkps = new LinkedList<>();
+ private boolean mPublicKeyPinningBypassForLocalTrustAnchorsEnabled;
+ private String mUserAgent;
+ private String mStoragePath;
+ private boolean mLegacyModeEnabled;
+ private CronetEngine.Builder.LibraryLoader mLibraryLoader;
+ 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;
+ private boolean mNetworkQualityEstimatorEnabled;
+ private String mCertVerifierData;
+
+ /**
+ * Default config enables SPDY, disables QUIC, SDCH and HTTP cache.
+ * @param context Android {@link Context} for engine to use.
+ */
+ public CronetEngineBuilderImpl(Context context) {
+ mContext = context;
+ setLibraryName("cronet");
+ enableLegacyMode(false);
+ enableQuic(false);
+ enableHttp2(true);
+ enableSdch(false);
+ enableHttpCache(HTTP_CACHE_DISABLED, 0);
+ enableNetworkQualityEstimator(false);
+ enablePublicKeyPinningBypassForLocalTrustAnchors(true);
+ }
+
+ @Override
+ public String getDefaultUserAgent() {
+ return UserAgent.from(mContext);
+ }
+
+ @Override
+ public CronetEngineBuilderImpl setUserAgent(String userAgent) {
+ mUserAgent = userAgent;
+ return this;
+ }
+
+ String getUserAgent() {
+ return mUserAgent;
+ }
+
+ @Override
+ public CronetEngineBuilderImpl setStoragePath(String value) {
+ if (!new File(value).isDirectory()) {
+ throw new IllegalArgumentException("Storage path must be set to existing directory");
+ }
+ mStoragePath = value;
+ return this;
+ }
+
+ String storagePath() {
+ return mStoragePath;
+ }
+
+ @Override
+ public CronetEngineBuilderImpl enableLegacyMode(boolean value) {
+ mLegacyModeEnabled = value;
+ return this;
+ }
+
+ private boolean legacyMode() {
+ return mLegacyModeEnabled;
+ }
+
+ /**
+ * Overrides the name of the native library backing Cronet.
+ * @param libName the name of the native library backing Cronet.
+ * @return the builder to facilitate chaining.
+ */
+ public CronetEngineBuilderImpl setLibraryName(String libName) {
+ mLibraryName = libName;
+ return this;
+ }
+
+ String libraryName() {
+ return mLibraryName;
+ }
+
+ @Override
+ public CronetEngineBuilderImpl setLibraryLoader(CronetEngine.Builder.LibraryLoader loader) {
+ mLibraryLoader = loader;
+ return this;
+ }
+
+ CronetEngine.Builder.LibraryLoader libraryLoader() {
+ return mLibraryLoader;
+ }
+
+ @Override
+ public CronetEngineBuilderImpl enableQuic(boolean value) {
+ mQuicEnabled = value;
+ return this;
+ }
+
+ boolean quicEnabled() {
+ return mQuicEnabled;
+ }
+
+ /**
+ * Constructs default QUIC User Agent Id string including application name
+ * and Cronet version. Returns empty string if QUIC is not enabled.
+ *
+ * @return QUIC User Agent ID string.
+ */
+ String getDefaultQuicUserAgentId() {
+ return mQuicEnabled ? UserAgent.getQuicUserAgentIdFrom(mContext) : "";
+ }
+
+ @Override
+ public CronetEngineBuilderImpl enableHttp2(boolean value) {
+ mHttp2Enabled = value;
+ return this;
+ }
+
+ boolean http2Enabled() {
+ return mHttp2Enabled;
+ }
+
+ @Override
+ public CronetEngineBuilderImpl enableSdch(boolean value) {
+ mSdchEnabled = value;
+ return this;
+ }
+
+ boolean sdchEnabled() {
+ return mSdchEnabled;
+ }
+
+ @Override
+ public CronetEngineBuilderImpl enableDataReductionProxy(String key) {
+ mDataReductionProxyKey = key;
+ return this;
+ }
+
+ String dataReductionProxyKey() {
+ return mDataReductionProxyKey;
+ }
+
+ @Override
+ public CronetEngineBuilderImpl setDataReductionProxyOptions(
+ String primaryProxy, String fallbackProxy, String secureProxyCheckUrl) {
+ if (primaryProxy.isEmpty() || fallbackProxy.isEmpty() || secureProxyCheckUrl.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Primary and fallback proxies and check url must be set");
+ }
+ mDataReductionProxyPrimaryProxy = primaryProxy;
+ mDataReductionProxyFallbackProxy = fallbackProxy;
+ mDataReductionProxySecureProxyCheckUrl = secureProxyCheckUrl;
+ return this;
+ }
+
+ String dataReductionProxyPrimaryProxy() {
+ return mDataReductionProxyPrimaryProxy;
+ }
+
+ String dataReductionProxyFallbackProxy() {
+ return mDataReductionProxyFallbackProxy;
+ }
+
+ String dataReductionProxySecureProxyCheckUrl() {
+ return mDataReductionProxySecureProxyCheckUrl;
+ }
+
+ @IntDef({
+ HTTP_CACHE_DISABLED, HTTP_CACHE_IN_MEMORY, HTTP_CACHE_DISK_NO_HTTP, HTTP_CACHE_DISK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface HttpCacheSetting {}
+
+ @Override
+ public CronetEngineBuilderImpl enableHttpCache(@HttpCacheSetting int cacheMode, long maxSize) {
+ if (cacheMode == HTTP_CACHE_DISK || cacheMode == HTTP_CACHE_DISK_NO_HTTP) {
+ if (storagePath() == null) {
+ throw new IllegalArgumentException("Storage path must be set");
+ }
+ } else {
+ if (storagePath() != null) {
+ throw new IllegalArgumentException("Storage path must not be set");
+ }
+ }
+ mDisableCache = (cacheMode == HTTP_CACHE_DISABLED || cacheMode == HTTP_CACHE_DISK_NO_HTTP);
+ mHttpCacheMaxSize = maxSize;
+
+ switch (cacheMode) {
+ case HTTP_CACHE_DISABLED:
+ mHttpCacheMode = HttpCacheType.DISABLED;
+ break;
+ case HTTP_CACHE_DISK_NO_HTTP:
+ case HTTP_CACHE_DISK:
+ mHttpCacheMode = HttpCacheType.DISK;
+ break;
+ case HTTP_CACHE_IN_MEMORY:
+ mHttpCacheMode = HttpCacheType.MEMORY;
+ break;
+ default:
+ throw new IllegalArgumentException("Unknown cache mode");
+ }
+ return this;
+ }
+
+ boolean cacheDisabled() {
+ return mDisableCache;
+ }
+
+ long httpCacheMaxSize() {
+ return mHttpCacheMaxSize;
+ }
+
+ int httpCacheMode() {
+ return mHttpCacheMode;
+ }
+
+ @Override
+ public CronetEngineBuilderImpl addQuicHint(String host, int port, int alternatePort) {
+ if (host.contains("/")) {
+ throw new IllegalArgumentException("Illegal QUIC Hint Host: " + host);
+ }
+ mQuicHints.add(new QuicHint(host, port, alternatePort));
+ return this;
+ }
+
+ List<QuicHint> quicHints() {
+ return mQuicHints;
+ }
+
+ @Override
+ public CronetEngineBuilderImpl addPublicKeyPins(String hostName, Set<byte[]> pinsSha256,
+ boolean includeSubdomains, Date expirationDate) {
+ if (hostName == null) {
+ throw new NullPointerException("The hostname cannot be null");
+ }
+ if (pinsSha256 == null) {
+ throw new NullPointerException("The set of SHA256 pins cannot be null");
+ }
+ if (expirationDate == null) {
+ 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");
+ }
+ hashes.add(pinSha256);
+ }
+ // Add new element to PKP list.
+ mPkps.add(new Pkp(idnHostName, hashes.toArray(new byte[hashes.size()][]), includeSubdomains,
+ expirationDate));
+ return this;
+ }
+
+ /**
+ * Returns list of public key pins.
+ * @return list of public key pins.
+ */
+ List<Pkp> publicKeyPins() {
+ return mPkps;
+ }
+
+ @Override
+ public CronetEngineBuilderImpl enablePublicKeyPinningBypassForLocalTrustAnchors(boolean value) {
+ mPublicKeyPinningBypassForLocalTrustAnchorsEnabled = value;
+ return this;
+ }
+
+ boolean publicKeyPinningBypassForLocalTrustAnchorsEnabled() {
+ return mPublicKeyPinningBypassForLocalTrustAnchorsEnabled;
+ }
+
+ /**
+ * Checks whether a given string represents a valid host name for PKP and converts it
+ * to ASCII Compatible Encoding representation according to RFC 1122, RFC 1123 and
+ * RFC 3490. This method is more restrictive than required by RFC 7469. Thus, a host
+ * that contains digits and the dot character only is considered invalid.
+ *
+ * Note: Currently Cronet doesn't have native implementation of host name validation that
+ * can be used. There is code that parses a provided URL but doesn't ensure its
+ * correctness. The implementation relies on {@code getaddrinfo} function.
+ *
+ * @param hostName host name to check and convert.
+ * @return true if the string is a valid host name.
+ * @throws IllegalArgumentException if the the given string does not represent a valid
+ * hostname.
+ */
+ private static String validateHostNameForPinningAndConvert(String hostName)
+ throws IllegalArgumentException {
+ if (INVALID_PKP_HOST_NAME.matcher(hostName).matches()) {
+ throw new IllegalArgumentException("Hostname " + hostName + " is illegal."
+ + " A hostname should not consist of digits and/or dots only.");
+ }
+ // Workaround for crash, see crbug.com/634914
+ if (hostName.length() > 255) {
+ throw new IllegalArgumentException("Hostname " + hostName + " is too long."
+ + " The name of the host does not comply with RFC 1122 and RFC 1123.");
+ }
+ try {
+ return IDN.toASCII(hostName, IDN.USE_STD3_ASCII_RULES);
+ } catch (IllegalArgumentException ex) {
+ throw new IllegalArgumentException("Hostname " + hostName + " is illegal."
+ + " The name of the host does not comply with RFC 1122 and RFC 1123.");
+ }
+ }
+
+ @Override
+ public CronetEngineBuilderImpl setExperimentalOptions(String options) {
+ mExperimentalOptions = options;
+ return this;
+ }
+
+ public String experimentalOptions() {
+ return mExperimentalOptions;
+ }
+
+ /**
+ * Sets a native MockCertVerifier for testing. See
+ * {@code MockCertVerifier.createMockCertVerifier} for a method that
+ * can be used to create a MockCertVerifier.
+ * @param mockCertVerifier pointer to native MockCertVerifier.
+ * @return the builder to facilitate chaining.
+ */
+ @VisibleForTesting
+ public CronetEngineBuilderImpl setMockCertVerifierForTesting(long mockCertVerifier) {
+ mMockCertVerifier = mockCertVerifier;
+ return this;
+ }
+
+ long mockCertVerifier() {
+ return mMockCertVerifier;
+ }
+
+ /**
+ * @return true if the network quality estimator has been enabled for
+ * this builder.
+ */
+ boolean networkQualityEstimatorEnabled() {
+ return mNetworkQualityEstimatorEnabled;
+ }
+
+ @Override
+ public CronetEngineBuilderImpl setCertVerifierData(String certVerifierData) {
+ mCertVerifierData = certVerifierData;
+ return this;
+ }
+
+ @Override
+ public CronetEngineBuilderImpl enableNetworkQualityEstimator(boolean value) {
+ mNetworkQualityEstimatorEnabled = value;
+ return this;
+ }
+
+ String certVerifierData() {
+ return mCertVerifierData;
+ }
+
+ /**
+ * Returns {@link Context} for builder.
+ *
+ * @return {@link Context} for builder.
+ */
+ Context getContext() {
+ return mContext;
+ }
+
+ @Override
+ public ExperimentalCronetEngine build() {
+ final ClassLoader loader = getClass().getClassLoader();
+ if (getUserAgent() == null) {
+ setUserAgent(getDefaultUserAgent());
+ }
+
+ ExperimentalCronetEngine cronetEngine = null;
+ if (!legacyMode()) {
+ cronetEngine = createNativeCronetEngine(loader);
+ }
+ if (cronetEngine == null) {
+ cronetEngine = createJavaCronetEngine(loader);
+ }
+
+ if (cronetEngine == null) {
+ Log.i(TAG,
+ "Class loader " + loader + " couldn't find any Cronet engine implementation.");
+ } else {
+ Log.i(TAG, loader.toString() + " found Cronet engine implementation "
+ + cronetEngine.getClass() + ". Network stack version "
+ + cronetEngine.getVersionString());
+ // Clear MOCK_CERT_VERIFIER reference if there is any, since
+ // the ownership has been transferred to the engine.
+ mMockCertVerifier = 0;
+ }
+ return cronetEngine;
+ }
+
+ private ExperimentalCronetEngine createNativeCronetEngine(ClassLoader loader) {
+ return createCronetEngine(loader, NATIVE_CRONET_IMPL_CLASS, this);
+ }
+
+ private ExperimentalCronetEngine createJavaCronetEngine(ClassLoader loader) {
+ return createCronetEngine(loader, JAVA_CRONET_IMPL_CLASS, getUserAgent());
+ }
+
+ private ExperimentalCronetEngine createCronetEngine(
+ ClassLoader loader, String name, Object argument) {
+ ExperimentalCronetEngine cronetEngine = null;
+ try {
+ Class<? extends ExperimentalCronetEngine> engineClass =
+ loader.loadClass(name).asSubclass(ExperimentalCronetEngine.class);
+ Constructor<? extends ExperimentalCronetEngine> constructor =
+ engineClass.getConstructor(argument.getClass());
+ cronetEngine = constructor.newInstance(argument);
+ } catch (ClassNotFoundException e) {
+ // Leave as null.
+ Log.i(TAG, "Class loader " + loader + " cannot find Cronet engine implementation: "
+ + name + ". Will try to find an alternative implementation.");
+ } catch (Exception e) {
+ throw new IllegalStateException("Cannot instantiate: " + name, e);
+ }
+ return cronetEngine;
+ }
+}

Powered by Google App Engine
This is Rietveld 408576698