// Copyright 2015 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package android.net.http; import android.annotation.SuppressLint; import android.content.Context; import android.net.Network; import org.chromium.net.ExperimentalCronetEngine; import org.chromium.net.ICronetEngineBuilder; import org.chromium.net.ApiVersion; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandlerFactory; import java.time.Instant; import java.util.Set; import java.util.concurrent.Executor; import javax.net.ssl.HttpsURLConnection; /** * An engine to process {@link UrlRequest}s, which uses the best HTTP stack available on the current * platform. An instance of this class can be created using {@link Builder}. */ // SuppressLint: Making the HttpEngine AutoCloseable indicates to the developers that it's // expected to be used in a try-with-resource clause. This in turn promotes local, narrowly // scoped instances of HttpEngine. That's the exact opposite of how HttpEngine is supposed // to be used - it should live in an application-wide scope and be reused multiple times across // the lifespan of the app. @SuppressLint("NotCloseable") public abstract class HttpEngine { /** * {@hide} */ protected HttpEngine() {} /** * Returns a new {@link Builder} object that facilitates creating a {@link HttpEngine}. * * {@hide} */ @NonNull public static Builder builder(@NonNull Context context) { return new Builder(context); } /** * A builder for {@link HttpEngine}s, which allows runtime configuration of * {@link HttpEngine}. Configuration options are set on the builder and * then {@link #build} is called to create the {@link HttpEngine}. */ // NOTE(kapishnikov): In order to avoid breaking the existing API clients, all future methods // added to this class and other API classes must have default implementation. // SuppressLint: Builder can not be final since ExperimentalHttpEngine.Builder inherit this // Builder. @SuppressLint("StaticFinalBuilder") public static class Builder { /** * Reference to the actual builder implementation. {@hide exclude from JavaDoc}. */ protected final IHttpEngineBuilder mBuilderDelegate; /** * Constructs a {@link Builder} object that facilitates creating a * {@link HttpEngine}. The default configuration enables HTTP/2 and * QUIC, but disables the HTTP cache. * * @param context Android {@link Context}, which is used by {@link Builder} to retrieve the * application context. A reference to only the application context will be kept, so as to * avoid extending the lifetime of {@code context} unnecessarily. */ public Builder(@NonNull Context context) { this(createBuilderDelegate(context)); } /** * Constructs {@link Builder} with a given delegate that provides the actual implementation * of the {@code Builder} methods. This constructor is used only by the internal * implementation. * * @param builderDelegate delegate that provides the actual implementation. * * {@hide} */ @VisibleForTesting public Builder(@NonNull IHttpEngineBuilder builderDelegate) { mBuilderDelegate = builderDelegate; } /** * Constructs a default User-Agent string including the system build version, model and id, * and the HTTP stack version. * * @return User-Agent string. */ // SuppressLint: API to get default user agent that could include system build version, // model, Id, and Cronet version. @NonNull @SuppressLint("GetterOnBuilder") public String getDefaultUserAgent() { return mBuilderDelegate.getDefaultUserAgent(); } /** * Overrides the User-Agent header for all requests. An explicitly set User-Agent header * (set using {@link UrlRequest.Builder#addHeader}) will override a value set using this * function. * * @param userAgent the User-Agent string to use for all requests. * @return the builder to facilitate chaining. */ // SuppressLint: Value is passed to JNI code and maintained by JNI code after build @NonNull @SuppressLint("MissingGetterMatchingBuilder") public Builder setUserAgent(@NonNull String userAgent) { mBuilderDelegate.setUserAgent(userAgent); return this; } /** * Sets directory for HTTP Cache and Cookie Storage. The directory must * exist. *

