OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 package org.chromium.net.impl; | |
5 | |
6 import android.content.Context; | |
7 import android.support.annotation.IntDef; | |
8 | |
9 import org.chromium.base.Log; | |
10 import org.chromium.base.VisibleForTesting; | |
11 import org.chromium.net.CronetEngine; | |
12 import org.chromium.net.ICronetEngineBuilder; | |
13 | |
14 import java.io.File; | |
15 import java.lang.annotation.Retention; | |
16 import java.lang.annotation.RetentionPolicy; | |
17 import java.net.IDN; | |
18 import java.util.Date; | |
19 import java.util.HashSet; | |
20 import java.util.LinkedList; | |
21 import java.util.List; | |
22 import java.util.Set; | |
23 import java.util.regex.Pattern; | |
24 | |
25 /** | |
26 * Implementation of {@link ICronetEngineBuilder}. | |
27 */ | |
28 public class CronetEngineBuilderImpl extends ICronetEngineBuilder { | |
29 /** | |
30 * A hint that a host supports QUIC. | |
31 */ | |
32 public static class QuicHint { | |
33 // The host. | |
34 final String mHost; | |
35 // Port of the server that supports QUIC. | |
36 final int mPort; | |
37 // Alternate protocol port. | |
38 final int mAlternatePort; | |
39 | |
40 QuicHint(String host, int port, int alternatePort) { | |
41 mHost = host; | |
42 mPort = port; | |
43 mAlternatePort = alternatePort; | |
44 } | |
45 } | |
46 | |
47 /** | |
48 * A public key pin. | |
49 */ | |
50 public static class Pkp { | |
51 // Host to pin for. | |
52 final String mHost; | |
53 // Array of SHA-256 hashes of keys. | |
54 final byte[][] mHashes; | |
55 // Should pin apply to subdomains? | |
56 final boolean mIncludeSubdomains; | |
57 // When the pin expires. | |
58 final Date mExpirationDate; | |
59 | |
60 Pkp(String host, byte[][] hashes, boolean includeSubdomains, Date expira tionDate) { | |
61 mHost = host; | |
62 mHashes = hashes; | |
63 mIncludeSubdomains = includeSubdomains; | |
64 mExpirationDate = expirationDate; | |
65 } | |
66 } | |
67 | |
68 private static final String TAG = "CronetEngineBuilder"; | |
69 private static final Pattern INVALID_PKP_HOST_NAME = Pattern.compile("^[0-9\ \.]*$"); | |
70 | |
71 // Private fields are simply storage of configuration for the resulting Cron etEngine. | |
72 // See setters below for verbose descriptions. | |
73 private final Context mContext; | |
74 private final List<QuicHint> mQuicHints = new LinkedList<>(); | |
75 private final List<Pkp> mPkps = new LinkedList<>(); | |
76 private boolean mPublicKeyPinningBypassForLocalTrustAnchorsEnabled; | |
77 private String mUserAgent; | |
78 private String mStoragePath; | |
79 private boolean mLegacyModeEnabled; | |
80 private CronetEngine.Builder.LibraryLoader mLibraryLoader; | |
81 private String mLibraryName; | |
82 private boolean mQuicEnabled; | |
83 private boolean mHttp2Enabled; | |
84 private boolean mSdchEnabled; | |
85 private String mDataReductionProxyKey; | |
86 private String mDataReductionProxyPrimaryProxy; | |
87 private String mDataReductionProxyFallbackProxy; | |
88 private String mDataReductionProxySecureProxyCheckUrl; | |
89 private boolean mDisableCache; | |
90 private int mHttpCacheMode; | |
91 private long mHttpCacheMaxSize; | |
92 private String mExperimentalOptions; | |
93 private long mMockCertVerifier; | |
94 private boolean mNetworkQualityEstimatorEnabled; | |
95 private String mCertVerifierData; | |
96 | |
97 /** | |
98 * Default config enables SPDY, disables QUIC, SDCH and HTTP cache. | |
99 * @param context Android {@link Context} for engine to use. | |
100 */ | |
101 public CronetEngineBuilderImpl(Context context) { | |
102 mContext = context; | |
103 setLibraryName("cronet"); | |
104 enableLegacyMode(false); | |
105 enableQuic(false); | |
106 enableHttp2(true); | |
107 enableSdch(false); | |
108 enableHttpCache(HTTP_CACHE_DISABLED, 0); | |
109 enableNetworkQualityEstimator(false); | |
110 enablePublicKeyPinningBypassForLocalTrustAnchors(true); | |
111 } | |
112 | |
113 @Override | |
114 public String getDefaultUserAgent() { | |
115 return UserAgent.from(mContext); | |
116 } | |
117 | |
118 @Override | |
119 public CronetEngineBuilderImpl setUserAgent(String userAgent) { | |
120 mUserAgent = userAgent; | |
121 return this; | |
122 } | |
123 | |
124 String getUserAgent() { | |
125 return mUserAgent; | |
126 } | |
127 | |
128 @Override | |
129 public CronetEngineBuilderImpl setStoragePath(String value) { | |
130 if (!new File(value).isDirectory()) { | |
131 throw new IllegalArgumentException("Storage path must be set to exis ting directory"); | |
132 } | |
133 mStoragePath = value; | |
134 return this; | |
135 } | |
136 | |
137 String storagePath() { | |
138 return mStoragePath; | |
139 } | |
140 | |
141 @Override | |
142 public CronetEngineBuilderImpl enableLegacyMode(boolean value) { | |
143 mLegacyModeEnabled = value; | |
144 return this; | |
145 } | |
146 | |
147 private boolean legacyMode() { | |
148 return mLegacyModeEnabled; | |
149 } | |
150 | |
151 /** | |
152 * Overrides the name of the native library backing Cronet. | |
153 * @param libName the name of the native library backing Cronet. | |
154 * @return the builder to facilitate chaining. | |
155 */ | |
156 public CronetEngineBuilderImpl setLibraryName(String libName) { | |
157 mLibraryName = libName; | |
158 return this; | |
159 } | |
160 | |
161 String libraryName() { | |
162 return mLibraryName; | |
163 } | |
164 | |
165 @Override | |
166 public CronetEngineBuilderImpl setLibraryLoader(CronetEngine.Builder.Library Loader loader) { | |
167 mLibraryLoader = loader; | |
168 return this; | |
169 } | |
170 | |
171 CronetEngine.Builder.LibraryLoader libraryLoader() { | |
172 return mLibraryLoader; | |
173 } | |
174 | |
175 @Override | |
176 public CronetEngineBuilderImpl enableQuic(boolean value) { | |
177 mQuicEnabled = value; | |
178 return this; | |
179 } | |
180 | |
181 boolean quicEnabled() { | |
182 return mQuicEnabled; | |
183 } | |
184 | |
185 /** | |
186 * Constructs default QUIC User Agent Id string including application name | |
187 * and Cronet version. Returns empty string if QUIC is not enabled. | |
188 * | |
189 * @param context Android {@link Context} to get package name from. | |
190 * @return QUIC User Agent ID string. | |
191 */ | |
192 // TODO(mef): remove |context| parameter when legacy ChromiumUrlRequestConte xt is removed. | |
193 String getDefaultQuicUserAgentId(Context context) { | |
pauljensen
2016/10/03 15:22:38
is this function used anymore? if yes, can we at l
kapishnikov
2016/10/03 23:49:28
This method is used and the returned value is pass
kapishnikov
2016/10/05 17:16:31
Removed context from the input parameter list. Cha
| |
194 return mQuicEnabled ? UserAgent.getQuicUserAgentIdFrom(context) : ""; | |
195 } | |
196 | |
197 @Override | |
198 public CronetEngineBuilderImpl enableHttp2(boolean value) { | |
199 mHttp2Enabled = value; | |
200 return this; | |
201 } | |
202 | |
203 boolean http2Enabled() { | |
204 return mHttp2Enabled; | |
205 } | |
206 | |
207 @Override | |
208 public CronetEngineBuilderImpl enableSdch(boolean value) { | |
209 mSdchEnabled = value; | |
210 return this; | |
211 } | |
212 | |
213 boolean sdchEnabled() { | |
214 return mSdchEnabled; | |
215 } | |
216 | |
217 @Override | |
218 public CronetEngineBuilderImpl enableDataReductionProxy(String key) { | |
219 mDataReductionProxyKey = key; | |
220 return this; | |
221 } | |
222 | |
223 String dataReductionProxyKey() { | |
224 return mDataReductionProxyKey; | |
225 } | |
226 | |
227 @Override | |
228 public CronetEngineBuilderImpl setDataReductionProxyOptions( | |
229 String primaryProxy, String fallbackProxy, String secureProxyCheckUr l) { | |
230 if (primaryProxy.isEmpty() || fallbackProxy.isEmpty() || secureProxyChec kUrl.isEmpty()) { | |
231 throw new IllegalArgumentException( | |
232 "Primary and fallback proxies and check url must be set"); | |
233 } | |
234 mDataReductionProxyPrimaryProxy = primaryProxy; | |
235 mDataReductionProxyFallbackProxy = fallbackProxy; | |
236 mDataReductionProxySecureProxyCheckUrl = secureProxyCheckUrl; | |
237 return this; | |
238 } | |
239 | |
240 String dataReductionProxyPrimaryProxy() { | |
241 return mDataReductionProxyPrimaryProxy; | |
242 } | |
243 | |
244 String dataReductionProxyFallbackProxy() { | |
245 return mDataReductionProxyFallbackProxy; | |
246 } | |
247 | |
248 String dataReductionProxySecureProxyCheckUrl() { | |
249 return mDataReductionProxySecureProxyCheckUrl; | |
250 } | |
251 | |
252 @IntDef({ | |
253 HTTP_CACHE_DISABLED, HTTP_CACHE_IN_MEMORY, HTTP_CACHE_DISK_NO_HTTP, HTTP_CACHE_DISK, | |
254 }) | |
255 @Retention(RetentionPolicy.SOURCE) | |
256 public @interface HttpCacheSetting {} | |
257 | |
258 /** | |
259 * Setting to disable HTTP cache. Some data may still be temporarily stored in memory. | |
260 * Passed to {@link #enableHttpCache}. | |
261 */ | |
262 static final int HTTP_CACHE_DISABLED = 0; | |
pauljensen
2016/10/03 15:22:38
we shouldn't have a copy of these constants here,
kapishnikov
2016/10/03 23:49:28
Good point. Done.
| |
263 | |
264 /** | |
265 * Setting to enable in-memory HTTP cache, including HTTP data. | |
266 * Passed to {@link #enableHttpCache}. | |
267 */ | |
268 public static final int HTTP_CACHE_IN_MEMORY = 1; | |
269 | |
270 /** | |
271 * Setting to enable on-disk cache, excluding HTTP data. | |
272 * {@link #setStoragePath} must be called prior to passing this constant to | |
273 * {@link #enableHttpCache}. | |
274 */ | |
275 static final int HTTP_CACHE_DISK_NO_HTTP = 2; | |
276 | |
277 /** | |
278 * Setting to enable on-disk cache, including HTTP data. | |
279 * {@link #setStoragePath} must be called prior to passing this constant to | |
280 * {@link #enableHttpCache}. | |
281 */ | |
282 static final int HTTP_CACHE_DISK = 3; | |
283 | |
284 @Override | |
285 public CronetEngineBuilderImpl enableHttpCache(@HttpCacheSetting int cacheMo de, long maxSize) { | |
286 if (cacheMode == HTTP_CACHE_DISK || cacheMode == HTTP_CACHE_DISK_NO_HTTP ) { | |
287 if (storagePath() == null) { | |
288 throw new IllegalArgumentException("Storage path must be set"); | |
289 } | |
290 } else { | |
291 if (storagePath() != null) { | |
292 throw new IllegalArgumentException("Storage path must not be set "); | |
293 } | |
294 } | |
295 mDisableCache = (cacheMode == HTTP_CACHE_DISABLED || cacheMode == HTTP_C ACHE_DISK_NO_HTTP); | |
296 mHttpCacheMaxSize = maxSize; | |
297 | |
298 switch (cacheMode) { | |
299 case HTTP_CACHE_DISABLED: | |
300 mHttpCacheMode = HttpCacheType.DISABLED; | |
301 break; | |
302 case HTTP_CACHE_DISK_NO_HTTP: | |
303 case HTTP_CACHE_DISK: | |
304 mHttpCacheMode = HttpCacheType.DISK; | |
305 break; | |
306 case HTTP_CACHE_IN_MEMORY: | |
307 mHttpCacheMode = HttpCacheType.MEMORY; | |
308 break; | |
309 default: | |
310 throw new IllegalArgumentException("Unknown cache mode"); | |
311 } | |
312 return this; | |
313 } | |
314 | |
315 boolean cacheDisabled() { | |
316 return mDisableCache; | |
317 } | |
318 | |
319 long httpCacheMaxSize() { | |
320 return mHttpCacheMaxSize; | |
321 } | |
322 | |
323 int httpCacheMode() { | |
324 return mHttpCacheMode; | |
325 } | |
326 | |
327 @Override | |
328 public CronetEngineBuilderImpl addQuicHint(String host, int port, int altern atePort) { | |
329 if (host.contains("/")) { | |
330 throw new IllegalArgumentException("Illegal QUIC Hint Host: " + host ); | |
331 } | |
332 mQuicHints.add(new QuicHint(host, port, alternatePort)); | |
333 return this; | |
334 } | |
335 | |
336 List<QuicHint> quicHints() { | |
337 return mQuicHints; | |
338 } | |
339 | |
340 @Override | |
341 public CronetEngineBuilderImpl addPublicKeyPins(String hostName, Set<byte[]> pinsSha256, | |
342 boolean includeSubdomains, Date expirationDate) { | |
343 if (hostName == null) { | |
344 throw new NullPointerException("The hostname cannot be null"); | |
345 } | |
346 if (pinsSha256 == null) { | |
347 throw new NullPointerException("The set of SHA256 pins cannot be nul l"); | |
348 } | |
349 if (expirationDate == null) { | |
350 throw new NullPointerException("The pin expiration date cannot be nu ll"); | |
351 } | |
352 String idnHostName = validateHostNameForPinningAndConvert(hostName); | |
353 // Convert the pin to BASE64 encoding. The hash set will eliminate dupli cations. | |
354 Set<byte[]> hashes = new HashSet<>(pinsSha256.size()); | |
355 for (byte[] pinSha256 : pinsSha256) { | |
356 if (pinSha256 == null || pinSha256.length != 32) { | |
357 throw new IllegalArgumentException("Public key pin is invalid"); | |
358 } | |
359 hashes.add(pinSha256); | |
360 } | |
361 // Add new element to PKP list. | |
362 mPkps.add(new Pkp(idnHostName, hashes.toArray(new byte[hashes.size()][]) , includeSubdomains, | |
363 expirationDate)); | |
364 return this; | |
365 } | |
366 | |
367 /** | |
368 * Returns list of public key pins. | |
369 * @return list of public key pins. | |
370 */ | |
371 List<Pkp> publicKeyPins() { | |
372 return mPkps; | |
373 } | |
374 | |
375 @Override | |
376 public CronetEngineBuilderImpl enablePublicKeyPinningBypassForLocalTrustAnch ors(boolean value) { | |
377 mPublicKeyPinningBypassForLocalTrustAnchorsEnabled = value; | |
378 return this; | |
379 } | |
380 | |
381 boolean publicKeyPinningBypassForLocalTrustAnchorsEnabled() { | |
382 return mPublicKeyPinningBypassForLocalTrustAnchorsEnabled; | |
383 } | |
384 | |
385 /** | |
386 * Checks whether a given string represents a valid host name for PKP and co nverts it | |
387 * to ASCII Compatible Encoding representation according to RFC 1122, RFC 11 23 and | |
388 * RFC 3490. This method is more restrictive than required by RFC 7469. Thus , a host | |
389 * that contains digits and the dot character only is considered invalid. | |
390 * | |
391 * Note: Currently Cronet doesn't have native implementation of host name va lidation that | |
392 * can be used. There is code that parses a provided URL but doesn't e nsure its | |
393 * correctness. The implementation relies on {@code getaddrinfo} funct ion. | |
394 * | |
395 * @param hostName host name to check and convert. | |
396 * @return true if the string is a valid host name. | |
397 * @throws IllegalArgumentException if the the given string does not represe nt a valid | |
398 * hostname. | |
399 */ | |
400 private static String validateHostNameForPinningAndConvert(String hostName) | |
401 throws IllegalArgumentException { | |
402 if (INVALID_PKP_HOST_NAME.matcher(hostName).matches()) { | |
403 throw new IllegalArgumentException("Hostname " + hostName + " is ill egal." | |
404 + " A hostname should not consist of digits and/or dots only ."); | |
405 } | |
406 // Workaround for crash, see crbug.com/634914 | |
407 if (hostName.length() > 255) { | |
408 throw new IllegalArgumentException("Hostname " + hostName + " is too long." | |
409 + " The name of the host does not comply with RFC 1122 and R FC 1123."); | |
410 } | |
411 try { | |
412 return IDN.toASCII(hostName, IDN.USE_STD3_ASCII_RULES); | |
413 } catch (IllegalArgumentException ex) { | |
414 throw new IllegalArgumentException("Hostname " + hostName + " is ill egal." | |
415 + " The name of the host does not comply with RFC 1122 and R FC 1123."); | |
416 } | |
417 } | |
418 | |
419 @Override | |
420 public CronetEngineBuilderImpl setExperimentalOptions(String options) { | |
421 mExperimentalOptions = options; | |
422 return this; | |
423 } | |
424 | |
425 public String experimentalOptions() { | |
426 return mExperimentalOptions; | |
427 } | |
428 | |
429 /** | |
430 * Sets a native MockCertVerifier for testing. See | |
431 * {@code MockCertVerifier.createMockCertVerifier} for a method that | |
432 * can be used to create a MockCertVerifier. | |
433 * @param mockCertVerifier pointer to native MockCertVerifier. | |
434 * @return the builder to facilitate chaining. | |
435 */ | |
436 @VisibleForTesting | |
437 public CronetEngineBuilderImpl setMockCertVerifierForTesting(long mockCertVe rifier) { | |
pauljensen
2016/10/03 15:22:38
@Override
kapishnikov
2016/10/03 23:49:28
This method is actually used by the unit tests dir
| |
438 mMockCertVerifier = mockCertVerifier; | |
439 return this; | |
440 } | |
441 | |
442 long mockCertVerifier() { | |
443 return mMockCertVerifier; | |
444 } | |
445 | |
446 /** | |
447 * @return true if the network quality estimator has been enabled for | |
448 * this builder. | |
449 */ | |
450 boolean networkQualityEstimatorEnabled() { | |
451 return mNetworkQualityEstimatorEnabled; | |
452 } | |
453 | |
454 @Override | |
455 public CronetEngineBuilderImpl setCertVerifierData(String certVerifierData) { | |
456 mCertVerifierData = certVerifierData; | |
457 return this; | |
458 } | |
459 | |
460 @Override | |
461 public CronetEngineBuilderImpl enableNetworkQualityEstimator(boolean value) { | |
462 mNetworkQualityEstimatorEnabled = value; | |
463 return this; | |
464 } | |
465 | |
466 String certVerifierData() { | |
467 return mCertVerifierData; | |
468 } | |
469 | |
470 /** | |
471 * Returns {@link Context} for builder. | |
472 * | |
473 * @return {@link Context} for builder. | |
474 */ | |
475 public Context getContext() { | |
pauljensen
2016/10/03 15:22:37
why public? why not package-private like other ac
kapishnikov
2016/10/03 23:49:28
Done.
| |
476 return mContext; | |
477 } | |
478 | |
479 @Override | |
480 public CronetEngine build() { | |
481 if (getUserAgent() == null) { | |
482 setUserAgent(getDefaultUserAgent()); | |
483 } | |
484 CronetEngine cronetEngine; | |
pauljensen
2016/10/03 15:22:37
final
kapishnikov
2016/10/03 23:49:28
Done.
| |
485 if (!legacyMode()) { | |
486 cronetEngine = new CronetUrlRequestContext(this); | |
487 } else { | |
488 cronetEngine = new JavaCronetEngine(getUserAgent()); | |
489 } | |
490 Log.i(TAG, "Using network stack: " + cronetEngine.getVersionString()); | |
491 // Clear MOCK_CERT_VERIFIER reference if there is any, since | |
492 // the ownership has been transferred to the engine. | |
493 mMockCertVerifier = 0; | |
494 return cronetEngine; | |
495 } | |
496 } | |
OLD | NEW |