From 51f6c2793adab2d864b3d2b360000ef8db1d3e92 Mon Sep 17 00:00:00 2001 From: Allan Sandfeld Jensen Date: Mon, 10 Dec 2018 16:19:40 +0100 Subject: BASELINE: Update Chromium to 71.0.3578.93 Change-Id: I6a32086c33670e1b033f8b10e6bf1fd4da1d105d Reviewed-by: Alexandru Croitor --- .../net/test/DummySpnegoAuthenticator.java | 186 +++++++ .../net/test/DummySpnegoAuthenticatorService.java | 22 + .../org/chromium/net/test/EmbeddedTestServer.java | 553 +++++++++++++++++++++ .../chromium/net/test/EmbeddedTestServerImpl.java | 333 +++++++++++++ .../chromium/net/test/EmbeddedTestServerRule.java | 47 ++ .../net/test/EmbeddedTestServerService.java | 19 + .../chromium/net/test/IEmbeddedTestServerImpl.aidl | 3 +- .../org/chromium/net/test/util/CertTestUtil.java | 77 +++ .../test/util/NetworkChangeNotifierTestUtil.java | 26 + .../org/chromium/net/test/util/TestWebServer.java | 437 ++++++++++++++++ .../src/org/chromium/net/test/util/WebServer.java | 506 +++++++++++++++++++ chromium/net/test/android/net_test_jni_onload.cc | 20 + chromium/net/test/android/net_test_jni_onload.h | 18 + .../android/embedded_test_server_android.cc | 5 +- .../android/embedded_test_server_android.h | 4 +- .../embedded_test_server/embedded_test_server.cc | 9 +- .../embedded_test_server/embedded_test_server.h | 4 +- .../embedded_test_server/request_handler_util.cc | 2 + chromium/net/test/run_all_unittests.cc | 5 - .../test/spawned_test_server/base_test_server.cc | 42 +- .../test/spawned_test_server/base_test_server.h | 3 - .../test/spawned_test_server/local_test_server.cc | 15 +- .../spawned_test_server/local_test_server_posix.cc | 6 +- .../remote_test_server_config.cc | 2 +- .../url_request/url_request_slow_download_job.cc | 6 +- 25 files changed, 2293 insertions(+), 57 deletions(-) create mode 100644 chromium/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticator.java create mode 100644 chromium/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticatorService.java create mode 100644 chromium/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServer.java create mode 100644 chromium/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServerImpl.java create mode 100644 chromium/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServerRule.java create mode 100644 chromium/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServerService.java create mode 100644 chromium/net/test/android/javatests/src/org/chromium/net/test/util/CertTestUtil.java create mode 100644 chromium/net/test/android/javatests/src/org/chromium/net/test/util/NetworkChangeNotifierTestUtil.java create mode 100644 chromium/net/test/android/javatests/src/org/chromium/net/test/util/TestWebServer.java create mode 100644 chromium/net/test/android/javatests/src/org/chromium/net/test/util/WebServer.java create mode 100644 chromium/net/test/android/net_test_jni_onload.cc create mode 100644 chromium/net/test/android/net_test_jni_onload.h (limited to 'chromium/net/test') diff --git a/chromium/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticator.java b/chromium/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticator.java new file mode 100644 index 00000000000..f5c82ad6670 --- /dev/null +++ b/chromium/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticator.java @@ -0,0 +1,186 @@ +// Copyright 2015 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.test; + +import android.accounts.AbstractAccountAuthenticator; +import android.accounts.Account; +import android.accounts.AccountAuthenticatorResponse; +import android.accounts.AccountManager; +import android.accounts.AuthenticatorException; +import android.accounts.NetworkErrorException; +import android.accounts.OperationCanceledException; +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; + +import org.chromium.base.ApplicationStatus; +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeClassQualifiedName; +import org.chromium.net.HttpNegotiateConstants; + +import java.io.IOException; + +/** + * Dummy Android authenticator, to test SPNEGO/Keberos support on Android. This is deliberately + * minimal, and is not intended as an example of how to write a real SPNEGO Authenticator. + */ +@JNINamespace("net::android") +public class DummySpnegoAuthenticator extends AbstractAccountAuthenticator { + private static final String ACCOUNT_TYPE = "org.chromium.test.DummySpnegoAuthenticator"; + private static final String ACCOUNT_NAME = "DummySpnegoAccount"; + private static int sResult; + private static String sToken; + private static boolean sCheckArguments; + private static long sNativeDummySpnegoAuthenticator; + private static final int GSS_S_COMPLETE = 0; + private static final int GSS_S_CONTINUE_NEEDED = 1; + private static final int GSS_S_FAILURE = 2; + + /** + * @param context + */ + public DummySpnegoAuthenticator(Context context) { + super(context); + } + + @Override + public Bundle addAccount(AccountAuthenticatorResponse arg0, String accountType, String arg2, + String[] arg3, Bundle arg4) throws NetworkErrorException { + Bundle result = new Bundle(); + result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_REQUEST); + result.putString(AccountManager.KEY_ERROR_MESSAGE, "Can't add new SPNEGO accounts"); + return result; + } + + @Override + public Bundle confirmCredentials(AccountAuthenticatorResponse arg0, Account arg1, Bundle arg2) + throws NetworkErrorException { + Bundle result = new Bundle(); + result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); + return result; + } + + @Override + public Bundle editProperties(AccountAuthenticatorResponse arg0, String arg1) { + return new Bundle(); + } + + @Override + public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, + String authTokenType, Bundle options) throws NetworkErrorException { + long nativeQuery = nativeGetNextQuery(sNativeDummySpnegoAuthenticator); + String incomingToken = options.getString(HttpNegotiateConstants.KEY_INCOMING_AUTH_TOKEN); + nativeCheckGetTokenArguments(nativeQuery, incomingToken); + Bundle result = new Bundle(); + result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); + result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); + result.putString(AccountManager.KEY_AUTHTOKEN, nativeGetTokenToReturn(nativeQuery)); + result.putInt(HttpNegotiateConstants.KEY_SPNEGO_RESULT, + decodeResult(nativeGetResult(nativeQuery))); + return result; + } + + /** + * @param nativeGetResult + * @return + */ + private int decodeResult(int gssApiResult) { + // This only handles the result values currently used in the tests. + switch (gssApiResult) { + case GSS_S_COMPLETE: + case GSS_S_CONTINUE_NEEDED: + return 0; + case GSS_S_FAILURE: + return HttpNegotiateConstants.ERR_MISSING_AUTH_CREDENTIALS; + default: + return HttpNegotiateConstants.ERR_UNEXPECTED; + } + } + + @Override + public String getAuthTokenLabel(String arg0) { + return "Spnego " + arg0; + } + + @Override + public Bundle hasFeatures(AccountAuthenticatorResponse arg0, Account arg1, String[] features) + throws NetworkErrorException { + Bundle result = new Bundle(); + for (String feature : features) { + if (!feature.equals(HttpNegotiateConstants.SPNEGO_FEATURE)) { + result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); + return result; + } + } + result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); + return result; + } + + @Override + public Bundle updateCredentials(AccountAuthenticatorResponse arg0, Account arg1, String arg2, + Bundle arg3) throws NetworkErrorException { + Bundle result = new Bundle(); + result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_REQUEST); + result.putString(AccountManager.KEY_ERROR_MESSAGE, "Can't add new SPNEGO accounts"); + return result; + } + + /** + * Called from tests, sets up the test account, if it doesn't already exist + */ + @CalledByNative + private static void ensureTestAccountExists() { + Activity activity = ApplicationStatus.getLastTrackedFocusedActivity(); + AccountManager am = AccountManager.get(activity); + Account account = new Account(ACCOUNT_NAME, ACCOUNT_TYPE); + am.addAccountExplicitly(account, null, null); + } + + /** + * Called from tests to tidy up test accounts. + */ + @SuppressWarnings("deprecation") + @CalledByNative + private static void removeTestAccounts() { + Activity activity = ApplicationStatus.getLastTrackedFocusedActivity(); + AccountManager am = AccountManager.get(activity); + String features[] = {HttpNegotiateConstants.SPNEGO_FEATURE}; + try { + Account accounts[] = + am.getAccountsByTypeAndFeatures(ACCOUNT_TYPE, features, null, null).getResult(); + for (Account account : accounts) { + // Deprecated, but the replacement not available on Android JB. + am.removeAccount(account, null, null).getResult(); + } + } catch (OperationCanceledException | AuthenticatorException | IOException e) { + // Should never happen. This is tidy-up after the tests. Ignore. + } + } + + @CalledByNative + private static void setNativeAuthenticator(long nativeDummySpnegoAuthenticator) { + sNativeDummySpnegoAuthenticator = nativeDummySpnegoAuthenticator; + } + + /** + * Send the relevant decoded arguments of getAuthToken to C++ for checking by googletest checks + * If the checks fail then the C++ unit test using this authenticator will fail. + * + * @param authTokenType + * @param spn + * @param incomingToken + */ + @NativeClassQualifiedName("DummySpnegoAuthenticator::SecurityContextQuery") + private native void nativeCheckGetTokenArguments(long nativeQuery, String incomingToken); + + @NativeClassQualifiedName("DummySpnegoAuthenticator::SecurityContextQuery") + private native String nativeGetTokenToReturn(long nativeQuery); + + @NativeClassQualifiedName("DummySpnegoAuthenticator::SecurityContextQuery") + private native int nativeGetResult(long nativeQuery); + + private native long nativeGetNextQuery(long nativeDummySpnegoAuthenticator); +} diff --git a/chromium/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticatorService.java b/chromium/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticatorService.java new file mode 100644 index 00000000000..42e4ab39d29 --- /dev/null +++ b/chromium/net/test/android/javatests/src/org/chromium/net/test/DummySpnegoAuthenticatorService.java @@ -0,0 +1,22 @@ +// Copyright 2015 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.test; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +/** + * Authenticator service for testing SPNEGO (Kerberos) support. + */ +public class DummySpnegoAuthenticatorService extends Service { + private static DummySpnegoAuthenticator sAuthenticator; + + @Override + public IBinder onBind(Intent arg0) { + if (sAuthenticator == null) sAuthenticator = new DummySpnegoAuthenticator(this); + return sAuthenticator.getIBinder(); + } +} diff --git a/chromium/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServer.java b/chromium/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServer.java new file mode 100644 index 00000000000..d43f832a4b6 --- /dev/null +++ b/chromium/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServer.java @@ -0,0 +1,553 @@ +// Copyright 2015 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.test; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Environment; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; + +import org.junit.Assert; + +import org.chromium.base.Log; +import org.chromium.base.ThreadUtils; +import org.chromium.net.X509Util; +import org.chromium.net.test.util.CertTestUtil; + +import java.io.File; + +/** A simple file server for java tests. + * + * An example use: + * EmbeddedTestServer s = EmbeddedTestServer.createAndStartServer(context); + * + * // serve requests... + * s.getURL("/foo/bar.txt"); + * + * s.stopAndDestroyServer(); + * + * Note that this runs net::test_server::EmbeddedTestServer in a service in a separate APK. + */ +public class EmbeddedTestServer { + private static final String TAG = "cr_TestServer"; + + private static final String EMBEDDED_TEST_SERVER_SERVICE = + "org.chromium.net.test.EMBEDDED_TEST_SERVER_SERVICE"; + private static final long SERVICE_CONNECTION_WAIT_INTERVAL_MS = 5000; + + private IEmbeddedTestServerImpl mImpl; + private ServiceConnection mConn = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mImplMonitor) { + mImpl = IEmbeddedTestServerImpl.Stub.asInterface(service); + mImplMonitor.notify(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + synchronized (mImplMonitor) { + mImpl = null; + mImplMonitor.notify(); + } + } + }; + + private Context mContext; + private final Object mImplMonitor = new Object(); + + // Whether the server should use HTTP or HTTPS. + public enum ServerHTTPSSetting { + USE_HTTP, + USE_HTTPS, + } + + /** + * Exception class raised on failure in the EmbeddedTestServer. + */ + public static final class EmbeddedTestServerFailure extends Error { + public EmbeddedTestServerFailure(String errorDesc) { + super(errorDesc); + } + + public EmbeddedTestServerFailure(String errorDesc, Throwable cause) { + super(errorDesc, cause); + } + } + + /** + * Connection listener class, to be notified of new connections and sockets reads. + * + * Notifications are asynchronous and delivered to the UI thread. + */ + public static class ConnectionListener { + private final IConnectionListener mListener = new IConnectionListener.Stub() { + @Override + public void acceptedSocket(final long socketId) { + ThreadUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + ConnectionListener.this.acceptedSocket(socketId); + } + }); + } + + @Override + public void readFromSocket(final long socketId) { + ThreadUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + ConnectionListener.this.readFromSocket(socketId); + } + }); + } + }; + + /** + * A new socket connection has been opened on the server. + * + * @param socketId Socket unique identifier. Unique as long as the socket stays open. + */ + public void acceptedSocket(long socketId) {} + + /** + * Data has been read from a socket. + * + * @param socketId Socket unique identifier. Unique as long as the socket stays open. + */ + public void readFromSocket(long socketId) {} + + private IConnectionListener getListener() { + return mListener; + } + } + + /** Bind the service that will run the native server object. + * + * @param context The context to use to bind the service. This will also be used to unbind + * the service at server destruction time. + * @param httpsSetting Whether the server should use HTTPS. + */ + public void initializeNative(Context context, ServerHTTPSSetting httpsSetting) + throws InterruptedException { + mContext = context; + + Intent intent = new Intent(EMBEDDED_TEST_SERVER_SERVICE); + setIntentClassName(intent); + if (!mContext.bindService(intent, mConn, Context.BIND_AUTO_CREATE)) { + throw new EmbeddedTestServerFailure( + "Unable to bind to the EmbeddedTestServer service."); + } + synchronized (mImplMonitor) { + Log.i(TAG, "Waiting for EmbeddedTestServer service connection."); + while (mImpl == null) { + mImplMonitor.wait(SERVICE_CONNECTION_WAIT_INTERVAL_MS); + Log.i(TAG, "Still waiting for EmbeddedTestServer service connection."); + } + Log.i(TAG, "EmbeddedTestServer service connected."); + boolean initialized = false; + try { + initialized = mImpl.initializeNative(httpsSetting == ServerHTTPSSetting.USE_HTTPS); + } catch (RemoteException e) { + Log.e(TAG, "Failed to initialize native server.", e); + initialized = false; + } + + if (!initialized) { + throw new EmbeddedTestServerFailure("Failed to initialize native server."); + } + + if (httpsSetting == ServerHTTPSSetting.USE_HTTPS) { + try { + String rootCertPemPath = mImpl.getRootCertPemPath(); + X509Util.addTestRootCertificate(CertTestUtil.pemToDer(rootCertPemPath)); + } catch (Exception e) { + throw new EmbeddedTestServerFailure( + "Failed to install root certificate from native server.", e); + } + } + } + } + + /** Set intent package and class name that will pass to the service. + * + * @param intent The intent to use to pass into the service. + */ + protected void setIntentClassName(Intent intent) { + intent.setClassName( + "org.chromium.net.test.support", "org.chromium.net.test.EmbeddedTestServerService"); + } + + /** Add the default handlers and serve files from the provided directory relative to the + * external storage directory. + * + * @param directory The directory from which files should be served relative to the external + * storage directory. + */ + public void addDefaultHandlers(File directory) { + addDefaultHandlers(directory.getPath()); + } + + /** Add the default handlers and serve files from the provided directory relative to the + * external storage directory. + * + * @param directoryPath The path of the directory from which files should be served relative + * to the external storage directory. + */ + public void addDefaultHandlers(String directoryPath) { + try { + synchronized (mImplMonitor) { + checkServiceLocked(); + mImpl.addDefaultHandlers(directoryPath); + } + } catch (RemoteException e) { + throw new EmbeddedTestServerFailure( + "Failed to add default handlers and start serving files from " + directoryPath + + ": " + e.toString()); + } + } + + /** Configure the server to use a particular type of SSL certificate. + * + * @param serverCertificate The type of certificate the server should use. + */ + public void setSSLConfig(int serverCertificate) { + try { + synchronized (mImplMonitor) { + checkServiceLocked(); + mImpl.setSSLConfig(serverCertificate); + } + } catch (RemoteException e) { + throw new EmbeddedTestServerFailure( + "Failed to set server certificate: " + e.toString()); + } + } + + /** Serve files from the provided directory. + * + * @param directory The directory from which files should be served. + */ + public void serveFilesFromDirectory(File directory) { + serveFilesFromDirectory(directory.getPath()); + } + + /** Serve files from the provided directory. + * + * @param directoryPath The path of the directory from which files should be served. + */ + public void serveFilesFromDirectory(String directoryPath) { + try { + synchronized (mImplMonitor) { + checkServiceLocked(); + mImpl.serveFilesFromDirectory(directoryPath); + } + } catch (RemoteException e) { + throw new EmbeddedTestServerFailure( + "Failed to start serving files from " + directoryPath + ": " + e.toString()); + } + } + + /** + * Sets a connection listener. Must be called after the server has been initialized, but + * before calling {@link start()}. + * + * @param listener The listener to set. + */ + public void setConnectionListener(ConnectionListener listener) { + try { + synchronized (mImplMonitor) { + checkServiceLocked(); + mImpl.setConnectionListener(listener.getListener()); + } + } catch (RemoteException e) { + throw new EmbeddedTestServerFailure("Cannot set the listener"); + } + } + + private void checkServiceLocked() { + if (mImpl == null) { + throw new EmbeddedTestServerFailure("Service disconnected."); + } + } + + /** Starts the server with an automatically selected port. + * + * Note that this should be called after handlers are set up, including any relevant calls + * serveFilesFromDirectory. + * + * @return Whether the server was successfully initialized. + */ + public boolean start() { + return start(0); + } + + /** Starts the server with the specified port. + * + * Note that this should be called after handlers are set up, including any relevant calls + * serveFilesFromDirectory. + * + * @param port The port to use for the server, 0 to auto-select an unused port. + * + * @return Whether the server was successfully initialized. + */ + public boolean start(int port) { + try { + synchronized (mImplMonitor) { + checkServiceLocked(); + return mImpl.start(port); + } + } catch (RemoteException e) { + throw new EmbeddedTestServerFailure("Failed to start server.", e); + } + } + + /** Create and initialize a server that serves files from the provided directory. + * + * This handles native object initialization, server configuration, and server initialization. + * On returning, the server is ready for use. + * + * @param context The context in which the server will run. + * @param directory The directory from which files should be served. This must be + * Environment.getExternalStorageDirectory(). + * @return The created server. + */ + public static EmbeddedTestServer createAndStartFileServer(Context context, File directory) + throws InterruptedException { + // TODO(jbudorick): Update all callers to use createAndStartServer() directly. + if (!directory.equals(Environment.getExternalStorageDirectory())) { + throw new IllegalArgumentException("Expected directory to be ExternalStorageDirectory"); + } + return createAndStartServer(context); + } + + /** Create and initialize a server with the default handlers. + * + * This handles native object initialization, server configuration, and server initialization. + * On returning, the server is ready for use. + * + * @param context The context in which the server will run. + * @return The created server. + */ + public static EmbeddedTestServer createAndStartDefaultServer(Context context) + throws InterruptedException { + // TODO(pkotwicz): Update all callers to use createAndStartServer() directly. + return createAndStartServer(context); + } + + /** Create and initialize a server with the default handlers. + * + * This handles native object initialization, server configuration, and server initialization. + * On returning, the server is ready for use. + * + * @param context The context in which the server will run. + * @return The created server. + */ + public static EmbeddedTestServer createAndStartServer(Context context) + throws InterruptedException { + return createAndStartServerWithPort(context, 0); + } + + /** Create and initialize a server with the default handlers and specified port. + * + * This handles native object initialization, server configuration, and server initialization. + * On returning, the server is ready for use. + * + * @param context The context in which the server will run. + * @param port The port to use for the server, 0 to auto-select an unused port. + * @return The created server. + */ + public static EmbeddedTestServer createAndStartServerWithPort(Context context, int port) + throws InterruptedException { + Assert.assertNotEquals("EmbeddedTestServer should not be created on UiThread, " + + "the instantiation will hang forever waiting for tasks to post to UI thread", + Looper.getMainLooper(), Looper.myLooper()); + EmbeddedTestServer server = new EmbeddedTestServer(); + return initializeAndStartServer(server, context, port); + } + + /** Create and initialize an HTTPS server with the default handlers. + * + * This handles native object initialization, server configuration, and server initialization. + * On returning, the server is ready for use. + * + * @param context The context in which the server will run. + * @param serverCertificate The certificate option that the server will use. + * @return The created server. + */ + public static EmbeddedTestServer createAndStartHTTPSServer( + Context context, int serverCertificate) throws InterruptedException { + return createAndStartHTTPSServerWithPort(context, serverCertificate, 0 /* port */); + } + + /** Create and initialize an HTTPS server with the default handlers and specified port. + * + * This handles native object initialization, server configuration, and server initialization. + * On returning, the server is ready for use. + * + * @param context The context in which the server will run. + * @param serverCertificate The certificate option that the server will use. + * @param port The port to use for the server, 0 to auto-select an unused port. + * @return The created server. + */ + public static EmbeddedTestServer createAndStartHTTPSServerWithPort( + Context context, int serverCertificate, int port) throws InterruptedException { + Assert.assertNotEquals("EmbeddedTestServer should not be created on UiThread, " + + "the instantiation will hang forever waiting for tasks" + + " to post to UI thread", + Looper.getMainLooper(), Looper.myLooper()); + EmbeddedTestServer server = new EmbeddedTestServer(); + return initializeAndStartHTTPSServer(server, context, serverCertificate, port); + } + + /** Initialize a server with the default handlers. + * + * This handles native object initialization, server configuration, and server initialization. + * On returning, the server is ready for use. + * + * @param server The server instance that will be initialized. + * @param context The context in which the server will run. + * @param port The port to use for the server, 0 to auto-select an unused port. + * @return The created server. + */ + public static T initializeAndStartServer( + T server, Context context, int port) throws InterruptedException { + server.initializeNative(context, ServerHTTPSSetting.USE_HTTP); + server.addDefaultHandlers(""); + if (!server.start(port)) { + throw new EmbeddedTestServerFailure("Failed to start serving using default handlers."); + } + return server; + } + + /** Initialize a server with the default handlers that uses HTTPS with the given certificate + * option. + * + * This handles native object initialization, server configuration, and server initialization. + * On returning, the server is ready for use. + * + * @param server The server instance that will be initialized. + * @param context The context in which the server will run. + * @param serverCertificate The certificate option that the server will use. + * @param port The port to use for the server. + * @return The created server. + */ + public static T initializeAndStartHTTPSServer(T server, + Context context, int serverCertificate, int port) throws InterruptedException { + server.initializeNative(context, ServerHTTPSSetting.USE_HTTPS); + server.addDefaultHandlers(""); + server.setSSLConfig(serverCertificate); + if (!server.start(port)) { + throw new EmbeddedTestServerFailure("Failed to start serving using default handlers."); + } + return server; + } + + /** Get the full URL for the given relative URL. + * + * @param relativeUrl The relative URL for which a full URL will be obtained. + * @return The URL as a String. + */ + public String getURL(String relativeUrl) { + try { + synchronized (mImplMonitor) { + checkServiceLocked(); + return mImpl.getURL(relativeUrl); + } + } catch (RemoteException e) { + throw new EmbeddedTestServerFailure("Failed to get URL for " + relativeUrl, e); + } + } + + /** Get the full URL for the given relative URL. Similar to the above method but uses the given + * hostname instead of 127.0.0.1. The hostname should be resolved to 127.0.0.1. + * + * @param hostName The host name which should be used. + * @param relativeUrl The relative URL for which a full URL should be returned. + * @return The URL as a String. + */ + public String getURLWithHostName(String hostname, String relativeUrl) { + try { + synchronized (mImplMonitor) { + checkServiceLocked(); + return mImpl.getURLWithHostName(hostname, relativeUrl); + } + } catch (RemoteException e) { + throw new EmbeddedTestServerFailure( + "Failed to get URL for " + hostname + " and " + relativeUrl, e); + } + } + + /** Get the full URLs for the given relative URLs. + * + * @see #getURL(String) + * + * @param relativeUrls The relative URLs for which full URLs will be obtained. + * @return The URLs as a String array. + */ + public String[] getURLs(String... relativeUrls) { + String[] absoluteUrls = new String[relativeUrls.length]; + + for (int i = 0; i < relativeUrls.length; ++i) absoluteUrls[i] = getURL(relativeUrls[i]); + + return absoluteUrls; + } + + /** Shutdown the server. + * + * @return Whether the server was successfully shut down. + */ + public boolean shutdownAndWaitUntilComplete() { + try { + synchronized (mImplMonitor) { + checkServiceLocked(); + return mImpl.shutdownAndWaitUntilComplete(); + } + } catch (RemoteException e) { + throw new EmbeddedTestServerFailure("Failed to shut down.", e); + } + } + + /** Destroy the native EmbeddedTestServer object. */ + public void destroy() { + try { + synchronized (mImplMonitor) { + checkServiceLocked(); + mImpl.destroy(); + } + } catch (RemoteException e) { + throw new EmbeddedTestServerFailure("Failed to destroy native server.", e); + } finally { + mContext.unbindService(mConn); + } + } + + /** Stop and destroy the server. + * + * This handles stopping the server and destroying the native object. + */ + public void stopAndDestroyServer() { + if (!shutdownAndWaitUntilComplete()) { + throw new EmbeddedTestServerFailure("Failed to stop server."); + } + destroy(); + } + + /** Get the path of the PEM file of the root cert. */ + public String getRootCertPemPath() { + try { + synchronized (mImplMonitor) { + checkServiceLocked(); + return mImpl.getRootCertPemPath(); + } + } catch (RemoteException e) { + throw new EmbeddedTestServerFailure("Failed to get root cert's path", e); + } + } +} diff --git a/chromium/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServerImpl.java b/chromium/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServerImpl.java new file mode 100644 index 00000000000..07932f93e88 --- /dev/null +++ b/chromium/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServerImpl.java @@ -0,0 +1,333 @@ +// Copyright 2015 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.test; + +import android.content.Context; +import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.RemoteException; + +import org.chromium.base.ContextUtils; +import org.chromium.base.Log; +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.library_loader.LibraryLoader; +import org.chromium.base.library_loader.LibraryProcessType; +import org.chromium.base.library_loader.ProcessInitException; +import org.chromium.base.test.util.UrlUtils; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Java bindings for running a net::test_server::EmbeddedTestServer. + * + * This should not be used directly. Use {@link EmbeddedTestServer} instead. + */ +@JNINamespace("net::test_server") +public class EmbeddedTestServerImpl extends IEmbeddedTestServerImpl.Stub { + private static final String TAG = "cr_TestServer"; + + private static AtomicInteger sCount = new AtomicInteger(); + + private final Context mContext; + private Handler mHandler; + private HandlerThread mHandlerThread; + private long mNativeEmbeddedTestServer; + private IConnectionListener mConnectionListener; + + /** Create an uninitialized EmbeddedTestServer. */ + public EmbeddedTestServerImpl(Context context) { + mContext = context; + } + + private V runOnHandlerThread(Callable c) { + FutureTask t = new FutureTask<>(c); + mHandler.post(t); + try { + return t.get(); + } catch (ExecutionException e) { + Log.e(TAG, "Exception raised from native EmbeddedTestServer", e); + } catch (InterruptedException e) { + Log.e(TAG, "Interrupted while waiting for native EmbeddedTestServer", e); + } + return null; + } + + /** Initialize the native EmbeddedTestServer object. + * + * @param https True if the server should use HTTPS, and false otherwise. + * @return Whether the native object was successfully initialized. + */ + @Override + public boolean initializeNative(final boolean https) { + // This is necessary as EmbeddedTestServerImpl is in a different process than the tests + // using it, so it needs to initialize its own application context. + ContextUtils.initApplicationContext(mContext.getApplicationContext()); + try { + LibraryLoader.getInstance().ensureInitialized(LibraryProcessType.PROCESS_BROWSER); + } catch (ProcessInitException e) { + Log.e(TAG, "Failed to load native libraries.", e); + return false; + } + + mHandlerThread = new HandlerThread("EmbeddedTestServer" + sCount.getAndIncrement()); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + + runOnHandlerThread(new Callable() { + @Override + public Void call() { + if (mNativeEmbeddedTestServer == 0) { + nativeInit(UrlUtils.getIsolatedTestRoot(), https); + } + assert mNativeEmbeddedTestServer != 0; + return null; + } + }); + return true; + } + + /** Starts the server. + * + * Note that this should be called after handlers are set up, including any relevant calls + * serveFilesFromDirectory. + * + * @param port The port to use for the server, 0 to auto-select an unused port. + * + * @return Whether the server was successfully started. + */ + @Override + public boolean start(int port) { + return runOnHandlerThread(new Callable() { + @Override + public Boolean call() { + return nativeStart(mNativeEmbeddedTestServer, port); + } + }); + } + + /** Returns the path to a PEM file containing the server's root certificate. + * + * @return The path to a PEM file containing the server's root certificate. + */ + @Override + public String getRootCertPemPath() { + return runOnHandlerThread(new Callable() { + @Override + public String call() { + return nativeGetRootCertPemPath(mNativeEmbeddedTestServer); + } + }); + } + + /** Add the default handlers and serve files from the provided directory relative to the + * external storage directory. + * + * @param directoryPath The path of the directory from which files should be served, relative + * to the external storage directory. + */ + @Override + public void addDefaultHandlers(final String directoryPath) { + runOnHandlerThread(new Callable() { + @Override + public Void call() { + nativeAddDefaultHandlers(mNativeEmbeddedTestServer, directoryPath); + return null; + } + }); + } + + /** Configure the server to use a particular type of SSL certificate. + * + * @param serverCertificate The type of certificate the server should use. + */ + @Override + public void setSSLConfig(final int serverCertificate) { + runOnHandlerThread(new Callable() { + @Override + public Void call() { + nativeSetSSLConfig(mNativeEmbeddedTestServer, serverCertificate); + return null; + } + }); + } + + /** Register multiple request handlers. + * Handlers must be registered before starting the server. + * + * @param handler The pointer of handler to be registered. + */ + public void registerRequestHandler(final long handler) { + runOnHandlerThread(new Callable() { + @Override + public Void call() { + nativeRegisterRequestHandler(mNativeEmbeddedTestServer, handler); + return null; + } + }); + } + + /** Serve files from the provided directory. + * + * @param directoryPath The path of the directory from which files should be served. + */ + @Override + public void serveFilesFromDirectory(final String directoryPath) { + runOnHandlerThread(new Callable() { + @Override + public Void call() { + nativeServeFilesFromDirectory(mNativeEmbeddedTestServer, directoryPath); + return null; + } + }); + } + + /** Sets a connection listener to be notified of new connections and socket reads. + * + * Must be done before starting the server. Setting a new one erases the previous one. + * + * @param listener Listener to notify. + */ + @Override + public void setConnectionListener(final IConnectionListener listener) { + runOnHandlerThread(new Callable() { + @Override + public Void call() { + mConnectionListener = listener; + return null; + } + }); + } + + /** Get the full URL for the given relative URL. + * + * @param relativeUrl The relative URL for which a full URL should be returned. + * @return The URL as a String. + */ + @Override + public String getURL(final String relativeUrl) { + return runOnHandlerThread(new Callable() { + @Override + public String call() { + return nativeGetURL(mNativeEmbeddedTestServer, relativeUrl); + } + }); + } + + /** Get the full URL for the given relative URL. Similar to the above method but uses the given + * hostname instead of 127.0.0.1. The hostname should be resolved to 127.0.0.1. + * + * @param hostName The host name which should be used. + * @param relativeUrl The relative URL for which a full URL should be returned. + * @return The URL as a String. + */ + @Override + public String getURLWithHostName(final String hostName, final String relativeUrl) { + return runOnHandlerThread(new Callable() { + @Override + public String call() { + return nativeGetURLWithHostName(mNativeEmbeddedTestServer, hostName, relativeUrl); + } + }); + } + + /** Shut down the server. + * + * @return Whether the server was successfully shut down. + */ + @Override + public boolean shutdownAndWaitUntilComplete() { + return runOnHandlerThread(new Callable() { + @Override + public Boolean call() { + return nativeShutdownAndWaitUntilComplete(mNativeEmbeddedTestServer); + } + }); + } + + /** Destroy the native EmbeddedTestServer object. */ + @Override + public void destroy() { + runOnHandlerThread(new Callable() { + @Override + public Void call() { + assert mNativeEmbeddedTestServer != 0; + nativeDestroy(mNativeEmbeddedTestServer); + assert mNativeEmbeddedTestServer == 0; + return null; + } + }); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + mHandlerThread.quitSafely(); + } else { + runOnHandlerThread(new Callable() { + @Override + public Void call() { + mHandlerThread.quit(); + return null; + } + }); + } + + try { + mHandlerThread.join(); + } catch (InterruptedException e) { + } + } + + @CalledByNative + private void acceptedSocket(long socketId) { + if (mConnectionListener == null) return; + try { + mConnectionListener.acceptedSocket(socketId); + } catch (RemoteException e) { + // Callback, ignore exception. + } + } + + @CalledByNative + private void readFromSocket(long socketId) { + if (mConnectionListener == null) return; + try { + mConnectionListener.readFromSocket(socketId); + } catch (RemoteException e) { + // Callback, ignore exception. + } + } + + @CalledByNative + private void setNativePtr(long nativePtr) { + assert mNativeEmbeddedTestServer == 0; + mNativeEmbeddedTestServer = nativePtr; + } + + @CalledByNative + private void clearNativePtr() { + assert mNativeEmbeddedTestServer != 0; + mNativeEmbeddedTestServer = 0; + } + + private native void nativeInit(String testDataDir, boolean https); + private native void nativeDestroy(long nativeEmbeddedTestServerAndroid); + private native boolean nativeStart(long nativeEmbeddedTestServerAndroid, int port); + private native String nativeGetRootCertPemPath(long nativeEmbeddedTestServerAndroid); + private native boolean nativeShutdownAndWaitUntilComplete(long nativeEmbeddedTestServerAndroid); + private native void nativeAddDefaultHandlers( + long nativeEmbeddedTestServerAndroid, String directoryPath); + private native void nativeSetSSLConfig( + long nativeEmbeddedTestServerAndroid, int serverCertificate); + private native void nativeRegisterRequestHandler( + long nativeEmbeddedTestServerAndroid, long handler); + private native String nativeGetURL(long nativeEmbeddedTestServerAndroid, String relativeUrl); + private native String nativeGetURLWithHostName( + long nativeEmbeddedTestServerAndroid, String hostName, String relativeUrl); + private native void nativeServeFilesFromDirectory( + long nativeEmbeddedTestServerAndroid, String directoryPath); +} diff --git a/chromium/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServerRule.java b/chromium/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServerRule.java new file mode 100644 index 00000000000..6d20871c1a5 --- /dev/null +++ b/chromium/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServerRule.java @@ -0,0 +1,47 @@ +// Copyright 2017 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.test; + +import android.support.test.InstrumentationRegistry; + +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; + +/** + * Junit4 rule for starting embedded test server before a test starts, and shutting it down when it + * finishes. + */ +public class EmbeddedTestServerRule extends TestWatcher { + EmbeddedTestServer mServer; + + @Override + protected void starting(Description description) { + try { + mServer = EmbeddedTestServer.createAndStartServer(InstrumentationRegistry.getContext()); + } catch (InterruptedException e) { + throw new EmbeddedTestServer.EmbeddedTestServerFailure("Test server didn't start"); + } + super.starting(description); + } + + @Override + protected void finished(Description description) { + super.finished(description); + mServer.stopAndDestroyServer(); + } + + /** + * Get the test server. + * + * @return the test server. + */ + public EmbeddedTestServer getServer() { + return mServer; + } + + public String getOrigin() { + return mServer.getURL("/"); + } +} diff --git a/chromium/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServerService.java b/chromium/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServerService.java new file mode 100644 index 00000000000..1390b8c0cf7 --- /dev/null +++ b/chromium/net/test/android/javatests/src/org/chromium/net/test/EmbeddedTestServerService.java @@ -0,0 +1,19 @@ +// Copyright 2015 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.test; + +import android.app.Service; +import android.content.Intent; +import android.os.IBinder; + +/** + * A {@link android.app.Service} that creates a new {@link EmbeddedTestServer} when bound. + */ +public class EmbeddedTestServerService extends Service { + @Override + public IBinder onBind(Intent intent) { + return new EmbeddedTestServerImpl(this); + } +} diff --git a/chromium/net/test/android/javatests/src/org/chromium/net/test/IEmbeddedTestServerImpl.aidl b/chromium/net/test/android/javatests/src/org/chromium/net/test/IEmbeddedTestServerImpl.aidl index 13372db9c10..6112b99eb32 100644 --- a/chromium/net/test/android/javatests/src/org/chromium/net/test/IEmbeddedTestServerImpl.aidl +++ b/chromium/net/test/android/javatests/src/org/chromium/net/test/IEmbeddedTestServerImpl.aidl @@ -13,9 +13,10 @@ interface IEmbeddedTestServerImpl { /** Start the server. * + * @param port The port to use for the server, 0 to auto-select an unused port. * @return Whether the server was successfully started. */ - boolean start(); + boolean start(int port); /** Get the path of the server's root certificate. * diff --git a/chromium/net/test/android/javatests/src/org/chromium/net/test/util/CertTestUtil.java b/chromium/net/test/android/javatests/src/org/chromium/net/test/util/CertTestUtil.java new file mode 100644 index 00000000000..9994da921ff --- /dev/null +++ b/chromium/net/test/android/javatests/src/org/chromium/net/test/util/CertTestUtil.java @@ -0,0 +1,77 @@ +// Copyright 2015 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.test.util; + +import android.util.Base64; + +import org.chromium.base.test.util.UrlUtils; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; + +/** + * Certificate related utility methods. + */ +public class CertTestUtil { + /** + * The location of the directory that contains certificates for testing. + */ + public static final String CERTS_DIRECTORY = + UrlUtils.getIsolatedTestFilePath("net/data/ssl/certificates/"); + + private static final String BEGIN_MARKER = "-----BEGIN CERTIFICATE-----"; + private static final String END_MARKER = "-----END CERTIFICATE-----"; + + private CertTestUtil() {} + + /** + * Converts a PEM formatted cert in a given file to the binary DER format. + * + * @param pemPathname the location of the certificate to convert. + * @return array of bytes that represent the certificate in DER format. + * @throws IOException if the file cannot be read. + */ + public static byte[] pemToDer(String pemPathname) throws IOException { + BufferedReader reader = new BufferedReader(new FileReader(pemPathname)); + StringBuilder builder = new StringBuilder(); + + // Skip past leading junk lines, if any. + String line = reader.readLine(); + while (line != null && !line.contains(BEGIN_MARKER)) line = reader.readLine(); + + // Then skip the BEGIN_MARKER itself, if present. + while (line != null && line.contains(BEGIN_MARKER)) line = reader.readLine(); + + // Now gather the data lines into the builder. + while (line != null && !line.contains(END_MARKER)) { + builder.append(line.trim()); + line = reader.readLine(); + } + + reader.close(); + return Base64.decode(builder.toString(), Base64.DEFAULT); + } + + /** + * Returns SHA256 hash of the public key of a given certificate. + * + * @param cert the cert that should be used to retrieve the public key from. + * @return SHA256 hash of the public key. + */ + public static byte[] getPublicKeySha256(Certificate cert) { + try { + byte[] publicKey = cert.getPublicKey().getEncoded(); + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + return digest.digest(publicKey); + } catch (NoSuchAlgorithmException ex) { + // This exception should never happen since SHA-256 is known algorithm + throw new RuntimeException(ex); + } + } +} diff --git a/chromium/net/test/android/javatests/src/org/chromium/net/test/util/NetworkChangeNotifierTestUtil.java b/chromium/net/test/android/javatests/src/org/chromium/net/test/util/NetworkChangeNotifierTestUtil.java new file mode 100644 index 00000000000..3a03f1a0782 --- /dev/null +++ b/chromium/net/test/android/javatests/src/org/chromium/net/test/util/NetworkChangeNotifierTestUtil.java @@ -0,0 +1,26 @@ +// Copyright 2015 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.test.util; + +import org.chromium.base.ThreadUtils; + +import java.util.concurrent.FutureTask; + +/** + * A utility class useful for testing NetworkChangeNotifier. + */ +public class NetworkChangeNotifierTestUtil { + /** + * Flushes UI thread task queue. + */ + public static void flushUiThreadTaskQueue() throws Exception { + FutureTask task = new FutureTask(new Runnable() { + @Override + public void run() {} + }, null); + ThreadUtils.postOnUiThread(task); + task.get(); + } +} diff --git a/chromium/net/test/android/javatests/src/org/chromium/net/test/util/TestWebServer.java b/chromium/net/test/android/javatests/src/org/chromium/net/test/util/TestWebServer.java new file mode 100644 index 00000000000..58394a75b68 --- /dev/null +++ b/chromium/net/test/android/javatests/src/org/chromium/net/test/util/TestWebServer.java @@ -0,0 +1,437 @@ +// Copyright 2012 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.test.util; + +import android.util.Base64; +import android.util.Log; +import android.util.Pair; + +import org.chromium.base.ApiCompatibilityUtils; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintStream; +import java.net.URI; +import java.nio.charset.Charset; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * Simple http test server for testing. + * + * Extends WebServer with the ability to map requests to prepared responses. + */ +public class TestWebServer extends WebServer { + private static final String TAG = "TestWebServer"; + + private static class Response { + final byte[] mResponseData; + final List> mResponseHeaders; + final boolean mIsRedirect; + final Runnable mResponseAction; + final boolean mIsNotFound; + final boolean mIsNoContent; + final boolean mForWebSocket; + final boolean mIsEmptyResponse; + + Response(byte[] responseData, List> responseHeaders, + boolean isRedirect, boolean isNotFound, boolean isNoContent, boolean forWebSocket, + boolean isEmptyResponse, Runnable responseAction) { + mIsRedirect = isRedirect; + mIsNotFound = isNotFound; + mIsNoContent = isNoContent; + mForWebSocket = forWebSocket; + mIsEmptyResponse = isEmptyResponse; + mResponseData = responseData; + mResponseHeaders = responseHeaders == null ? new ArrayList>() + : responseHeaders; + mResponseAction = responseAction; + } + } + + // The Maps below are modified on both the client thread and the internal server thread, so + // need to use a lock when accessing them. + private final Object mLock = new Object(); + private final Map mResponseMap = new HashMap(); + private final Map mResponseCountMap = new HashMap(); + private final Map mLastRequestMap = new HashMap(); + + /** + * Create and start a local HTTP server instance. + * @param port Port number the server must use, or 0 to automatically choose a free port. + * @param ssl True if the server should be using secure sockets. + * @throws Exception + */ + private TestWebServer(int port, boolean ssl) throws Exception { + super(port, ssl); + setRequestHandler(new Handler()); + } + + private class Handler implements WebServer.RequestHandler { + @Override + public void handleRequest(WebServer.HTTPRequest request, OutputStream stream) { + WebServerPrintStream printStream = new WebServerPrintStream(stream); + try { + outputResponse(request, printStream); + } catch (NoSuchAlgorithmException ignore) { + } catch (IOException e) { + Log.w(TAG, e); + } finally { + printStream.close(); + } + } + } + + public static TestWebServer start(int port) throws Exception { + return new TestWebServer(port, false); + } + + public static TestWebServer start() throws Exception { + return start(0); + } + + public static TestWebServer startSsl(int port) throws Exception { + return new TestWebServer(port, true); + } + + public static TestWebServer startSsl() throws Exception { + return startSsl(0); + } + + private static final int RESPONSE_STATUS_NORMAL = 0; + private static final int RESPONSE_STATUS_MOVED_TEMPORARILY = 1; + private static final int RESPONSE_STATUS_NOT_FOUND = 2; + private static final int RESPONSE_STATUS_NO_CONTENT = 3; + private static final int RESPONSE_STATUS_FOR_WEBSOCKET = 4; + private static final int RESPONSE_STATUS_EMPTY_RESPONSE = 5; + + private String setResponseInternal(String requestPath, byte[] responseData, + List> responseHeaders, Runnable responseAction, int status) { + final boolean isRedirect = (status == RESPONSE_STATUS_MOVED_TEMPORARILY); + final boolean isNotFound = (status == RESPONSE_STATUS_NOT_FOUND); + final boolean isNoContent = (status == RESPONSE_STATUS_NO_CONTENT); + final boolean forWebSocket = (status == RESPONSE_STATUS_FOR_WEBSOCKET); + final boolean isEmptyResponse = (status == RESPONSE_STATUS_EMPTY_RESPONSE); + + synchronized (mLock) { + mResponseMap.put(requestPath, + new Response(responseData, responseHeaders, isRedirect, isNotFound, isNoContent, + forWebSocket, isEmptyResponse, responseAction)); + mResponseCountMap.put(requestPath, Integer.valueOf(0)); + mLastRequestMap.put(requestPath, null); + } + return getResponseUrl(requestPath); + } + + /** + * Sets a 404 (not found) response to be returned when a particular request path is passed in. + * + * @param requestPath The path to respond to. + * @return The full URL including the path that should be requested to get the expected + * response. + */ + public String setResponseWithNotFoundStatus(String requestPath) { + return setResponseWithNotFoundStatus(requestPath, null); + } + + /** + * Sets a 404 (not found) response to be returned when a particular request path is passed in. + * + * @param requestPath The path to respond to. + * @param responseHeaders Any additional headers that should be returned along with the + * response (null is acceptable). + * @return The full URL including the path that should be requested to get the expected + * response. + */ + public String setResponseWithNotFoundStatus( + String requestPath, List> responseHeaders) { + return setResponseInternal(requestPath, ApiCompatibilityUtils.getBytesUtf8(""), + responseHeaders, null, RESPONSE_STATUS_NOT_FOUND); + } + + /** + * Sets a 204 (no content) response to be returned when a particular request path is passed in. + * + * @param requestPath The path to respond to. + * @return The full URL including the path that should be requested to get the expected + * response. + */ + public String setResponseWithNoContentStatus(String requestPath) { + return setResponseInternal(requestPath, ApiCompatibilityUtils.getBytesUtf8(""), null, null, + RESPONSE_STATUS_NO_CONTENT); + } + + /** + * Sets an empty response to be returned when a particular request path is passed in. + * + * @param requestPath The path to respond to. + * @return The full URL including the path that should be requested to get the expected + * response. + */ + public String setEmptyResponse(String requestPath) { + return setResponseInternal(requestPath, ApiCompatibilityUtils.getBytesUtf8(""), null, null, + RESPONSE_STATUS_EMPTY_RESPONSE); + } + + /** + * Sets a response to be returned when a particular request path is passed + * in (with the option to specify additional headers). + * + * @param requestPath The path to respond to. + * @param responseString The response body that will be returned. + * @param responseHeaders Any additional headers that should be returned along with the + * response (null is acceptable). + * @return The full URL including the path that should be requested to get the expected + * response. + */ + public String setResponse( + String requestPath, String responseString, List> responseHeaders) { + return setResponseInternal(requestPath, ApiCompatibilityUtils.getBytesUtf8(responseString), + responseHeaders, null, RESPONSE_STATUS_NORMAL); + } + + /** + * Sets a response to be returned when a particular request path is passed + * in with the option to specify additional headers as well as an arbitrary action to be + * executed on each request. + * + * @param requestPath The path to respond to. + * @param responseString The response body that will be returned. + * @param responseHeaders Any additional headers that should be returned along with the + * response (null is acceptable). + * @param responseAction The action to be performed when fetching the response. This action + * will be executed for each request and will be handled on a background + * thread. + * @return The full URL including the path that should be requested to get the expected + * response. + */ + public String setResponseWithRunnableAction(String requestPath, String responseString, + List> responseHeaders, Runnable responseAction) { + return setResponseInternal(requestPath, ApiCompatibilityUtils.getBytesUtf8(responseString), + responseHeaders, responseAction, RESPONSE_STATUS_NORMAL); + } + + /** + * Sets a redirect. + * + * @param requestPath The path to respond to. + * @param targetPath The path to redirect to. + * @return The full URL including the path that should be requested to get the expected + * response. + */ + public String setRedirect(String requestPath, String targetPath) { + List> responseHeaders = new ArrayList>(); + responseHeaders.add(Pair.create("Location", targetPath)); + + return setResponseInternal(requestPath, ApiCompatibilityUtils.getBytesUtf8(targetPath), + responseHeaders, null, RESPONSE_STATUS_MOVED_TEMPORARILY); + } + + /** + * Sets a base64 encoded response to be returned when a particular request path is passed + * in (with the option to specify additional headers). + * + * @param requestPath The path to respond to. + * @param base64EncodedResponse The response body that is base64 encoded. The actual server + * response will the decoded binary form. + * @param responseHeaders Any additional headers that should be returned along with the + * response (null is acceptable). + * @return The full URL including the path that should be requested to get the expected + * response. + */ + public String setResponseBase64(String requestPath, String base64EncodedResponse, + List> responseHeaders) { + return setResponseInternal(requestPath, + Base64.decode(base64EncodedResponse, Base64.DEFAULT), responseHeaders, null, + RESPONSE_STATUS_NORMAL); + } + + /** + * Sets a response to a WebSocket handshake request. + * + * @param requestPath The path to respond to. + * @param responseHeaders Any additional headers that should be returned along with the + * response (null is acceptable). + * @return The full URL including the path that should be requested to get the expected + * response. + */ + public String setResponseForWebSocket( + String requestPath, List> responseHeaders) { + if (responseHeaders == null) { + responseHeaders = new ArrayList>(); + } else { + responseHeaders = new ArrayList>(responseHeaders); + } + responseHeaders.add(Pair.create("Connection", "Upgrade")); + responseHeaders.add(Pair.create("Upgrade", "websocket")); + return setResponseInternal(requestPath, ApiCompatibilityUtils.getBytesUtf8(""), + responseHeaders, null, RESPONSE_STATUS_FOR_WEBSOCKET); + } + + /** + * Get the number of requests was made at this path since it was last set. + */ + public int getRequestCount(String requestPath) { + Integer count = null; + synchronized (mLock) { + count = mResponseCountMap.get(requestPath); + } + if (count == null) throw new IllegalArgumentException("Path not set: " + requestPath); + return count.intValue(); + } + + /** + * Returns the last HttpRequest at this path. Can return null if it is never requested. + */ + public HTTPRequest getLastRequest(String requestPath) { + synchronized (mLock) { + if (!mLastRequestMap.containsKey(requestPath)) + throw new IllegalArgumentException("Path not set: " + requestPath); + return mLastRequestMap.get(requestPath); + } + } + + private static class WebServerPrintStream extends PrintStream { + WebServerPrintStream(OutputStream out) { + super(out); + } + + @Override + public void println(String s) { + Log.w(TAG, s); + super.println(s); + } + } + + /** + * Generate a response to the given request. + * + *