* NOTE: Do not use the same storage directory with more than one * {@link HttpEngine} at a time. Access to the storage directory does * not support concurrent access by multiple {@link HttpEngine} instances. * * @param value path to existing directory. * @return the builder to facilitate chaining. */ // SuppressLint: Value is passed to JNI code and maintained by JNI code after build @NonNull @SuppressLint("MissingGetterMatchingBuilder") public Builder setStoragePath(@NonNull String value) { mBuilderDelegate.setStoragePath(value); return this; } /** * Sets whether QUIC protocol * is enabled. Defaults to enabled. * * @param value {@code true} to enable QUIC, {@code false} to disable. * @return the builder to facilitate chaining. */ // SuppressLint: Value is passed to JNI code and maintained by JNI code after build @NonNull @SuppressLint("MissingGetterMatchingBuilder") public Builder setEnableQuic(boolean value) { mBuilderDelegate.setEnableQuic(value); return this; } /** * Sets whether HTTP/2 protocol is * enabled. Defaults to enabled. * * @param value {@code true} to enable HTTP/2, {@code false} to disable. * @return the builder to facilitate chaining. */ // SuppressLint: Value is passed to JNI code and maintained by JNI code after build @NonNull @SuppressLint("MissingGetterMatchingBuilder") public Builder setEnableHttp2(boolean value) { mBuilderDelegate.setEnableHttp2(value); return this; } /** * Sets whether Brotli compression is * enabled. If enabled, Brotli will be advertised in Accept-Encoding request headers. * Defaults to disabled. * * @param value {@code true} to enable Brotli, {@code false} to disable. * @return the builder to facilitate chaining. */ // SuppressLint: Value is passed to JNI code and maintained by JNI code after build @NonNull @SuppressLint("MissingGetterMatchingBuilder") public Builder setEnableBrotli(boolean value) { mBuilderDelegate.setEnableBrotli(value); return this; } /** * Setting to disable HTTP cache. Some data may still be temporarily stored in memory. * Passed to {@link #setEnableHttpCache}. */ public static final int HTTP_CACHE_DISABLED = 0; /** * Setting to enable in-memory HTTP cache, including HTTP data. * Passed to {@link #setEnableHttpCache}. */ public static final int HTTP_CACHE_IN_MEMORY = 1; /** * Setting to enable on-disk cache, excluding HTTP data. * {@link #setStoragePath} must be called prior to passing this constant to * {@link #setEnableHttpCache}. */ public static final int HTTP_CACHE_DISK_NO_HTTP = 2; /** * Setting to enable on-disk cache, including HTTP data. * {@link #setStoragePath} must be called prior to passing this constant to * {@link #setEnableHttpCache}. */ public static final int HTTP_CACHE_DISK = 3; /** * Enables or disables caching of HTTP data and other information like QUIC server * information. * * @param cacheMode control location and type of cached data. Must be one of {@link * #HTTP_CACHE_DISABLED HTTP_CACHE_*}. * @param maxSize maximum size in bytes used to cache data (advisory and maybe exceeded at * times). * @return the builder to facilitate chaining. */ // SuppressLint: Value is passed to JNI code and maintained by JNI code after build @NonNull @SuppressLint("MissingGetterMatchingBuilder") public Builder setEnableHttpCache(int cacheMode, long maxSize) { mBuilderDelegate.setEnableHttpCache(cacheMode, maxSize); return this; } /** * Adds hint that {@code host} supports QUIC. * Note that {@link #setEnableHttpCache enableHttpCache} * ({@link #HTTP_CACHE_DISK}) is needed to take advantage of 0-RTT * connection establishment between sessions. * * @param host hostname of the server that supports QUIC. * @param port host of the server that supports QUIC. * @param alternatePort alternate port to use for QUIC. * @return the builder to facilitate chaining. */ // SuppressLint: Value is passed to JNI code and maintained by JNI code after build @NonNull @SuppressLint("MissingGetterMatchingBuilder") public Builder addQuicHint(@NonNull String host, int port, int alternatePort) { mBuilderDelegate.addQuicHint(host, port, alternatePort); return this; } /** * Pins a set of public keys for a given host. By pinning a set of public keys, {@code * pinsSha256}, communication with {@code hostName} is required to authenticate with a * certificate with a public key from the set of pinned ones. An app can pin the public key * of the root certificate, any of the intermediate certificates or the end-entry * certificate. Authentication will fail and secure communication will not be established if * none of the public keys is present in the host's certificate chain, even if the host * attempts to authenticate with a certificate allowed by the device's trusted store of * certificates. * *

Calling this method multiple times with the same host name overrides the previously * set pins for the host. * *

