1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.net.http;
18
19import com.google.mockwebserver.MockResponse;
20import com.google.mockwebserver.MockWebServer;
21import com.google.mockwebserver.RecordedRequest;
22import com.google.mockwebserver.SocketPolicy;
23import java.io.IOException;
24import java.io.InputStreamReader;
25import java.io.Reader;
26import java.io.StringWriter;
27import java.util.List;
28import junit.framework.TestCase;
29import libcore.javax.net.ssl.TestSSLContext;
30import org.apache.http.HttpHost;
31import org.apache.http.HttpRequest;
32import org.apache.http.HttpResponse;
33import org.apache.http.client.HttpClient;
34import org.apache.http.client.methods.HttpGet;
35import org.apache.http.conn.params.ConnRoutePNames;
36import org.apache.http.conn.params.ConnRouteParams;
37import org.apache.http.conn.scheme.Scheme;
38import org.apache.http.conn.ssl.AllowAllHostnameVerifier;
39import org.apache.http.conn.ssl.SSLSocketFactory;
40
41public abstract class AbstractProxyTest extends TestCase {
42
43    private MockWebServer server = new MockWebServer();
44
45    protected abstract HttpClient newHttpClient();
46
47    @Override protected void tearDown() throws Exception {
48        System.clearProperty("proxyHost");
49        System.clearProperty("proxyPort");
50        System.clearProperty("http.proxyHost");
51        System.clearProperty("http.proxyPort");
52        System.clearProperty("https.proxyHost");
53        System.clearProperty("https.proxyPort");
54
55        server.shutdown();
56        super.tearDown();
57    }
58
59    public void testConnectToHttps() throws Exception {
60        TestSSLContext testSSLContext = TestSSLContext.create();
61
62        server.useHttps(testSSLContext.serverContext.getSocketFactory(), false);
63        server.enqueue(new MockResponse()
64                .setResponseCode(200)
65                .setBody("this response comes via HTTPS"));
66        server.play();
67
68        HttpClient httpClient = newHttpClient();
69
70        SSLSocketFactory sslSocketFactory = newSslSocketFactory(testSSLContext);
71        sslSocketFactory.setHostnameVerifier(new AllowAllHostnameVerifier());
72        httpClient.getConnectionManager().getSchemeRegistry()
73                .register(new Scheme("https", sslSocketFactory, server.getPort()));
74
75        HttpResponse response = httpClient.execute(
76                new HttpGet("https://localhost:" + server.getPort() + "/foo"));
77        assertEquals("this response comes via HTTPS", contentToString(response));
78
79        RecordedRequest request = server.takeRequest();
80        assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
81    }
82
83    private SSLSocketFactory newSslSocketFactory(TestSSLContext testSSLContext) throws Exception {
84        // call through to Apache HTTP's non-public SSLSocketFactory constructor
85        return SSLSocketFactory.class.getConstructor(javax.net.ssl.SSLSocketFactory.class)
86                .newInstance(testSSLContext.clientContext.getSocketFactory());
87    }
88
89    /**
90     * We had bugs where proxy system properties weren't being honored.
91     * http://b/3254717
92     */
93    public void testConnectViaProxyUsingProxySystemProperty() throws Exception {
94        testConnectViaProxy(ProxyConfig.PROXY_SYSTEM_PROPERTY);
95    }
96
97    public void testConnectViaProxyUsingHttpProxySystemProperty() throws Exception {
98        testConnectViaProxy(ProxyConfig.HTTP_PROXY_SYSTEM_PROPERTY);
99    }
100
101    public void testConnectViaProxyUsingRequestParameter() throws Exception {
102        testConnectViaProxy(ProxyConfig.REQUEST_PARAMETER);
103    }
104
105    public void testConnectViaProxyUsingClientParameter() throws Exception {
106        testConnectViaProxy(ProxyConfig.CLIENT_PARAMETER);
107    }
108
109    /**
110     * http://code.google.com/p/android/issues/detail?id=2690
111     */
112    private void testConnectViaProxy(ProxyConfig proxyConfig) throws Exception {
113        MockResponse mockResponse = new MockResponse()
114                .setResponseCode(200)
115                .setBody("this response comes via a proxy");
116        server.enqueue(mockResponse);
117        server.play();
118
119        HttpClient httpProxyClient = newHttpClient();
120
121        HttpGet request = new HttpGet("http://android.com/foo");
122        proxyConfig.configure(server, httpProxyClient, request);
123
124        HttpResponse response = httpProxyClient.execute(request);
125        assertEquals("this response comes via a proxy", contentToString(response));
126
127        RecordedRequest get = server.takeRequest();
128        assertEquals("GET http://android.com/foo HTTP/1.1", get.getRequestLine());
129        assertContains(get.getHeaders(), "Host: android.com");
130    }
131
132    public void testConnectViaHttpProxyToHttpsUsingProxySystemProperty() throws Exception {
133        testConnectViaHttpProxyToHttps(ProxyConfig.PROXY_SYSTEM_PROPERTY);
134    }
135
136    public void testConnectViaHttpProxyToHttpsUsingHttpsProxySystemProperty() throws Exception {
137        testConnectViaHttpProxyToHttps(ProxyConfig.HTTPS_PROXY_SYSTEM_PROPERTY);
138    }
139
140    public void testConnectViaHttpProxyToHttpsUsingClientParameter() throws Exception {
141        testConnectViaHttpProxyToHttps(ProxyConfig.CLIENT_PARAMETER);
142    }
143
144    public void testConnectViaHttpProxyToHttpsUsingRequestParameter() throws Exception {
145        testConnectViaHttpProxyToHttps(ProxyConfig.REQUEST_PARAMETER);
146    }
147
148    private void testConnectViaHttpProxyToHttps(ProxyConfig proxyConfig) throws Exception {
149        TestSSLContext testSSLContext = TestSSLContext.create();
150
151        server.useHttps(testSSLContext.serverContext.getSocketFactory(), true);
152        server.enqueue(new MockResponse()
153                .setSocketPolicy(SocketPolicy.UPGRADE_TO_SSL_AT_END)
154                .clearHeaders());
155        server.enqueue(new MockResponse()
156                .setResponseCode(200)
157                .setBody("this response comes via a secure proxy"));
158        server.play();
159
160        HttpClient httpProxyClient = newHttpClient();
161        SSLSocketFactory sslSocketFactory = newSslSocketFactory(testSSLContext);
162        sslSocketFactory.setHostnameVerifier(new AllowAllHostnameVerifier());
163        httpProxyClient.getConnectionManager().getSchemeRegistry()
164                .register(new Scheme("https", sslSocketFactory, 443));
165
166        HttpGet request = new HttpGet("https://android.com/foo");
167        proxyConfig.configure(server, httpProxyClient, request);
168
169        HttpResponse response = httpProxyClient.execute(request);
170        assertEquals("this response comes via a secure proxy", contentToString(response));
171
172        RecordedRequest connect = server.takeRequest();
173        assertEquals("Connect line failure on proxy " + proxyConfig,
174                "CONNECT android.com:443 HTTP/1.1", connect.getRequestLine());
175        assertContains(connect.getHeaders(), "Host: android.com");
176
177        RecordedRequest get = server.takeRequest();
178        assertEquals("GET /foo HTTP/1.1", get.getRequestLine());
179        assertContains(get.getHeaders(), "Host: android.com");
180    }
181
182    public void testClientParamPreferredOverSystemProperty() throws Exception {
183        testParamPreferredOverSystemProperty(ProxyConfig.CLIENT_PARAMETER);
184    }
185
186    public void testRequestParamPreferredOverSystemProperty() throws Exception {
187        testParamPreferredOverSystemProperty(ProxyConfig.REQUEST_PARAMETER);
188    }
189
190    private void testParamPreferredOverSystemProperty(ProxyConfig proxyConfig) throws Exception {
191        server.enqueue(new MockResponse().setBody("Via request parameter proxy!"));
192        server.play();
193        System.setProperty("http.proxyHost", "proxy.foo");
194        System.setProperty("http.proxyPort", "8080");
195
196        HttpClient client = newHttpClient();
197        HttpGet request = new HttpGet("http://origin.foo/bar");
198        proxyConfig.configure(server, client, request);
199        HttpResponse response = client.execute(request);
200        assertEquals("Via request parameter proxy!", contentToString(response));
201
202        RecordedRequest recordedRequest = server.takeRequest();
203        assertEquals("GET http://origin.foo/bar HTTP/1.1", recordedRequest.getRequestLine());
204    }
205
206    public void testExplicitNoProxyCancelsSystemProperty() throws Exception {
207        server.enqueue(new MockResponse().setBody("Via the origin server!"));
208        server.play();
209        System.setProperty("http.proxyHost", "proxy.foo");
210        System.setProperty("http.proxyPort", "8080");
211
212        HttpClient client = newHttpClient();
213        HttpGet request = new HttpGet(server.getUrl("/bar").toURI());
214        request.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY, ConnRouteParams.NO_HOST);
215        HttpResponse response = client.execute(request);
216        assertEquals("Via the origin server!", contentToString(response));
217
218        RecordedRequest recordedRequest = server.takeRequest();
219        assertEquals("GET /bar HTTP/1.1", recordedRequest.getRequestLine());
220    }
221
222    // http://b/5372438
223    public void testRetryWithProxy() throws Exception {
224        server.enqueue(new MockResponse()
225                .setSocketPolicy(SocketPolicy.DISCONNECT_AT_START));
226        server.play();
227
228        HttpClient httpProxyClient = newHttpClient();
229        HttpGet request = new HttpGet("http://android.com/foo");
230        ProxyConfig.REQUEST_PARAMETER.configure(server, httpProxyClient, request);
231
232        try {
233            httpProxyClient.execute(request);
234            fail();
235        } catch (IOException expected) {
236        }
237    }
238
239    enum ProxyConfig {
240        PROXY_SYSTEM_PROPERTY() {
241            @Override void configure(MockWebServer server, HttpClient client, HttpRequest request) {
242                System.setProperty("proxyHost", "localhost");
243                System.setProperty("proxyPort", Integer.toString(server.getPort()));
244            }
245        },
246        HTTP_PROXY_SYSTEM_PROPERTY() {
247            @Override void configure(MockWebServer server, HttpClient client, HttpRequest request) {
248                System.setProperty("http.proxyHost", "localhost");
249                System.setProperty("http.proxyPort", Integer.toString(server.getPort()));
250            }
251        },
252        HTTPS_PROXY_SYSTEM_PROPERTY() {
253            @Override void configure(MockWebServer server, HttpClient client, HttpRequest request) {
254                System.setProperty("https.proxyHost", "localhost");
255                System.setProperty("https.proxyPort", Integer.toString(server.getPort()));
256            }
257        },
258        CLIENT_PARAMETER() {
259            @Override void configure(MockWebServer server, HttpClient client, HttpRequest request) {
260                client.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY,
261                        new HttpHost("localhost", server.getPort()));
262            }
263        },
264        REQUEST_PARAMETER() {
265            @Override void configure(MockWebServer server, HttpClient client, HttpRequest request) {
266                request.getParams().setParameter(ConnRoutePNames.DEFAULT_PROXY,
267                        new HttpHost("localhost", server.getPort()));
268            }
269        };
270
271        abstract void configure(MockWebServer proxy, HttpClient client, HttpRequest request);
272    }
273
274    private void assertContains(List<String> headers, String header) {
275        assertTrue(headers.toString(), headers.contains(header));
276    }
277
278    private String contentToString(HttpResponse response) throws IOException {
279        StringWriter writer = new StringWriter();
280        char[] buffer = new char[1024];
281        Reader reader = new InputStreamReader(response.getEntity().getContent());
282        int length;
283        while ((length = reader.read(buffer)) != -1) {
284            writer.write(buffer, 0, length);
285        }
286        reader.close();
287        return writer.toString();
288    }
289}
290