| /* |
| * Copyright (C) 2015 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package libcore.net; |
| |
| import junit.framework.TestCase; |
| import libcore.io.IoUtils; |
| import java.io.Closeable; |
| import java.io.IOException; |
| import java.net.JarURLConnection; |
| import java.net.ServerSocket; |
| import java.net.Socket; |
| import java.net.URL; |
| import java.util.Arrays; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.Future; |
| import java.util.concurrent.FutureTask; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.TimeoutException; |
| import java.util.logging.ErrorManager; |
| import java.util.logging.Level; |
| import java.util.logging.LogRecord; |
| import java.util.logging.SocketHandler; |
| |
| public class NetworkSecurityPolicyTest extends TestCase { |
| |
| private NetworkSecurityPolicy mOriginalPolicy; |
| |
| @Override |
| protected void setUp() throws Exception { |
| super.setUp(); |
| mOriginalPolicy = NetworkSecurityPolicy.getInstance(); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| try { |
| NetworkSecurityPolicy.setInstance(mOriginalPolicy); |
| } finally { |
| super.tearDown(); |
| } |
| } |
| |
| public void testCleartextTrafficPolicySetterAndGetter() { |
| NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(false)); |
| assertEquals(false, NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted()); |
| |
| NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(true)); |
| assertEquals(true, NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted()); |
| |
| NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(false)); |
| assertEquals(false, NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted()); |
| |
| NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(true)); |
| assertEquals(true, NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted()); |
| } |
| |
| public void testCleartextTrafficPolicyWithHttpURLConnection() throws Exception { |
| // Assert that client transmits some data when cleartext traffic is permitted. |
| NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(true)); |
| try (CapturingServerSocket server = new CapturingServerSocket()) { |
| URL url = new URL("http://localhost:" + server.getPort() + "/test.txt"); |
| try { |
| url.openConnection().getContent(); |
| fail(); |
| } catch (IOException expected) { |
| } |
| server.assertDataTransmittedByClient(); |
| } |
| |
| // Assert that client does not transmit any data when cleartext traffic is not permitted and |
| // that URLConnection.openConnection or getContent fail with an IOException. |
| NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(false)); |
| try (CapturingServerSocket server = new CapturingServerSocket()) { |
| URL url = new URL("http://localhost:" + server.getPort() + "/test.txt"); |
| try { |
| url.openConnection().getContent(); |
| fail(); |
| } catch (IOException expected) { |
| } |
| server.assertNoDataTransmittedByClient(); |
| } |
| } |
| |
| public void testCleartextTrafficPolicyWithFtpURLConnection() throws Exception { |
| // Assert that client transmits some data when cleartext traffic is permitted. |
| NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(true)); |
| byte[] serverReplyOnConnect = "220\r\n".getBytes("US-ASCII"); |
| try (CapturingServerSocket server = new CapturingServerSocket(serverReplyOnConnect)) { |
| URL url = new URL("ftp://localhost:" + server.getPort() + "/test.txt"); |
| try { |
| url.openConnection().getContent(); |
| fail(); |
| } catch (IOException expected) { |
| } |
| server.assertDataTransmittedByClient(); |
| } |
| |
| // Assert that client does not transmit any data when cleartext traffic is not permitted and |
| // that URLConnection.openConnection or getContent fail with an IOException. |
| NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(false)); |
| try (CapturingServerSocket server = new CapturingServerSocket(serverReplyOnConnect)) { |
| URL url = new URL("ftp://localhost:" + server.getPort() + "/test.txt"); |
| try { |
| url.openConnection().getContent(); |
| fail(); |
| } catch (IOException expected) { |
| } |
| server.assertNoDataTransmittedByClient(); |
| } |
| } |
| |
| public void testCleartextTrafficPolicyWithJarHttpURLConnection() throws Exception { |
| // Assert that client transmits some data when cleartext traffic is permitted. |
| NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(true)); |
| try (CapturingServerSocket server = new CapturingServerSocket()) { |
| URL url = new URL("jar:http://localhost:" + server.getPort() + "/test.jar!/"); |
| try { |
| ((JarURLConnection) url.openConnection()).getManifest(); |
| fail(); |
| } catch (IOException expected) { |
| } |
| server.assertDataTransmittedByClient(); |
| } |
| |
| // Assert that client does not transmit any data when cleartext traffic is not permitted and |
| // that JarURLConnection.openConnection or getManifest fail with an IOException. |
| NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(false)); |
| try (CapturingServerSocket server = new CapturingServerSocket()) { |
| URL url = new URL("jar:http://localhost:" + server.getPort() + "/test.jar!/"); |
| try { |
| ((JarURLConnection) url.openConnection()).getManifest(); |
| fail(); |
| } catch (IOException expected) { |
| } |
| server.assertNoDataTransmittedByClient(); |
| } |
| } |
| |
| public void testCleartextTrafficPolicyWithJarFtpURLConnection() throws Exception { |
| // Assert that client transmits some data when cleartext traffic is permitted. |
| NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(true)); |
| byte[] serverReplyOnConnect = "220\r\n".getBytes("US-ASCII"); |
| try (CapturingServerSocket server = new CapturingServerSocket(serverReplyOnConnect)) { |
| URL url = new URL("jar:ftp://localhost:" + server.getPort() + "/test.jar!/"); |
| try { |
| ((JarURLConnection) url.openConnection()).getManifest(); |
| fail(); |
| } catch (IOException expected) { |
| } |
| server.assertDataTransmittedByClient(); |
| } |
| |
| // Assert that client does not transmit any data when cleartext traffic is not permitted and |
| // that JarURLConnection.openConnection or getManifest fail with an IOException. |
| NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(false)); |
| try (CapturingServerSocket server = new CapturingServerSocket(serverReplyOnConnect)) { |
| URL url = new URL("jar:ftp://localhost:" + server.getPort() + "/test.jar!/"); |
| try { |
| ((JarURLConnection) url.openConnection()).getManifest(); |
| fail(); |
| } catch (IOException expected) { |
| } |
| server.assertNoDataTransmittedByClient(); |
| } |
| } |
| |
| public void testCleartextTrafficPolicyWithLoggingSocketHandler() throws Exception { |
| // Assert that client transmits some data when cleartext traffic is permitted. |
| NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(true)); |
| try (CapturingServerSocket server = new CapturingServerSocket()) { |
| SocketHandler logger = new SocketHandler("localhost", server.getPort()); |
| MockErrorManager mockErrorManager = new MockErrorManager(); |
| logger.setErrorManager(mockErrorManager); |
| logger.setLevel(Level.ALL); |
| LogRecord record = new LogRecord(Level.INFO, "A log record"); |
| assertTrue(logger.isLoggable(record)); |
| logger.publish(record); |
| assertNull(mockErrorManager.getMostRecentException()); |
| server.assertDataTransmittedByClient(); |
| } |
| |
| // Assert that client does not transmit any data when cleartext traffic is not permitted. |
| NetworkSecurityPolicy.setInstance(new TestNetworkSecurityPolicy(false)); |
| try (CapturingServerSocket server = new CapturingServerSocket()) { |
| try { |
| new SocketHandler("localhost", server.getPort()); |
| fail(); |
| } catch (IOException expected) { |
| } |
| server.assertNoDataTransmittedByClient(); |
| } |
| } |
| |
| /** |
| * Server socket which listens on a local port and captures the first chunk of data transmitted |
| * by the client. |
| */ |
| private static class CapturingServerSocket implements Closeable { |
| private final ServerSocket mSocket; |
| private final int mPort; |
| private final Thread mListeningThread; |
| private final FutureTask<byte[]> mFirstChunkReceivedFuture; |
| |
| /** |
| * Constructs a new socket listening on a local port. |
| */ |
| public CapturingServerSocket() throws IOException { |
| this(null); |
| } |
| |
| /** |
| * Constructs a new socket listening on a local port, which sends the provided reply as |
| * soon as a client connects to it. |
| */ |
| public CapturingServerSocket(final byte[] replyOnConnect) throws IOException { |
| mSocket = new ServerSocket(0); |
| mPort = mSocket.getLocalPort(); |
| mFirstChunkReceivedFuture = new FutureTask<byte[]>(new Callable<byte[]>() { |
| @Override |
| public byte[] call() throws Exception { |
| try (Socket client = mSocket.accept()) { |
| // Reply (if requested) |
| if (replyOnConnect != null) { |
| client.getOutputStream().write(replyOnConnect); |
| client.getOutputStream().flush(); |
| } |
| |
| // Read request |
| byte[] buf = new byte[64 * 1024]; |
| int chunkSize = client.getInputStream().read(buf); |
| if (chunkSize == -1) { |
| // Connection closed without any data received |
| return new byte[0]; |
| } |
| // Received some data |
| return Arrays.copyOf(buf, chunkSize); |
| } finally { |
| IoUtils.closeQuietly(mSocket); |
| } |
| } |
| }); |
| mListeningThread = new Thread(mFirstChunkReceivedFuture); |
| mListeningThread.start(); |
| } |
| |
| public int getPort() { |
| return mPort; |
| } |
| |
| public Future<byte[]> getFirstReceivedChunkFuture() { |
| return mFirstChunkReceivedFuture; |
| } |
| |
| @Override |
| public void close() { |
| IoUtils.closeQuietly(mSocket); |
| mListeningThread.interrupt(); |
| } |
| |
| private void assertDataTransmittedByClient() |
| throws Exception { |
| byte[] firstChunkFromClient = getFirstReceivedChunkFuture().get(2, TimeUnit.SECONDS); |
| if ((firstChunkFromClient == null) || (firstChunkFromClient.length == 0)) { |
| fail("Client did not transmit any data to server"); |
| } |
| } |
| |
| private void assertNoDataTransmittedByClient() |
| throws Exception { |
| byte[] firstChunkFromClient; |
| try { |
| firstChunkFromClient = getFirstReceivedChunkFuture().get(2, TimeUnit.SECONDS); |
| } catch (TimeoutException expected) { |
| return; |
| } |
| if ((firstChunkFromClient != null) && (firstChunkFromClient.length > 0)) { |
| fail("Client transmitted " + firstChunkFromClient.length+ " bytes: " |
| + new String(firstChunkFromClient, "US-ASCII")); |
| } |
| } |
| } |
| |
| private static class MockErrorManager extends ErrorManager { |
| private Exception mMostRecentException; |
| |
| public Exception getMostRecentException() { |
| synchronized (this) { |
| return mMostRecentException; |
| } |
| } |
| |
| @Override |
| public void error(String message, Exception exception, int errorCode) { |
| synchronized (this) { |
| mMostRecentException = exception; |
| } |
| } |
| } |
| |
| private static class TestNetworkSecurityPolicy extends NetworkSecurityPolicy { |
| private final boolean mCleartextTrafficPermitted; |
| |
| public TestNetworkSecurityPolicy(boolean cleartextTrafficPermitted) { |
| mCleartextTrafficPermitted = cleartextTrafficPermitted; |
| } |
| |
| @Override |
| public boolean isCleartextTrafficPermitted() { |
| return mCleartextTrafficPermitted; |
| } |
| } |
| } |