More information about the public key pinning can be found in RFC 7469. * * @param hostName name of the host to which the public keys should be pinned. A host that * consists only of digits and the dot character is treated as invalid. * @param pinsSha256 a set of pins. Each pin is the SHA-256 cryptographic hash of the * DER-encoded ASN.1 representation of the Subject Public Key Info (SPKI) of the host's * X.509 certificate. Use {@link java.security.cert.Certificate#getPublicKey() * Certificate.getPublicKey()} and {@link java.security.Key#getEncoded() Key.getEncoded()} * to obtain DER-encoded ASN.1 representation of the SPKI. Although, the method does not * mandate the presence of the backup pin that can be used if the control of the primary * private key has been lost, it is highly recommended to supply one. * @param includeSubdomains indicates whether the pinning policy should be applied to * subdomains of {@code hostName}. * @param expirationInstant specifies the expiration instant for the pins. * @return the builder to facilitate chaining. * @throws NullPointerException if any of the input parameters are {@code null}. * @throws IllegalArgumentException if the given host name is invalid or {@code pinsSha256} * contains a byte array that does not represent a valid SHA-256 hash. */ // SuppressLint: Value is passed to JNI code and maintained by JNI code after build @NonNull @SuppressLint("MissingGetterMatchingBuilder") public Builder addPublicKeyPins(@NonNull String hostName, @NonNull Set pinsSha256, boolean includeSubdomains, @NonNull Instant expirationInstant) { mBuilderDelegate.addPublicKeyPins( hostName, pinsSha256, includeSubdomains, expirationInstant); return this; } /** * Enables or disables public key pinning bypass for local trust anchors. Disabling the * bypass for local trust anchors is highly discouraged since it may prohibit the app from * communicating with the pinned hosts. E.g., a user may want to send all traffic through an * SSL enabled proxy by changing the device proxy settings and adding the proxy certificate * to the list of local trust anchor. Disabling the bypass will most likely prevent the app * from sending any traffic to the pinned hosts. For more information see 'How does key * pinning interact with local proxies and filters?' at * https://www.chromium.org/Home/chromium-security/security-faq * * @param value {@code true} to enable the bypass, {@code false} to disable. * @return the builder to facilitate chaining. */ // SuppressLint: Value is passed to JNI code and maintained by JNI code after build @NonNull @SuppressLint("MissingGetterMatchingBuilder") public Builder setEnablePublicKeyPinningBypassForLocalTrustAnchors(boolean value) { mBuilderDelegate.setEnablePublicKeyPinningBypassForLocalTrustAnchors(value); return this; } /** * Configures the behavior of the HTTP stack when using QUIC. For more details, see * documentation of {@link QuicOptions} and the individual methods * of {@link QuicOptions.Builder}. * *