Always executed on the background server thread. + * + *

If there is an action associated with the response, it will be executed inside of + * this function. + * + * @throws NoSuchAlgorithmException, IOException + */ + private void outputResponse(HTTPRequest request, WebServerPrintStream stream) + throws NoSuchAlgorithmException, IOException { + // Don't dump headers to decrease log. + Log.w(TAG, request.requestLine()); + + final String bodyTemplate = "%s" + + "%s"; + + boolean copyHeadersToResponse = true; + boolean copyBinaryBodyToResponse = false; + boolean contentLengthAlreadyIncluded = false; + boolean contentTypeAlreadyIncluded = false; + String path = URI.create(request.getURI()).getPath(); + StringBuilder textBody = new StringBuilder(); + + Response response; + synchronized (mLock) { + response = mResponseMap.get(path); + } + + if (response == null || response.mIsNotFound) { + stream.println("HTTP/1.0 404 Not Found"); + textBody.append(String.format(bodyTemplate, "Not Found", "Not Found")); + } else if (response.mForWebSocket) { + String keyHeader = request.headerValue("Sec-WebSocket-Key"); + if (!keyHeader.isEmpty()) { + stream.println("HTTP/1.0 101 Switching Protocols"); + stream.println("Sec-WebSocket-Accept: " + computeWebSocketAccept(keyHeader)); + } else { + stream.println("HTTP/1.0 404 Not Found"); + textBody.append(String.format(bodyTemplate, "Not Found", "Not Found")); + copyHeadersToResponse = false; + } + } else if (response.mIsNoContent) { + stream.println("HTTP/1.0 200 OK"); + copyHeadersToResponse = false; + } else if (response.mIsRedirect) { + stream.println("HTTP/1.0 302 Found"); + textBody.append(String.format(bodyTemplate, "Found", "Found")); + } else if (response.mIsEmptyResponse) { + stream.println("HTTP/1.0 403 Forbidden"); + copyHeadersToResponse = false; + } else { + if (response.mResponseAction != null) response.mResponseAction.run(); + + stream.println("HTTP/1.0 200 OK"); + copyBinaryBodyToResponse = true; + } + + if (response != null) { + if (copyHeadersToResponse) { + for (Pair header : response.mResponseHeaders) { + stream.println(header.first + ": " + header.second); + if (header.first.toLowerCase(Locale.ENGLISH).equals("content-length")) { + contentLengthAlreadyIncluded = true; + } else if (header.first.toLowerCase(Locale.ENGLISH).equals("content-type")) { + contentTypeAlreadyIncluded = true; + } + } + } + synchronized (mLock) { + mResponseCountMap.put( + path, Integer.valueOf(mResponseCountMap.get(path).intValue() + 1)); + mLastRequestMap.put(path, request); + } + } + + // RFC 1123 + final SimpleDateFormat dateFormat = + new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); + + // Using print and println() because we don't want to dump it into log. + stream.print("Date: " + dateFormat.format(new Date())); + stream.println(); + + if (textBody.length() != 0) { + if (!contentTypeAlreadyIncluded && (path.endsWith(".html") || path.endsWith(".htm"))) { + stream.println("Content-Type: text/html"); + } + stream.println("Content-Length: " + textBody.length()); + stream.println(); + stream.print(textBody.toString()); + } else if (copyBinaryBodyToResponse) { + if (!contentTypeAlreadyIncluded && path.endsWith(".js")) { + stream.println("Content-Type: application/javascript"); + } else if (!contentTypeAlreadyIncluded + && (path.endsWith(".html") || path.endsWith(".htm"))) { + stream.println("Content-Type: text/html"); + } + if (!contentLengthAlreadyIncluded) { + stream.println("Content-Length: " + response.mResponseData.length); + } + stream.println(); + stream.write(response.mResponseData); + } else { + stream.println(); + } + } + + /** + * Return a response for WebSocket handshake challenge. + */ + private static String computeWebSocketAccept(String keyString) throws NoSuchAlgorithmException { + byte[] key = keyString.getBytes(Charset.forName("US-ASCII")); + byte[] guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".getBytes(Charset.forName("US-ASCII")); + + MessageDigest md = MessageDigest.getInstance("SHA"); + md.update(key); + md.update(guid); + byte[] output = md.digest(); + return Base64.encodeToString(output, Base64.NO_WRAP); + } +} diff --git a/chromium/net/test/android/javatests/src/org/chromium/net/test/util/WebServer.java b/chromium/net/test/android/javatests/src/org/chromium/net/test/util/WebServer.java new file mode 100644 index 00000000000..82cf22b62d1 --- /dev/null +++ b/chromium/net/test/android/javatests/src/org/chromium/net/test/util/WebServer.java @@ -0,0 +1,506 @@ +// Copyright 2018 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.test.util; + +import android.util.Base64; + +import org.chromium.base.Log; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketException; +import java.security.KeyStore; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.annotation.concurrent.GuardedBy; +import javax.net.ssl.KeyManager; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; + +/** + * Simple http test server for testing. + * + * This server runs in a thread in the current process, so it is convenient + * for loopback testing without the need to setup TCP forwarding to the + * host computer. + */ +public class WebServer { + private static final String TAG = "WebServer"; + + private static WebServer sInstance; + private static WebServer sSecureInstance; + + private final ServerThread mServerThread; + private String mServerUri; + private final boolean mSsl; + private final int mPort; + + public static final String STATUS_OK = "200 OK"; + + /** + * Writes an HTTP response to |output|. + * |status| should be one of the STATUS_* values above. + */ + public static void writeResponse(OutputStream output, String status, byte[] body) + throws IOException { + if (body == null) { + body = new byte[0]; + } + output.write(("HTTP/1.1 " + status + "\r\nContent-Length: " + String.valueOf(body.length) + + "\r\n\r\n") + .getBytes()); + output.write(body); + output.flush(); + } + + /** Represents an HTTP header. */ + public static class HTTPHeader { + public final String key; + public final String value; + /** Constructs an HTTP header. */ + public HTTPHeader(String key, String value) { + this.key = key; + this.value = value; + } + /** + * Parse an HTTP header from a string line. Returns null if the line is not a valid HTTP + * header. + */ + public static HTTPHeader parseLine(String line) { + String[] parts = line.split(":", 2); + if (parts.length == 2) { + return new HTTPHeader(parts[0].trim(), parts[1].trim()); + } + return null; + } + + @Override + public String toString() { + return key + ": " + value; + } + } + + /** Thrown when an HTTP request could not be parsed. */ + public static class InvalidRequest extends Exception { + /** Constructor */ + public InvalidRequest() { + super("Invalid HTTP request"); + } + } + + /** A parsed HTTP request. */ + public static class HTTPRequest { + private String mMethod; + private String mURI; + private String mHTTPVersion; + private HTTPHeader[] mHeaders; + private byte[] mBody; + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(requestLine()); + builder.append("\r\n"); + for (HTTPHeader header : mHeaders) { + builder.append(header.toString()); + builder.append("\r\n"); + } + if (mBody != null) { + builder.append("\r\n"); + try { + builder.append(new String(mBody, "UTF-8")); + } catch (UnsupportedEncodingException e) { + builder.append("\r\n"); + } + } + return builder.toString(); + } + + /** Returns the request line as a String. */ + public String requestLine() { + return mMethod + " " + mURI + " " + mHTTPVersion; + } + + /** Returns the request method. */ + public String getMethod() { + return mMethod; + } + + /** Returns the request URI. */ + public String getURI() { + return mURI; + } + + /** Returns the request HTTP version. */ + public String getHTTPVersion() { + return mHTTPVersion; + } + + /** Returns the request headers. */ + public HTTPHeader[] getHeaders() { + return mHeaders; + } + + /** Returns the request body. */ + public byte[] getBody() { + return mBody; + } + + /** + * Returns the header value for the given header name. If a header is present multiple + * times, this only returns the first occurence. Returns "" if the header is not found. + */ + public String headerValue(String headerName) { + for (String value : headerValues(headerName)) { + return value; + } + return ""; + } + + /** Returns all header values for the given header name. */ + public List headerValues(String headerName) { + List matchingHeaders = new ArrayList(); + for (HTTPHeader header : mHeaders) { + if (header.key.equalsIgnoreCase(headerName)) { + matchingHeaders.add(header.value); + } + } + return matchingHeaders; + } + + /** Parses an HTTP request from an input stream. */ + static public HTTPRequest parse(InputStream stream) throws InvalidRequest, IOException { + boolean firstLine = true; + HTTPRequest req = new HTTPRequest(); + ArrayList mHeaders = new ArrayList(); + ByteArrayOutputStream line = new ByteArrayOutputStream(); + for (int b = stream.read(); b != -1; b = stream.read()) { + if (b == '\r') { + int next = stream.read(); + if (next == '\n') { + String lineString; + try { + lineString = new String(line.toByteArray(), "UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new InvalidRequest(); + } + line.reset(); + if (firstLine) { + String[] parts = lineString.split(" ", 3); + if (parts.length != 3) { + throw new InvalidRequest(); + } + req.mMethod = parts[0]; + req.mURI = parts[1]; + req.mHTTPVersion = parts[2]; + firstLine = false; + } else { + if (lineString.length() == 0) { + break; + } + HTTPHeader header = HTTPHeader.parseLine(lineString); + if (header != null) { + mHeaders.add(header); + } + } + } else if (next == -1) { + throw new InvalidRequest(); + } else { + line.write(b); + line.write(next); + } + } else { + line.write(b); + } + } + if (firstLine) { + if (line.size() == 0) return null; + throw new InvalidRequest(); + } + req.mHeaders = mHeaders.toArray(new HTTPHeader[0]); + int contentLength = -1; + if (req.mMethod.equals("GET") || req.mMethod.equals("HEAD")) { + contentLength = 0; + } + try { + contentLength = Integer.parseInt(req.headerValue("Content-Length")); + } catch (NumberFormatException e) { + } + if (contentLength >= 0) { + byte[] content = new byte[contentLength]; + for (int offset = 0; offset < contentLength;) { + int bytesRead = stream.read(content, offset, contentLength); + if (bytesRead == -1) { // short read, keep truncated content. + content = Arrays.copyOf(content, offset); + break; + } + offset += bytesRead; + } + req.mBody = content; + } else { + ByteArrayOutputStream mBody = new ByteArrayOutputStream(); + byte[] buffer = new byte[1000]; + int bytesRead; + while ((bytesRead = stream.read(buffer, 0, buffer.length)) != -1) { + mBody.write(buffer, 0, bytesRead); + } + req.mBody = mBody.toByteArray(); + } + return req; + } + } + + /** An interface for handling HTTP requests. */ + public interface RequestHandler { + /** handleRequest is called when an HTTP request is received. handleRequest should write a + * response to stream. */ + void handleRequest(HTTPRequest request, OutputStream stream); + } + + private RequestHandler mRequestHandler; + + /** Sets the request handler. */ + public void setRequestHandler(RequestHandler handler) { + mRequestHandler = handler; + } + + /** Handle an HTTP request. Calls |mRequestHandler| if set. */ + private void handleRequest(HTTPRequest request, OutputStream stream) { + assert Thread.currentThread() + == mServerThread : "handleRequest called from non-server thread"; + if (mRequestHandler != null) { + mRequestHandler.handleRequest(request, stream); + } + } + + public void setServerHost(String hostname) { + try { + mServerUri = new java.net + .URI(mSsl ? "https" : "http", null, hostname, + mServerThread.mSocket.getLocalPort(), null, null, null) + .toString(); + } catch (java.net.URISyntaxException e) { + Log.wtf(TAG, e.getMessage()); + } + } + + /** + * Create and start a local HTTP server instance. + * @param port Port number the server must use, or 0 to automatically choose a free port. + * @param ssl True if the server should be using secure sockets. + * @throws Exception + */ + public WebServer(int port, boolean ssl) throws Exception { + mPort = port; + mSsl = ssl; + + if (mSsl) { + if (sSecureInstance != null) { + throw new IllegalStateException("Tried to start multiple SSL TestWebServers"); + } + } else { + if (sInstance != null) { + throw new IllegalStateException("Tried to start multiple TestWebServers"); + } + } + mServerThread = new ServerThread(mPort, mSsl); + + setServerHost("localhost"); + + mServerThread.start(); + if (mSsl) { + WebServer.sSecureInstance = this; + } else { + WebServer.sInstance = this; + } + } + + /** + * Terminate the http server. + */ + public void shutdown() { + if (mSsl) { + WebServer.sSecureInstance = null; + } else { + WebServer.sInstance = null; + } + + try { + mServerThread.cancelAllRequests(); + // Block until the server thread is done shutting down. + mServerThread.join(); + } catch (MalformedURLException e) { + throw new IllegalStateException(e); + } catch (InterruptedException | IOException e) { + throw new RuntimeException(e); + } + } + + public String getBaseUrl() { + return mServerUri + "/"; + } + + /** + * Gets the URL on the server under which a particular request path will be accessible. + * + * This only gets the URL, you still need to set the response if you intend to access it. + * + * @param requestPath The path to respond to. + * @return The full URL including the requestPath. + */ + public String getResponseUrl(String requestPath) { + return mServerUri + requestPath; + } + + private class ServerThread extends Thread { + private final boolean mIsSsl; + private ServerSocket mSocket; + private SSLContext mSslContext; + + private final Object mLock = new Object(); + @GuardedBy("mLock") + private boolean mIsCancelled; + @GuardedBy("mLock") + private Socket mCurrentRequestSocket; + + /** + * Defines the keystore contents for the server, BKS version. Holds just a + * single self-generated key. The subject name is "Test Server". + */ + private static final String SERVER_KEYS_BKS = + "AAAAAQAAABQDkebzoP1XwqyWKRCJEpn/t8dqIQAABDkEAAVteWtleQAAARpYl20nAAAAAQAFWC41" + + "MDkAAAJNMIICSTCCAbKgAwIBAgIESEfU1jANBgkqhkiG9w0BAQUFADBpMQswCQYDVQQGEwJVUzET" + + "MBEGA1UECBMKQ2FsaWZvcm5pYTEMMAoGA1UEBxMDTVRWMQ8wDQYDVQQKEwZHb29nbGUxEDAOBgNV" + + "BAsTB0FuZHJvaWQxFDASBgNVBAMTC1Rlc3QgU2VydmVyMB4XDTA4MDYwNTExNTgxNFoXDTA4MDkw" + + "MzExNTgxNFowaTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExDDAKBgNVBAcTA01U" + + "VjEPMA0GA1UEChMGR29vZ2xlMRAwDgYDVQQLEwdBbmRyb2lkMRQwEgYDVQQDEwtUZXN0IFNlcnZl" + + "cjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0LIdKaIr9/vsTq8BZlA3R+NFWRaH4lGsTAQy" + + "DPMF9ZqEDOaL6DJuu0colSBBBQ85hQTPa9m9nyJoN3pEi1hgamqOvQIWcXBk+SOpUGRZZFXwniJV" + + "zDKU5nE9MYgn2B9AoiH3CSuMz6HRqgVaqtppIe1jhukMc/kHVJvlKRNy9XMCAwEAATANBgkqhkiG" + + "9w0BAQUFAAOBgQC7yBmJ9O/eWDGtSH9BH0R3dh2NdST3W9hNZ8hIa8U8klhNHbUCSSktZmZkvbPU" + + "hse5LI3dh6RyNDuqDrbYwcqzKbFJaq/jX9kCoeb3vgbQElMRX8D2ID1vRjxwlALFISrtaN4VpWzV" + + "yeoHPW4xldeZmoVtjn8zXNzQhLuBqX2MmAAAAqwAAAAUvkUScfw9yCSmALruURNmtBai7kQAAAZx" + + "4Jmijxs/l8EBaleaUru6EOPioWkUAEVWCxjM/TxbGHOi2VMsQWqRr/DZ3wsDmtQgw3QTrUK666sR" + + "MBnbqdnyCyvM1J2V1xxLXPUeRBmR2CXorYGF9Dye7NkgVdfA+9g9L/0Au6Ugn+2Cj5leoIgkgApN" + + "vuEcZegFlNOUPVEs3SlBgUF1BY6OBM0UBHTPwGGxFBBcetcuMRbUnu65vyDG0pslT59qpaR0TMVs" + + "P+tcheEzhyjbfM32/vwhnL9dBEgM8qMt0sqF6itNOQU/F4WGkK2Cm2v4CYEyKYw325fEhzTXosck" + + "MhbqmcyLab8EPceWF3dweoUT76+jEZx8lV2dapR+CmczQI43tV9btsd1xiBbBHAKvymm9Ep9bPzM" + + "J0MQi+OtURL9Lxke/70/MRueqbPeUlOaGvANTmXQD2OnW7PISwJ9lpeLfTG0LcqkoqkbtLKQLYHI" + + "rQfV5j0j+wmvmpMxzjN3uvNajLa4zQ8l0Eok9SFaRr2RL0gN8Q2JegfOL4pUiHPsh64WWya2NB7f" + + "V+1s65eA5ospXYsShRjo046QhGTmymwXXzdzuxu8IlnTEont6P4+J+GsWk6cldGbl20hctuUKzyx" + + "OptjEPOKejV60iDCYGmHbCWAzQ8h5MILV82IclzNViZmzAapeeCnexhpXhWTs+xDEYSKEiG/camt" + + "bhmZc3BcyVJrW23PktSfpBQ6D8ZxoMfF0L7V2GQMaUg+3r7ucrx82kpqotjv0xHghNIm95aBr1Qw" + + "1gaEjsC/0wGmmBDg1dTDH+F1p9TInzr3EFuYD0YiQ7YlAHq3cPuyGoLXJ5dXYuSBfhDXJSeddUkl" + + "k1ufZyOOcskeInQge7jzaRfmKg3U94r+spMEvb0AzDQVOKvjjo1ivxMSgFRZaDb/4qw="; + + private static final String PASSWORD = "android"; + + /** + * Loads a keystore from a base64-encoded String. Returns the KeyManager[] + * for the result. + */ + private KeyManager[] getKeyManagers() throws Exception { + byte[] bytes = Base64.decode(SERVER_KEYS_BKS, Base64.DEFAULT); + InputStream inputStream = new ByteArrayInputStream(bytes); + + KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); + keyStore.load(inputStream, PASSWORD.toCharArray()); + inputStream.close(); + + String algorithm = KeyManagerFactory.getDefaultAlgorithm(); + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(algorithm); + keyManagerFactory.init(keyStore, PASSWORD.toCharArray()); + + return keyManagerFactory.getKeyManagers(); + } + + private void setCurrentRequestSocket(Socket socket) { + synchronized (mLock) { + mCurrentRequestSocket = socket; + } + } + + private boolean getIsCancelled() { + synchronized (mLock) { + return mIsCancelled; + } + } + + // Called from non-server thread. + public void cancelAllRequests() throws IOException { + synchronized (mLock) { + mIsCancelled = true; + if (mCurrentRequestSocket != null) { + try { + mCurrentRequestSocket.close(); + } catch (IOException ignored) { + // Catching this to ensure the server socket is closed as well. + } + } + } + // Any current and subsequent accept call will throw instead of block. + mSocket.close(); + } + + public ServerThread(int port, boolean ssl) throws Exception { + super("ServerThread"); + mIsSsl = ssl; + // If tests are run back-to-back, it may take time for the port to become available. + // Retry a few times with a sleep to wait for the port. + int retry = 3; + while (true) { + try { + if (mIsSsl) { + mSslContext = SSLContext.getInstance("TLS"); + mSslContext.init(getKeyManagers(), null, null); + mSocket = mSslContext.getServerSocketFactory().createServerSocket(port); + } else { + mSocket = new ServerSocket(port); + } + return; + } catch (IOException e) { + Log.w(TAG, e.getMessage()); + if (--retry == 0) { + throw e; + } + // sleep in case server socket is still being closed + Thread.sleep(1000); + } + } + } + + @Override + public void run() { + try { + while (!getIsCancelled()) { + Socket socket = mSocket.accept(); + try { + setCurrentRequestSocket(socket); + HTTPRequest request = HTTPRequest.parse(socket.getInputStream()); + if (request != null) { + handleRequest(request, socket.getOutputStream()); + } + } catch (InvalidRequest e) { + Log.e(TAG, e.getMessage()); + } finally { + socket.close(); + } + } + } catch (SocketException e) { + } catch (IOException e) { + Log.w(TAG, e.getMessage()); + } + } + } +} diff --git a/chromium/net/test/android/net_test_jni_onload.cc b/chromium/net/test/android/net_test_jni_onload.cc new file mode 100644 index 00000000000..73739190643 --- /dev/null +++ b/chromium/net/test/android/net_test_jni_onload.cc @@ -0,0 +1,20 @@ +// Copyright 2015 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. + +#include "net/test/android/net_test_jni_onload.h" + +#include "base/android/base_jni_onload.h" +#include "base/android/jni_android.h" +#include "base/bind.h" +#include "net/test/embedded_test_server/android/embedded_test_server_android.h" + +namespace net { +namespace test { + +bool OnJNIOnLoadInit() { + return base::android::OnJNIOnLoadInit(); +} + +} // namespace test +} // namespace net diff --git a/chromium/net/test/android/net_test_jni_onload.h b/chromium/net/test/android/net_test_jni_onload.h new file mode 100644 index 00000000000..35c734b8061 --- /dev/null +++ b/chromium/net/test/android/net_test_jni_onload.h @@ -0,0 +1,18 @@ +// Copyright 2015 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. + +#ifndef NET_TEST_ANDROID_NET_TEST_JNI_ONLOAD_H_ +#define NET_TEST_ANDROID_NET_TEST_JNI_ONLOAD_H_ + +#include + +namespace net { +namespace test { + +bool OnJNIOnLoadInit(); + +} // namespace test +} // namespace net + +#endif // NET_TEST_ANDROID_NET_TEST_JNI_ONLOAD_H_ diff --git a/chromium/net/test/embedded_test_server/android/embedded_test_server_android.cc b/chromium/net/test/embedded_test_server/android/embedded_test_server_android.cc index 16fe95014a8..6f6431e1a3a 100644 --- a/chromium/net/test/embedded_test_server/android/embedded_test_server_android.cc +++ b/chromium/net/test/embedded_test_server/android/embedded_test_server_android.cc @@ -56,8 +56,9 @@ EmbeddedTestServerAndroid::~EmbeddedTestServerAndroid() { } jboolean EmbeddedTestServerAndroid::Start(JNIEnv* env, - const JavaParamRef& jobj) { - return test_server_.Start(); + const JavaParamRef& jobj, + jint port) { + return test_server_.Start(static_cast(port)); } ScopedJavaLocalRef EmbeddedTestServerAndroid::GetRootCertPemPath( diff --git a/chromium/net/test/embedded_test_server/android/embedded_test_server_android.h b/chromium/net/test/embedded_test_server/android/embedded_test_server_android.h index 7d96cec23b4..a3b22512dea 100644 --- a/chromium/net/test/embedded_test_server/android/embedded_test_server_android.h +++ b/chromium/net/test/embedded_test_server/android/embedded_test_server_android.h @@ -28,7 +28,9 @@ class EmbeddedTestServerAndroid { void Destroy(JNIEnv* env, const base::android::JavaParamRef& obj); - jboolean Start(JNIEnv* env, const base::android::JavaParamRef& jobj); + jboolean Start(JNIEnv* env, + const base::android::JavaParamRef& jobj, + jint port); base::android::ScopedJavaLocalRef GetRootCertPemPath( JNIEnv* jenv, diff --git a/chromium/net/test/embedded_test_server/embedded_test_server.cc b/chromium/net/test/embedded_test_server/embedded_test_server.cc index 3cd2e5c6afe..358d44e5f8e 100644 --- a/chromium/net/test/embedded_test_server/embedded_test_server.cc +++ b/chromium/net/test/embedded_test_server/embedded_test_server.cc @@ -89,15 +89,15 @@ void EmbeddedTestServer::SetConnectionListener( connection_listener_ = listener; } -bool EmbeddedTestServer::Start() { - bool success = InitializeAndListen(); +bool EmbeddedTestServer::Start(int port) { + bool success = InitializeAndListen(port); if (!success) return false; StartAcceptingConnections(); return true; } -bool EmbeddedTestServer::InitializeAndListen() { +bool EmbeddedTestServer::InitializeAndListen(int port) { DCHECK(!Started()); const int max_tries = 5; @@ -114,7 +114,8 @@ bool EmbeddedTestServer::InitializeAndListen() { listen_socket_.reset(new TCPServerSocket(nullptr, NetLogSource())); - int result = listen_socket_->ListenWithAddressAndPort("127.0.0.1", 0, 10); + int result = + listen_socket_->ListenWithAddressAndPort("127.0.0.1", port, 10); if (result) { LOG(ERROR) << "Listen failed: " << ErrorToString(result); listen_socket_.reset(); diff --git a/chromium/net/test/embedded_test_server/embedded_test_server.h b/chromium/net/test/embedded_test_server/embedded_test_server.h index 2f273ae1f28..c663e1d12c3 100644 --- a/chromium/net/test/embedded_test_server/embedded_test_server.h +++ b/chromium/net/test/embedded_test_server/embedded_test_server.h @@ -145,11 +145,11 @@ class EmbeddedTestServer { // This is the equivalent of calling InitializeAndListen() followed by // StartAcceptingConnections(). // Returns whether a listening socket has been successfully created. - bool Start() WARN_UNUSED_RESULT; + bool Start(int port = 0) WARN_UNUSED_RESULT; // Starts listening for incoming connections but will not yet accept them. // Returns whether a listening socket has been succesfully created. - bool InitializeAndListen() WARN_UNUSED_RESULT; + bool InitializeAndListen(int port = 0) WARN_UNUSED_RESULT; // Starts the Accept IO Thread and begins accepting connections. void StartAcceptingConnections(); diff --git a/chromium/net/test/embedded_test_server/request_handler_util.cc b/chromium/net/test/embedded_test_server/request_handler_util.cc index 3eff4f4ed9d..1d12833dcbc 100644 --- a/chromium/net/test/embedded_test_server/request_handler_util.cc +++ b/chromium/net/test/embedded_test_server/request_handler_util.cc @@ -32,6 +32,8 @@ const char kMockHttpHeadersExtension[] = "mock-http-headers"; std::string GetContentType(const base::FilePath& path) { if (path.MatchesExtension(FILE_PATH_LITERAL(".crx"))) return "application/x-chrome-extension"; + if (path.MatchesExtension(FILE_PATH_LITERAL(".css"))) + return "text/css"; if (path.MatchesExtension(FILE_PATH_LITERAL(".exe"))) return "application/octet-stream"; if (path.MatchesExtension(FILE_PATH_LITERAL(".gif"))) diff --git a/chromium/net/test/run_all_unittests.cc b/chromium/net/test/run_all_unittests.cc index 5786dfea0aa..3e3eaf9ef3e 100644 --- a/chromium/net/test/run_all_unittests.cc +++ b/chromium/net/test/run_all_unittests.cc @@ -11,10 +11,7 @@ #include "net/socket/client_socket_pool_base.h" #include "net/test/net_test_suite.h" #include "url/url_features.h" - -#if !defined(OS_IOS) #include "mojo/core/embedder/embedder.h" // nogncheck -#endif using net::internal::ClientSocketPoolBaseHelper; @@ -57,9 +54,7 @@ int main(int argc, char** argv) { NetTestSuite test_suite(argc, argv); ClientSocketPoolBaseHelper::set_connect_backup_jobs_enabled(false); -#if !defined(OS_IOS) mojo::core::Init(); -#endif return base::LaunchUnitTests( argc, argv, base::Bind(&NetTestSuite::Run, diff --git a/chromium/net/test/spawned_test_server/base_test_server.cc b/chromium/net/test/spawned_test_server/base_test_server.cc index fb1163ee44e..0b5dd3cf604 100644 --- a/chromium/net/test/spawned_test_server/base_test_server.cc +++ b/chromium/net/test/spawned_test_server/base_test_server.cc @@ -114,15 +114,6 @@ bool GetLocalCertificatesDir(const base::FilePath& certificates_dir, return true; } -std::unique_ptr GetTokenBindingParams( - std::vector params) { - std::unique_ptr values(new base::ListValue()); - for (int param : params) { - values->AppendInteger(param); - } - return values; -} - std::string OCSPStatusToString( const BaseTestServer::SSLOptions::OCSPStatus& ocsp_status) { switch (ocsp_status) { @@ -417,9 +408,7 @@ bool BaseTestServer::GetFilePathWithReplacements( std::string new_file_path = original_file_path; bool first_query_parameter = true; const std::vector::const_iterator end = text_to_replace.end(); - for (std::vector::const_iterator it = text_to_replace.begin(); - it != end; - ++it) { + for (auto it = text_to_replace.begin(); it != end; ++it) { const std::string& old_text = it->first; const std::string& new_text = it->second; std::string base64_old; @@ -449,15 +438,23 @@ void BaseTestServer::RegisterTestCerts() { bool BaseTestServer::LoadTestRootCert() const { TestRootCerts* root_certs = TestRootCerts::GetInstance(); - if (!root_certs) - return false; + DCHECK(root_certs); // Should always use absolute path to load the root certificate. base::FilePath root_certificate_path; - if (!GetLocalCertificatesDir(certificates_dir_, &root_certificate_path)) + if (!GetLocalCertificatesDir(certificates_dir_, &root_certificate_path)) { + LOG(ERROR) << "Could not get local certificates directory from " + << certificates_dir_ << "."; + return false; + } + + if (!RegisterRootCertsInternal(root_certificate_path)) { + LOG(ERROR) << "Could not register root certificates from " + << root_certificate_path << "."; return false; + } - return RegisterRootCertsInternal(root_certificate_path); + return true; } scoped_refptr BaseTestServer::GetCertificate() const { @@ -529,8 +526,10 @@ bool BaseTestServer::SetupWhenServerStarted() { DCHECK(host_port_pair_.port()); DCHECK(!started_); - if (UsingSSL(type_) && !LoadTestRootCert()) - return false; + if (UsingSSL(type_) && !LoadTestRootCert()) { + LOG(ERROR) << "Could not load test root certificate."; + return false; + } started_ = true; allowed_port_.reset(new ScopedPortException(host_port_pair_.port())); @@ -719,13 +718,6 @@ bool BaseTestServer::GenerateArguments(base::DictionaryValue* arguments) const { arguments->Set("disable-extended-master-secret", std::make_unique()); } - if (!ssl_options_.supported_token_binding_params.empty()) { - std::unique_ptr token_binding_params( - new base::ListValue()); - arguments->Set( - "token-binding-params", - GetTokenBindingParams(ssl_options_.supported_token_binding_params)); - } } return GenerateAdditionalArguments(arguments); diff --git a/chromium/net/test/spawned_test_server/base_test_server.h b/chromium/net/test/spawned_test_server/base_test_server.h index c5b71a51dba..da11ca28ba2 100644 --- a/chromium/net/test/spawned_test_server/base_test_server.h +++ b/chromium/net/test/spawned_test_server/base_test_server.h @@ -342,9 +342,6 @@ class BaseTestServer { // If true, disables extended master secret tls extension. bool disable_extended_master_secret = false; - - // List of token binding params that the server supports and will negotiate. - std::vector supported_token_binding_params; }; // Initialize a TestServer. diff --git a/chromium/net/test/spawned_test_server/local_test_server.cc b/chromium/net/test/spawned_test_server/local_test_server.cc index c4378a55334..e0522e0adf7 100644 --- a/chromium/net/test/spawned_test_server/local_test_server.cc +++ b/chromium/net/test/spawned_test_server/local_test_server.cc @@ -95,14 +95,20 @@ bool LocalTestServer::StartInBackground() { // Get path to Python server script. base::FilePath testserver_path; - if (!GetTestServerPath(&testserver_path)) + if (!GetTestServerPath(&testserver_path)) { + LOG(ERROR) << "Could not get test server path."; return false; + } - if (!SetPythonPath()) + if (!SetPythonPath()) { + LOG(ERROR) << "Could not set Python path."; return false; + } - if (!LaunchPython(testserver_path)) + if (!LaunchPython(testserver_path)) { + LOG(ERROR) << "Could not launch Python with path " << testserver_path; return false; + } return true; } @@ -201,8 +207,7 @@ bool LocalTestServer::AddCommandLineArguments( const base::ListValue* list = NULL; if (!value.GetAsList(&list) || !list || list->empty()) return false; - for (base::ListValue::const_iterator list_it = list->begin(); - list_it != list->end(); ++list_it) { + for (auto list_it = list->begin(); list_it != list->end(); ++list_it) { if (!AppendArgumentFromJSONValue(key, *list_it, command_line)) return false; } diff --git a/chromium/net/test/spawned_test_server/local_test_server_posix.cc b/chromium/net/test/spawned_test_server/local_test_server_posix.cc index 0ba4ee34b71..dd601591a84 100644 --- a/chromium/net/test/spawned_test_server/local_test_server_posix.cc +++ b/chromium/net/test/spawned_test_server/local_test_server_posix.cc @@ -41,10 +41,8 @@ class OrphanedTestServerFilter : public base::ProcessFilter { return false; bool found_path_string = false; bool found_port_string = false; - for (std::vector::const_iterator it = - entry.cmd_line_args().begin(); - it != entry.cmd_line_args().end(); - ++it) { + for (auto it = entry.cmd_line_args().begin(); + it != entry.cmd_line_args().end(); ++it) { if (it->find(path_string_) != std::string::npos) found_path_string = true; if (it->find(port_string_) != std::string::npos) diff --git a/chromium/net/test/spawned_test_server/remote_test_server_config.cc b/chromium/net/test/spawned_test_server/remote_test_server_config.cc index 125ec6f8490..ac92e248652 100644 --- a/chromium/net/test/spawned_test_server/remote_test_server_config.cc +++ b/chromium/net/test/spawned_test_server/remote_test_server_config.cc @@ -29,7 +29,7 @@ base::FilePath GetTestServerConfigFilePath() { #if defined(OS_ANDROID) base::PathService::Get(base::DIR_ANDROID_EXTERNAL_STORAGE, &dir); #elif defined(OS_FUCHSIA) - dir = base::FilePath("/data"); + dir = base::FilePath("/test-shared"); #else base::PathService::Get(base::DIR_TEMP, &dir); #endif diff --git a/chromium/net/test/url_request/url_request_slow_download_job.cc b/chromium/net/test/url_request/url_request_slow_download_job.cc index dfba97ec634..72a2b5a5889 100644 --- a/chromium/net/test/url_request/url_request_slow_download_job.cc +++ b/chromium/net/test/url_request/url_request_slow_download_job.cc @@ -95,16 +95,14 @@ size_t URLRequestSlowDownloadJob::NumberOutstandingRequests() { // static void URLRequestSlowDownloadJob::FinishPendingRequests() { - typedef std::set JobList; - for (JobList::iterator it = pending_requests_.Get().begin(); + for (auto it = pending_requests_.Get().begin(); it != pending_requests_.Get().end(); ++it) { (*it)->set_should_finish_download(); } } void URLRequestSlowDownloadJob::ErrorPendingRequests() { - typedef std::set JobList; - for (JobList::iterator it = pending_requests_.Get().begin(); + for (auto it = pending_requests_.Get().begin(); it != pending_requests_.Get().end(); ++it) { (*it)->set_should_error_download(); } -- cgit v1.2.1