1 // Copyright 2015 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.net.test; 6 7 import android.content.ComponentName; 8 import android.content.Context; 9 import android.content.Intent; 10 import android.content.ServiceConnection; 11 import android.os.IBinder; 12 import android.os.Looper; 13 import android.os.RemoteException; 14 15 import androidx.annotation.GuardedBy; 16 17 import org.junit.Assert; 18 19 import org.chromium.base.Log; 20 import org.chromium.base.ResettersForTesting; 21 import org.chromium.base.ThreadUtils; 22 import org.chromium.base.test.BaseJUnit4ClassRunner.ClassHook; 23 import org.chromium.base.test.util.UrlUtils; 24 import org.chromium.net.X509Util; 25 import org.chromium.net.test.util.CertTestUtil; 26 27 import java.io.File; 28 29 /** 30 * A simple file server for java tests. 31 * 32 * An example use: 33 * <pre> 34 * EmbeddedTestServer s = EmbeddedTestServer.createAndStartServer(context); 35 * 36 * // serve requests... 37 * s.getURL("/foo/bar.txt"); 38 * 39 * // Generally safe to omit as ResettersForTesting will call it. 40 * s.stopAndDestroyServer(); 41 * </pre> 42 * 43 * Note that this runs net::test_server::EmbeddedTestServer in a service in a separate APK. 44 */ 45 public class EmbeddedTestServer { 46 private static final String TAG = "TestServer"; 47 48 private static final String EMBEDDED_TEST_SERVER_SERVICE = 49 "org.chromium.net.test.EMBEDDED_TEST_SERVER_SERVICE"; 50 private static final long SERVICE_CONNECTION_WAIT_INTERVAL_MS = 5000; 51 52 private static boolean sTestRootInitDone; 53 54 @GuardedBy("mImplMonitor") 55 private IEmbeddedTestServerImpl mImpl; 56 57 private ServiceConnection mConn = 58 new ServiceConnection() { 59 @Override 60 public void onServiceConnected(ComponentName name, IBinder service) { 61 synchronized (mImplMonitor) { 62 mImpl = IEmbeddedTestServerImpl.Stub.asInterface(service); 63 mImplMonitor.notify(); 64 } 65 } 66 67 @Override 68 public void onServiceDisconnected(ComponentName name) { 69 synchronized (mImplMonitor) { 70 mImpl = null; 71 mImplMonitor.notify(); 72 } 73 } 74 }; 75 76 private Context mContext; 77 private final Object mImplMonitor = new Object(); 78 boolean mDisableResetterForTesting; 79 80 // Whether the server should use HTTP or HTTPS. 81 public enum ServerHTTPSSetting { 82 USE_HTTP, 83 USE_HTTPS, 84 } 85 86 /** Exception class raised on failure in the EmbeddedTestServer. */ 87 public static final class EmbeddedTestServerFailure extends Error { EmbeddedTestServerFailure(String errorDesc)88 public EmbeddedTestServerFailure(String errorDesc) { 89 super(errorDesc); 90 } 91 EmbeddedTestServerFailure(String errorDesc, Throwable cause)92 public EmbeddedTestServerFailure(String errorDesc, Throwable cause) { 93 super(errorDesc, cause); 94 } 95 } 96 97 /** 98 * Connection listener class, to be notified of new connections and sockets reads. 99 * 100 * Notifications are asynchronous and delivered to the UI thread. 101 */ 102 public static class ConnectionListener { 103 private final IConnectionListener mListener = 104 new IConnectionListener.Stub() { 105 @Override 106 public void acceptedSocket(final long socketId) { 107 ThreadUtils.runOnUiThread( 108 () -> { 109 ConnectionListener.this.acceptedSocket(socketId); 110 }); 111 } 112 113 @Override 114 public void readFromSocket(final long socketId) { 115 ThreadUtils.runOnUiThread( 116 () -> { 117 ConnectionListener.this.readFromSocket(socketId); 118 }); 119 } 120 }; 121 122 /** 123 * A new socket connection has been opened on the server. 124 * 125 * @param socketId Socket unique identifier. Unique as long as the socket stays open. 126 */ acceptedSocket(long socketId)127 public void acceptedSocket(long socketId) {} 128 129 /** 130 * Data has been read from a socket. 131 * 132 * @param socketId Socket unique identifier. Unique as long as the socket stays open. 133 */ readFromSocket(long socketId)134 public void readFromSocket(long socketId) {} 135 getListener()136 private IConnectionListener getListener() { 137 return mListener; 138 } 139 } 140 141 /** Bind the service that will run the native server object. 142 * 143 * @param context The context to use to bind the service. This will also be used to unbind 144 * the service at server destruction time. 145 * @param httpsSetting Whether the server should use HTTPS. 146 */ initializeNative(Context context, ServerHTTPSSetting httpsSetting)147 public void initializeNative(Context context, ServerHTTPSSetting httpsSetting) { 148 mContext = context; 149 150 Intent intent = new Intent(EMBEDDED_TEST_SERVER_SERVICE); 151 setIntentClassName(intent); 152 if (!mContext.bindService(intent, mConn, Context.BIND_AUTO_CREATE)) { 153 throw new EmbeddedTestServerFailure( 154 "Unable to bind to the EmbeddedTestServer service."); 155 } 156 synchronized (mImplMonitor) { 157 Log.i(TAG, "Waiting for EmbeddedTestServer service connection."); 158 while (mImpl == null) { 159 try { 160 mImplMonitor.wait(SERVICE_CONNECTION_WAIT_INTERVAL_MS); 161 } catch (InterruptedException e) { 162 // Ignore the InterruptedException. Rely on the outer while loop to re-run. 163 } 164 Log.i(TAG, "Still waiting for EmbeddedTestServer service connection."); 165 } 166 Log.i(TAG, "EmbeddedTestServer service connected."); 167 boolean initialized = false; 168 try { 169 initialized = mImpl.initializeNative(httpsSetting == ServerHTTPSSetting.USE_HTTPS); 170 } catch (RemoteException e) { 171 Log.e(TAG, "Failed to initialize native server.", e); 172 initialized = false; 173 } 174 175 if (!initialized) { 176 throw new EmbeddedTestServerFailure("Failed to initialize native server."); 177 } 178 if (!mDisableResetterForTesting) { 179 ResettersForTesting.register(this::stopAndDestroyServer); 180 } 181 182 if (httpsSetting == ServerHTTPSSetting.USE_HTTPS) { 183 try { 184 String rootCertPemPath = mImpl.getRootCertPemPath(); 185 X509Util.addTestRootCertificate(CertTestUtil.pemToDer(rootCertPemPath)); 186 } catch (Exception e) { 187 throw new EmbeddedTestServerFailure( 188 "Failed to install root certificate from native server.", e); 189 } 190 } 191 } 192 } 193 194 /** Set intent package and class name that will pass to the service. 195 * 196 * @param intent The intent to use to pass into the service. 197 */ setIntentClassName(Intent intent)198 protected void setIntentClassName(Intent intent) { 199 intent.setClassName( 200 "org.chromium.net.test.support", "org.chromium.net.test.EmbeddedTestServerService"); 201 } 202 203 /** Add the default handlers and serve files from the provided directory relative to the 204 * external storage directory. 205 * 206 * @param directory The directory from which files should be served relative to the external 207 * storage directory. 208 */ addDefaultHandlers(File directory)209 public void addDefaultHandlers(File directory) { 210 addDefaultHandlers(directory.getPath()); 211 } 212 213 /** Add the default handlers and serve files from the provided directory relative to the 214 * external storage directory. 215 * 216 * @param directoryPath The path of the directory from which files should be served relative 217 * to the external storage directory. 218 */ addDefaultHandlers(String directoryPath)219 public void addDefaultHandlers(String directoryPath) { 220 try { 221 synchronized (mImplMonitor) { 222 checkServiceLocked(); 223 mImpl.addDefaultHandlers(directoryPath); 224 } 225 } catch (RemoteException e) { 226 throw new EmbeddedTestServerFailure( 227 "Failed to add default handlers and start serving files from " 228 + directoryPath 229 + ": " 230 + e.toString()); 231 } 232 } 233 234 /** Configure the server to use a particular type of SSL certificate. 235 * 236 * @param serverCertificate The type of certificate the server should use. 237 */ setSSLConfig(@erverCertificate int serverCertificate)238 public void setSSLConfig(@ServerCertificate int serverCertificate) { 239 try { 240 synchronized (mImplMonitor) { 241 checkServiceLocked(); 242 mImpl.setSSLConfig(serverCertificate); 243 } 244 } catch (RemoteException e) { 245 throw new EmbeddedTestServerFailure( 246 "Failed to set server certificate: " + e.toString()); 247 } 248 } 249 250 /** Serve files from the provided directory. 251 * 252 * @param directory The directory from which files should be served. 253 */ serveFilesFromDirectory(File directory)254 public void serveFilesFromDirectory(File directory) { 255 serveFilesFromDirectory(directory.getPath()); 256 } 257 258 /** Serve files from the provided directory. 259 * 260 * @param directoryPath The path of the directory from which files should be served. 261 */ serveFilesFromDirectory(String directoryPath)262 public void serveFilesFromDirectory(String directoryPath) { 263 try { 264 synchronized (mImplMonitor) { 265 checkServiceLocked(); 266 mImpl.serveFilesFromDirectory(directoryPath); 267 } 268 } catch (RemoteException e) { 269 throw new EmbeddedTestServerFailure( 270 "Failed to start serving files from " + directoryPath + ": " + e.toString()); 271 } 272 } 273 274 /** 275 * Sets a connection listener. Must be called after the server has been initialized, but 276 * before calling {@link start()}. 277 * 278 * @param listener The listener to set. 279 */ setConnectionListener(ConnectionListener listener)280 public void setConnectionListener(ConnectionListener listener) { 281 try { 282 synchronized (mImplMonitor) { 283 checkServiceLocked(); 284 mImpl.setConnectionListener(listener.getListener()); 285 } 286 } catch (RemoteException e) { 287 throw new EmbeddedTestServerFailure("Cannot set the listener"); 288 } 289 } 290 291 @GuardedBy("mImplMonitor") checkServiceLocked()292 private void checkServiceLocked() { 293 if (mImpl == null) { 294 throw new EmbeddedTestServerFailure("Service disconnected."); 295 } 296 } 297 298 /** Starts the server with an automatically selected port. 299 * 300 * Note that this should be called after handlers are set up, including any relevant calls 301 * serveFilesFromDirectory. 302 * 303 * @return Whether the server was successfully initialized. 304 */ start()305 public boolean start() { 306 return start(0); 307 } 308 309 /** Starts the server with the specified port. 310 * 311 * Note that this should be called after handlers are set up, including any relevant calls 312 * serveFilesFromDirectory. 313 * 314 * @param port The port to use for the server, 0 to auto-select an unused port. 315 * 316 * @return Whether the server was successfully initialized. 317 */ start(int port)318 public boolean start(int port) { 319 try { 320 synchronized (mImplMonitor) { 321 checkServiceLocked(); 322 return mImpl.start(port); 323 } 324 } catch (RemoteException e) { 325 throw new EmbeddedTestServerFailure("Failed to start server.", e); 326 } 327 } 328 329 /** Create and initialize a server with the default handlers. 330 * 331 * This handles native object initialization, server configuration, and server initialization. 332 * On returning, the server is ready for use. 333 * 334 * @param context The context in which the server will run. 335 * @return The created server. 336 */ createAndStartServer(Context context)337 public static EmbeddedTestServer createAndStartServer(Context context) { 338 return createAndStartServerWithPort(context, 0); 339 } 340 341 /** Create and initialize a server with the default handlers and specified port. 342 * 343 * This handles native object initialization, server configuration, and server initialization. 344 * On returning, the server is ready for use. 345 * 346 * @param context The context in which the server will run. 347 * @param port The port to use for the server, 0 to auto-select an unused port. 348 * @return The created server. 349 */ createAndStartServerWithPort(Context context, int port)350 public static EmbeddedTestServer createAndStartServerWithPort(Context context, int port) { 351 Assert.assertNotEquals( 352 "EmbeddedTestServer should not be created on UiThread, the instantiation will hang" 353 + " forever waiting for tasks to post to UI thread", 354 Looper.getMainLooper(), 355 Looper.myLooper()); 356 EmbeddedTestServer server = new EmbeddedTestServer(); 357 return initializeAndStartServer(server, context, port); 358 } 359 360 /** Create and initialize an HTTPS server with the default handlers. 361 * 362 * This handles native object initialization, server configuration, and server initialization. 363 * On returning, the server is ready for use. 364 * 365 * @param context The context in which the server will run. 366 * @param serverCertificate The certificate option that the server will use. 367 * @return The created server. 368 */ createAndStartHTTPSServer( Context context, @ServerCertificate int serverCertificate)369 public static EmbeddedTestServer createAndStartHTTPSServer( 370 Context context, @ServerCertificate int serverCertificate) { 371 return createAndStartHTTPSServerWithPort(context, serverCertificate, /* port= */ 0); 372 } 373 374 /** Create and initialize an HTTPS server with the default handlers and specified port. 375 * 376 * This handles native object initialization, server configuration, and server initialization. 377 * On returning, the server is ready for use. 378 * 379 * @param context The context in which the server will run. 380 * @param serverCertificate The certificate option that the server will use. 381 * @param port The port to use for the server, 0 to auto-select an unused port. 382 * @return The created server. 383 */ createAndStartHTTPSServerWithPort( Context context, @ServerCertificate int serverCertificate, int port)384 public static EmbeddedTestServer createAndStartHTTPSServerWithPort( 385 Context context, @ServerCertificate int serverCertificate, int port) { 386 Assert.assertNotEquals( 387 "EmbeddedTestServer should not be created on UiThread, " 388 + "the instantiation will hang forever waiting for tasks" 389 + " to post to UI thread", 390 Looper.getMainLooper(), 391 Looper.myLooper()); 392 EmbeddedTestServer server = new EmbeddedTestServer(); 393 return initializeAndStartHTTPSServer(server, context, serverCertificate, port); 394 } 395 396 /** Initialize a server with the default handlers. 397 * 398 * This handles native object initialization, server configuration, and server initialization. 399 * On returning, the server is ready for use. 400 * 401 * @param server The server instance that will be initialized. 402 * @param context The context in which the server will run. 403 * @param port The port to use for the server, 0 to auto-select an unused port. 404 * @return The created server. 405 */ initializeAndStartServer( T server, Context context, int port)406 public static <T extends EmbeddedTestServer> T initializeAndStartServer( 407 T server, Context context, int port) { 408 server.initializeNative(context, ServerHTTPSSetting.USE_HTTP); 409 server.addDefaultHandlers(""); 410 if (!server.start(port)) { 411 throw new EmbeddedTestServerFailure("Failed to start serving using default handlers."); 412 } 413 return server; 414 } 415 416 /** Initialize a server with the default handlers that uses HTTPS with the given certificate 417 * option. 418 * 419 * This handles native object initialization, server configuration, and server initialization. 420 * On returning, the server is ready for use. 421 * 422 * @param server The server instance that will be initialized. 423 * @param context The context in which the server will run. 424 * @param serverCertificate The certificate option that the server will use. 425 * @param port The port to use for the server. 426 * @return The created server. 427 */ initializeAndStartHTTPSServer( T server, Context context, @ServerCertificate int serverCertificate, int port)428 public static <T extends EmbeddedTestServer> T initializeAndStartHTTPSServer( 429 T server, Context context, @ServerCertificate int serverCertificate, int port) { 430 server.initializeNative(context, ServerHTTPSSetting.USE_HTTPS); 431 server.addDefaultHandlers(""); 432 server.setSSLConfig(serverCertificate); 433 if (!server.start(port)) { 434 throw new EmbeddedTestServerFailure("Failed to start serving using default handlers."); 435 } 436 return server; 437 } 438 439 /** Get the full URL for the given relative URL. 440 * 441 * @param relativeUrl The relative URL for which a full URL will be obtained. 442 * @return The URL as a String. 443 */ getURL(String relativeUrl)444 public String getURL(String relativeUrl) { 445 try { 446 synchronized (mImplMonitor) { 447 checkServiceLocked(); 448 return mImpl.getURL(relativeUrl); 449 } 450 } catch (RemoteException e) { 451 throw new EmbeddedTestServerFailure("Failed to get URL for " + relativeUrl, e); 452 } 453 } 454 455 /** Get the full URL for the given relative URL. Similar to the above method but uses the given 456 * hostname instead of 127.0.0.1. The hostname should be resolved to 127.0.0.1. 457 * 458 * @param hostName The host name which should be used. 459 * @param relativeUrl The relative URL for which a full URL should be returned. 460 * @return The URL as a String. 461 */ getURLWithHostName(String hostname, String relativeUrl)462 public String getURLWithHostName(String hostname, String relativeUrl) { 463 try { 464 synchronized (mImplMonitor) { 465 checkServiceLocked(); 466 return mImpl.getURLWithHostName(hostname, relativeUrl); 467 } 468 } catch (RemoteException e) { 469 throw new EmbeddedTestServerFailure( 470 "Failed to get URL for " + hostname + " and " + relativeUrl, e); 471 } 472 } 473 474 /** Get the full URLs for the given relative URLs. 475 * 476 * @see #getURL(String) 477 * 478 * @param relativeUrls The relative URLs for which full URLs will be obtained. 479 * @return The URLs as a String array. 480 */ getURLs(String... relativeUrls)481 public String[] getURLs(String... relativeUrls) { 482 String[] absoluteUrls = new String[relativeUrls.length]; 483 484 for (int i = 0; i < relativeUrls.length; ++i) absoluteUrls[i] = getURL(relativeUrls[i]); 485 486 return absoluteUrls; 487 } 488 489 /** 490 * Stop and destroy the server. 491 * 492 * This handles stopping the server and destroying the native object. 493 */ stopAndDestroyServer()494 public void stopAndDestroyServer() { 495 synchronized (mImplMonitor) { 496 // ResettersForTesting call can cause this to be called multiple times. 497 if (mImpl == null) { 498 return; 499 } 500 try { 501 if (!mImpl.shutdownAndWaitUntilComplete()) { 502 throw new EmbeddedTestServerFailure("Failed to stop server."); 503 } 504 mImpl.destroy(); 505 mImpl = null; 506 } catch (RemoteException e) { 507 throw new EmbeddedTestServerFailure("Failed to shut down.", e); 508 } finally { 509 mContext.unbindService(mConn); 510 } 511 } 512 } 513 514 /** Get the path of the PEM file of the root cert. */ getRootCertPemPath()515 public String getRootCertPemPath() { 516 try { 517 synchronized (mImplMonitor) { 518 checkServiceLocked(); 519 return mImpl.getRootCertPemPath(); 520 } 521 } catch (RemoteException e) { 522 throw new EmbeddedTestServerFailure("Failed to get root cert's path", e); 523 } 524 } 525 getPreClassHook()526 public static ClassHook getPreClassHook() { 527 return (targetContext, testClass) -> EmbeddedTestServer.setUpClass(testClass); 528 } 529 setUpClass(Class<?> clazz)530 public static void setUpClass(Class<?> clazz) { 531 if (sTestRootInitDone) { 532 return; 533 } 534 535 // Always try to add the testing HTTPS root to the cert verifier. We do this here because we 536 // need this to happen before the native code loads the user-added roots, and this is the 537 // safest place to put it. 538 try { 539 // Use the same PEM file as net/test/embedded_test_server/embedded_test_server.cc. 540 String rootCertPemPath = 541 UrlUtils.getIsolatedTestFilePath("net/data/ssl/certificates/root_ca_cert.pem"); 542 byte[] rootCertBytesDer = CertTestUtil.pemToDer(rootCertPemPath); 543 X509Util.setTestRootCertificateForBuiltin(rootCertBytesDer); 544 sTestRootInitDone = true; 545 } catch (Exception e) { 546 throw new EmbeddedTestServer.EmbeddedTestServerFailure( 547 "Failed to install root certificate.", e); 548 } 549 } 550 } 551