Only relevant if {@link #setEnableQuic(boolean)} is enabled. * * @return the builder to facilitate chaining. */ // SuppressLint: Value is passed to JNI code and maintained by JNI code after build @NonNull @SuppressLint("MissingGetterMatchingBuilder") @QuicOptions.Experimental public Builder setQuicOptions(@NonNull QuicOptions quicOptions) { mBuilderDelegate.setQuicOptions(quicOptions); return this; } /** * Configures the behavior of hostname lookup. For more details, see documentation * of {@link DnsOptions} and the individual methods of {@link DnsOptions.Builder}. * *

Only relevant if {@link #setEnableQuic(boolean)} is enabled. * * @return the builder to facilitate chaining. */ // SuppressLint: Value is passed to JNI code and maintained by JNI code after build @NonNull @SuppressLint("MissingGetterMatchingBuilder") @DnsOptions.Experimental public Builder setDnsOptions(@NonNull DnsOptions dnsOptions) { mBuilderDelegate.setDnsOptions(dnsOptions); return this; } /** * Configures the behavior of connection migration. For more details, see documentation * of {@link ConnectionMigrationOptions} and the individual methods of {@link * ConnectionMigrationOptions.Builder}. * *

Only relevant if {@link #setEnableQuic(boolean)} is enabled. * * @return the builder to facilitate chaining. */ // SuppressLint: Value is passed to JNI code and maintained by JNI code after build @NonNull @SuppressLint("MissingGetterMatchingBuilder") @ConnectionMigrationOptions.Experimental public Builder setConnectionMigrationOptions( @NonNull ConnectionMigrationOptions connectionMigrationOptions) { mBuilderDelegate.setConnectionMigrationOptions(connectionMigrationOptions); return this; } /** * Build a {@link HttpEngine} using this builder's configuration. * @return constructed {@link HttpEngine}. */ @NonNull public HttpEngine build() { return mBuilderDelegate.build(); } /** * Creates an implementation of {@link IHttpEngineBuilder} that can be used * to delegate the builder calls to. * * @param context Android Context to use. * @return the created {@link IHttpEngineBuilder}. */ private static IHttpEngineBuilder createBuilderDelegate(Context context) { try { Class cronetClazz = context.getClassLoader().loadClass( "android.net.connectivity.org.chromium.net.impl.NativeCronetEngineBuilderImpl"); Class aospClazz = context.getClassLoader().loadClass( "android.net.http.CronetEngineBuilderWrapper"); ICronetEngineBuilder cronetBuilderImpl = (ICronetEngineBuilder) cronetClazz.getConstructor(Context.class).newInstance(context); IHttpEngineBuilder aospBuilderImpl = (IHttpEngineBuilder) aospClazz.getConstructor(ExperimentalCronetEngine.Builder.class).newInstance(new ExperimentalCronetEngine.Builder(cronetBuilderImpl)); return aospBuilderImpl; } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { throw new IllegalArgumentException(e); } } } /** * @return a human-readable version string of the engine. */ @NonNull public static String getVersionString() { return ApiVersion.getCronetVersion(); } /** * Shuts down the {@link HttpEngine} if there are no active requests, * otherwise throws an exception. * * Cannot be called on network thread - the thread the HTTP stack calls into * Executor on (which is different from the thread the Executor invokes * callbacks on). May block until all the {@link HttpEngine} resources have been cleaned up. */ public abstract void shutdown(); /** * Binds the engine to the specified network. All requests created through this engine * will use the network associated to this handle. If this network disconnects all requests will * fail, the exact error will depend on the stage of request processing when the network * disconnects. * * @param network the network to bind the engine to. Specify {@code null} to unbind. */ public void bindToNetwork(@Nullable Network network) {} /** * Establishes a new connection to the resource specified by the {@link URL} {@code url}. *

* Note: This {@link java.net.HttpURLConnection} implementation is subject to certain * limitations, see {@link #createUrlStreamHandlerFactory} for details. * * @param url URL of resource to connect to. * @return an {@link java.net.HttpURLConnection} instance implemented * by this {@link HttpEngine}. * @throws IOException if an error occurs while opening the connection. */ // SuppressLint since this is for interface parity with j.n.URLConnection @SuppressLint("AndroidUri") @NonNull public abstract URLConnection openConnection( @SuppressLint("AndroidUri") @NonNull URL url) throws IOException; /** * Creates a {@link URLStreamHandlerFactory} to handle HTTP and HTTPS * traffic. An instance of this class can be installed via * {@link URL#setURLStreamHandlerFactory} thus using this {@link HttpEngine} by default for * all requests created via {@link URL#openConnection}. *

* This {@link java.net.HttpURLConnection} implementation does not implement all features * offered by the API: *

*

* While we support and encourages requests using the HTTPS protocol, we don't provide support * for the {@link HttpsURLConnection} API. This lack of support also includes not using certain * HTTPS features provided via {@link HttpsURLConnection}: *

* * @return an {@link URLStreamHandlerFactory} instance implemented by this * {@link HttpEngine}. */ // SuppressLint since this is for interface parity with j.n.URLStreamHandlerFactory @SuppressLint("AndroidUri") @NonNull public abstract URLStreamHandlerFactory createUrlStreamHandlerFactory(); /** * Creates a builder for {@link UrlRequest}. All callbacks for * generated {@link UrlRequest} objects will be invoked on * {@code executor}'s threads. {@code executor} must not run tasks on the * thread calling {@link Executor#execute} to prevent blocking networking * operations and causing exceptions during shutdown. * * @param url URL for the generated requests. * @param executor {@link Executor} on which all callbacks will be invoked. * @param callback callback object that gets invoked on different events. */ @NonNull public abstract UrlRequest.Builder newUrlRequestBuilder( @NonNull String url, @NonNull Executor executor, @NonNull UrlRequest.Callback callback); /** * Creates a builder for {@link BidirectionalStream} objects. All callbacks for * generated {@code BidirectionalStream} objects will be invoked on * {@code executor}. {@code executor} must not run tasks on the * current thread, otherwise the networking operations may block and exceptions * may be thrown at shutdown time. * * @param url URL for the generated streams. * @param executor the {@link Executor} on which {@code callback} methods will be invoked. * @param callback the {@link BidirectionalStream.Callback} object that gets invoked upon * different events occurring. * * @return the created builder. */ @NonNull public abstract BidirectionalStream.Builder newBidirectionalStreamBuilder( @NonNull String url, @NonNull Executor executor, @NonNull BidirectionalStream.Callback callback); }